summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics/forms
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/html/semantics/forms')
-rw-r--r--testing/web-platform/tests/html/semantics/forms/META.yml2
-rw-r--r--testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-ltr-iframe.html4
-rw-r--r--testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-ltr.html36
-rw-r--r--testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-auto.html37
-rw-r--r--testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-inherited.html38
-rw-r--r--testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-manual.html26
-rw-r--r--testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/disabled-elements-01.html84
-rw-r--r--testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/formAction_document_address.html93
-rw-r--r--testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/formaction.html42
-rw-r--r--testing/web-platform/tests/html/semantics/forms/beforeinput.tentative.html58
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-checkValidity.html145
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-reportValidity.html145
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validate.html127
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-badInput.html46
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-customError.html48
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-patternMismatch.html51
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeOverflow-weekmonth.html47
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeOverflow.html89
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow-weekmonth.html47
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow.html87
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-stepMismatch.html81
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-tooLong.html50
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-tooShort.html52
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-typeMismatch.html41
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valid-weekmonth.html37
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valid.html110
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valueMissing-weekmonth.html55
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valueMissing.html147
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-willValidate-datalist.html67
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/form-validation-willValidate.html96
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/infinite_backtracking.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/input-number-validity-dynamic-value-no-change.html18
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/input-pattern-dynamic-value.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/inputwillvalidate.html26
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/number-input-lang-validationMessage-crash.html15
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/radio-valueMissing.html90
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/reportValidity-crash.html37
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/support/validator.js481
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-email-delete-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-password-delete-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-search-delete-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-tel-delete-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-text-delete-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-url-delete-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooLong-textarea-delete-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-email-add-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-password-add-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-search-add-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-tel-add-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-text-add-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-url-add-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/tooShort-textarea-add-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/association.window.js7
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form.html91
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_attribute.html233
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table.html50
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table_2.html45
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table_3.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/FormDataEvent.window.js20
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/SubmitEvent.window.js41
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/constructing-form-data-set.html161
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/enctypes-helper.js187
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js99
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv-form.html27
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv.html52
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-2.html49
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-3.html50
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-default-action.html108
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-multiple-targets.html37
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault-click.html67
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault.html59
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-to-different-origin-frame.html63
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit.html50
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-echo.py7
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submission-algorithm.html165
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submit-iframe-then-location-navigate.html23
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/getactionurl.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/historical.window.js19
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/implicit-submission.optional.html47
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-form-submit.tentative.html32
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-navigation-then-form-submit.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/multipart-formdata.window.js346
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/newline-normalization.html112
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html15
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/request-submit-activation.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/file-submission.py10
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form-submission.py12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form.html4
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/getactionurl-iframe.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/jsurl-form-submit-iframe.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/location.html4
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/targetted-form.js38
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/submission-checks.window.js62
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-entity-body.html114
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-file.sub.html25
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/text-plain.window.js215
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/url-encoded.html51
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/urlencoded2.window.js215
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-target/form-target-iframe-helper.py3
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-target/form-target-iframe.html29
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-target/form-target-request-header.html31
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-base-target.html13
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-button-target.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-form-target.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-input-target.html13
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-target/resources/endpoint.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-target/resources/form-target-request-header-helper.py14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-target/resources/reltester.js82
-rw-r--r--testing/web-platform/tests/html/semantics/forms/historical-search-event.html21
-rw-r--r--testing/web-platform/tests/html/semantics/forms/historical.html97
-rw-r--r--testing/web-platform/tests/html/semantics/forms/input-change-event-properties.html86
-rw-r--r--testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-event.html18
-rw-r--r--testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-form-2.html61
-rw-r--r--testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-form-event-realm.html38
-rw-r--r--testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-form.html118
-rw-r--r--testing/web-platform/tests/html/semantics/forms/resetting-a-form/support/reset-form-event-realm.html3
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/defaultSelection.html28
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/original-id.json1
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/select-event.html143
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-after-content-change.html144
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application-textarea.html40
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application.html112
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end-extra.html153
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end.html206
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html127
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection.html206
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/setSelectionRange.html18
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/textarea-selection-while-parsing.xhtml20
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setRangeText.html155
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setSelectionRange.html304
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/active-onblur.html33
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-activate-frame.html3
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-activate-keyup-prevented.html38
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-activate.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-checkvalidity.html44
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-click-submits.html210
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-events.html166
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-labels.html38
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-menu-historical.html25
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-setcustomvalidity.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-children.html34
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-remove-children-jssubmit.html33
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-remove-children.html32
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-remove-jssubmit.html36
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-type-enumerated-ascii-case-insensitive.html34
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-type.html41
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-untrusted-key-event.html112
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-validation.html29
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-validationmessage.html40
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-validity.html40
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-willvalidate-readonly-attribute.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-button-element/button-willvalidate.html40
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-datalist-element/datalistoptions.html43
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-datalist-element/remove-datalist-crash.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/HTMLFieldSetElement.html54
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/README.md12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/aria-manual.html7
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/baseline-manual.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-appearance-none-manual.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-display-contents-manual.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-display-none-manual.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-div-display-contents-manual.html13
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-role-none-manual.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-role-presentation-manual.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-visibility-collapse-manual.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-visibility-hidden-manual.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/flexbox-manual.html13
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/grid-manual.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-abspos-manual.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-child-display-none-manual.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-child-visibility-hidden-manual.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-display-contents-manual.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-display-none-manual.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-float-manual.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-role-group-manual.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-visibility-collapse-manual.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-visibility-hidden-manual.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/multiple-legends-manual.html10
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/role-manual.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/shadow-dom-manual.html32
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/title-attribute-and-empty-legend-manual.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/disabled-001.html78
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/disabled-002.xhtml25
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-checkvalidity.html45
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-intrinsic-size.html74
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-setcustomvalidity.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-validationmessage.html41
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-validity.html41
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-willvalidate.html41
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-in-inactive-document-crash.html6
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-reflection-with-base-url.html36
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-reflection.html35
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-submission-with-base-url.html56
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-submission.html56
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-action.html43
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-autocomplete.html130
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-checkvalidity.html47
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-filter.html192
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-interfaces-01.html20
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-matches.html46
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-nameditem-01.html43
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-nameditem-02.html28
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-sameobject.html20
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-indexed-element.html45
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-length.html38
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-nameditem.html418
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/form-requestsubmit.html215
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-no-action-with-base.html19
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-no-action.html18
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-with-action-and-base.sub.html19
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-with-action.sub.html18
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-form-element/resources/target/form-action-url-target.html5
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/anchor-active-contenteditable.html25
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/anchor-contenteditable-navigate.html25
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/button.html66
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-onblur-with-click.html158
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-onblur.html52
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-space-key-being-disabled.html90
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-space-key-prevented-default.html55
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-space-key-untrusted-event.html48
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/checkbox-click-events.html109
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/checkbox.html149
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/checked.xhtml19
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/clone.html150
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/cloning-steps.html64
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/color.html45
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/date.html90
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/datetime-local-trailing-zeros.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/datetime-local.html42
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/datetime-weekmonth.html51
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/datetime.html60
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/defaultValue-clobbering.html36
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/disabled-click-picker-manual.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/email-set-value.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/email.html66
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/event-select-manual.html39
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/file-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/files.html83
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/focus-dynamic-type-change.html51
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/hidden-charset-case-sensitive-child.html5
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/hidden-charset-case-sensitive.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/hidden.html74
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/image-click-form-data.html28
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/image01-ref.html5
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/image01.html7
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-checkvalidity.html44
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-form-detach-style-crash.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-height.html42
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-labels.html49
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-list.html67
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-seconds-leading-zeroes.html52
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-setcustomvalidity.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepdown-weekmonth.html22
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepdown.html43
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepup-weekmonth.html22
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepup.html44
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-submit-remove-jssubmit.html36
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-button.html51
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-checkbox.html60
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-types.js24
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-untrusted-key-event.html225
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-validationmessage.html40
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-validity.html40
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-value-invalidstateerr.html41
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasdate-invalidstateerr.html41
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasdate-stepping.html83
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasdate.html108
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasnumber-invalidstateerr.html39
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasnumber-stepping.html94
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasnumber.html151
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-whitespace.html34
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-width.html42
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-willvalidate.html40
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/invalid-datalist-options-crash.html6
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/large-step-crash.html9
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/maxlength-manual.html37
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/maxlength-number.html19
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/maxlength.html55
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/minlength.html55
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/month.html100
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder-cr.html1
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder-crlf.html19
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder-ref.html5
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder.html19
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/number-disabled.html18
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/number.html57
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/password-delete-space.html20
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/password.html79
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/pattern_attribute.html34
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/radio-double-activate-pseudo.html22
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/radio-groupname-case.html83
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/radio-input-cancel.html41
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/radio-morphed.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/radio-multiple-selected.html21
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/radio.html351
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-2.html43
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-intrinsic-size-ref.html97
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-intrinsic-size.html85
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-added-repaint.html19
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-change-repaint.html24
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-duplicate-id-repaint.html26
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-nonexistent.html20
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-option-add-repaint.html21
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-option-remove-repaint.html20
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-option-value-change-repaint.html20
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-restore-oninput-onchange-event.https.html52
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-setattribute-value-ref.html7
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-setattribute-value.html27
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-01-notref.html3
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-01.html13
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-02-ref.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-02.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-03-notref.html9
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-03.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-04-ref.html7
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-04.html15
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-05-ref.html7
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-05.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/range.html241
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/required_attribute.html34
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/reset.html113
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/resources/image-submit-click.html15
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/resources/loadresolver.html6
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/resources/range-restore-events.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/resources/range-restore-events.html.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/resources/show-picker-child-iframe.html26
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/resources/text-restore-events.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/resources/text-restore-events.html.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/search_input.html35
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/selection-pointer.html44
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/selection-weekmonth.html57
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/selection.html140
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-cross-origin-iframe.html79
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-disabled-readonly.html43
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-user-gesture.html31
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/telephone.html84
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/text.html104
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/time-2.html42
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/time-datalist-crash.html15
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/time-focus-dynamic-value-change.html31
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/time.html357
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/type-change-file-to-text-crash.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/type-change-state-weekmonth.html169
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/type-change-state.html166
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/url.html59
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/valueMode-weekmonth.html37
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/valueMode.html304
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/week.html41
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-label-element/clicking-interactive-content.html111
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-label-element/clicking-noninteractive-labelable-content.html112
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-label-element/clicking-noninteractive-unlabelable-content.html130
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-label-element/forward-focus-to-associated-element.html99
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-label-element/iframe-label-attributes.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-label-element/label-attributes.sub.html339
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-label-element/label-inside-anchor.html28
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-label-element/labelable-elements.html174
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-label-element/proxy-click-to-associated-element.html58
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-label-element/proxy-modifier-click-to-associated-element.tentative.html67
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-legend-element/HTMLLegendElement.html37
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-legend-element/legend-form.html33
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-meter-element/meter-min-rendering-ref.html3
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-meter-element/meter-min-rendering.html9
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-meter-element/meter.html250
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-optgroup-element/optgroup-disabled-manual.html35
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/dynamic-content-change-rendering-ref.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/dynamic-content-change-rendering.html34
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/option-disabled-manual.html28
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/option-element-constructor.html135
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/option-form.html32
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/option-index.html54
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/option-label-value.js82
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/option-label.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/option-selected.html61
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-backslash.html15
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-label.html23
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-recurse.html92
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-setter.html24
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-spaces.html75
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-option-element/option-value.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-output-element/mutations.window.js36
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-output-element/output-setcustomvalidity.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-output-element/output-validity.html23
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-output-element/output.html46
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-progress-element/progress-2.html34
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-progress-element/progress.html74
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-progress-element/progress.window.js18
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/common-HTMLOptionsCollection-add.html89
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/common-HTMLOptionsCollection-namedItem.html54
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/common-HTMLOptionsCollection.html117
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/inserted-or-removed.html87
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/reset-algorithm-rendering-ref.html21
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/reset-algorithm-rendering.html39
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-add-option-crash.html13
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-add.html36
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-ask-for-reset.html97
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-multiple.html48
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-named-getter.html46
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-remove.html64
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-selectedOptions.html143
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-setcustomvalidity.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-validity.html124
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-value.html56
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-willvalidate-readonly-attribute.html24
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/selected-index.html143
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-ask-for-reset.html119
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-events.tentative.html207
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-form-attribute.tentative.html231
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-form-state-restore.tentative.html39
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-form-submission.tentative.html57
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-keyboard.tentative.html123
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-labels.tentative.html29
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-many-options.tentative.html140
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-nested.tentative.html94
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-displayed-ref.tentative.html48
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-displayed.tentative.html67
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-not-displayed-ref.tentative.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-not-displayed.tentative.html28
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-focusable.tentative.html49
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-parts-structure.tentative.html530
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position-with-zoom.tentative.html135
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position.tentative.html115
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative.html107
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-required-attribute.tentative.html61
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-validity.tentative.html88
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-value-selectedOption.tentative.html204
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/support/back.html1
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/cloning-steps.html34
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder-cr.html1
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder-crlf.html21
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder-ref.html15
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder.html22
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/placeholder-white-space-notref.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/placeholder-white-space.tentative.html15
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/support/placeholder.css6
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-maxlength.html51
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-minlength.html51
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-newline-bidi-ref.html19
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-newline-bidi.html23
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-placeholder-lineheight.html37
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-placeholder-manual.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-select-event-manual.html31
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-select-manual.html13
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-setcustomvalidity.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-textLength.html19
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-type.html16
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-validity-clone.html27
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/value-defaultValue-textContent-xhtml.xhtml26
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/value-defaultValue-textContent.html181
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-enumerated-ascii-case-insensitive-child.html5
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-enumerated-ascii-case-insensitive.html47
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-reflect-1-ref.html5
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-reflect-1a.html9
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-reflect-1b.html9
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrapping-transformation.window.js58
454 files changed, 26191 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/semantics/forms/META.yml b/testing/web-platform/tests/html/semantics/forms/META.yml
new file mode 100644
index 0000000000..ce84e4ae4c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/META.yml
@@ -0,0 +1,2 @@
+suggested_reviewers:
+ - tkent-google
diff --git a/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-ltr-iframe.html b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-ltr-iframe.html
new file mode 100644
index 0000000000..b5ed7e3d9a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-ltr-iframe.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Submitting element directionality: the dirname attribute support</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
diff --git a/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-ltr.html b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-ltr.html
new file mode 100644
index 0000000000..9d1c9eb77e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-ltr.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Submitting element directionality: the dirname attribute</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#submitting-element-directionality:-the-dirname-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form action="dirname-ltr-iframe.html" method=get target="iframe">
+ <p><label>Comment: <input type=text name="comment" dirname="comment.dir" required></label></p>
+ <p><button type=submit>Post Comment</button></p>
+</form>
+<iframe name="iframe"></iframe>
+<script>
+ function getParameterByName(name) {
+ name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
+ var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
+ results = regex.exec(document.querySelector("iframe").contentWindow.location.search);
+ return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
+ }
+
+ var t = async_test("submit element directionality");
+ document.querySelector("input").value = "foobar";
+ document.querySelector("button").click();
+
+ var iframe = document.querySelector("iframe");
+ iframe.onload = t.step_func(function() {
+ // The initial about:blank load event can be fired before the form navigation occurs.
+ // See https://github.com/whatwg/html/issues/490 for more information.
+ if(iframe.contentWindow.location.href == "about:blank") { return; }
+
+ assert_equals(getParameterByName("comment.dir"), "ltr");
+
+ t.done();
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-auto.html b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-auto.html
new file mode 100644
index 0000000000..6368a26faf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-auto.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Submitting element directionality: the dirname attribute</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#submitting-element-directionality:-the-dirname-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form action="dirname-ltr-iframe.html" method=get target="iframe">
+ <p><label>Comment: <input type=text name="comment" dir="auto" dirname="comment.dir" required/></label></p>
+ <p><button type=submit>Post Comment</button></p>
+</form>
+<iframe name="iframe"></iframe>
+<script>
+ function getParameterByName(name) {
+ name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
+ var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
+ results = regex.exec(document.querySelector("iframe").contentWindow.location.search);
+ return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
+ }
+
+ var t = async_test("submit element directionality");
+ var rtlValue = "مرحبا";
+ document.querySelector("input").value = rtlValue;
+ document.querySelector("button").click();
+
+ var iframe = document.querySelector("iframe");
+ iframe.onload = t.step_func(function() {
+ // The initial about:blank load event can be fired before the form navigation occurs.
+ // See https://github.com/whatwg/html/issues/490 for more information.
+ if(iframe.contentWindow.location.href == "about:blank") { return; }
+
+ assert_equals(getParameterByName("comment.dir"), "rtl");
+
+ t.done();
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-inherited.html b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-inherited.html
new file mode 100644
index 0000000000..1e6967d914
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-inherited.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Submitting element directionality: the dirname attribute</title>
+<link rel="author" title="Kolupaev Dmitry" href="mailto:dmitry.klpv@gmail.com">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#submitting-element-directionality:-the-dirname-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div dir="rtl">
+ <form action="dirname-ltr-iframe.html" method=get target="iframe">
+ <p><label>Comment: <input type=text name="comment" dirname="comment.dir" required/></label></p>
+ <p><button type=submit>Post Comment</button></p>
+ </form>
+</div>
+<iframe name="iframe"></iframe>
+<script>
+ function getParameterByName(name) {
+ name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
+ var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
+ results = regex.exec(document.querySelector("iframe").contentWindow.location.search);
+ return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
+ }
+
+ var t = async_test("submit element directionality");
+ document.querySelector("input").value = "foobar";
+ document.querySelector("button").click();
+
+ var iframe = document.querySelector("iframe");
+ iframe.onload = t.step_func(function() {
+ // The initial about:blank load event can be fired before the form navigation occurs.
+ // See https://github.com/whatwg/html/issues/490 for more information.
+ if(iframe.contentWindow.location.href == "about:blank") { return; }
+
+ assert_equals(getParameterByName("comment.dir"), "rtl");
+
+ t.done();
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-manual.html b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-manual.html
new file mode 100644
index 0000000000..cb00f6972d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-manual.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Submitting element directionality: the dirname attribute (rtl)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#submitting-element-directionality:-the-dirname-attribute">
+<form action="dirname-rtl-manual.html" method=get>
+ <p><label>Comment: <input type=text name="comment" dirname="comment.dir" required></label></p>
+ <p><button type=submit>Post Comment</button></p>
+</form>
+<p>Switch to a right-to-left writing direction, enter a text in the input and submit the form.</p>
+<p>Test passes if the word "PASS" appears below</p>
+<script>
+ function getParameterByName(name) {
+ name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
+ var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
+ results = regex.exec(location.search);
+ return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
+ }
+
+ var commentDir = getParameterByName("comment.dir");
+ if (commentDir) {
+ var p = document.createElement("p");
+ p.textContent = (commentDir == "rtl") ? "PASS" : "FAIL";
+ document.body.appendChild(p);
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/disabled-elements-01.html b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/disabled-elements-01.html
new file mode 100644
index 0000000000..14443e4099
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/disabled-elements-01.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<title>HTMLFormElement: the disabled attribute</title>
+<link rel="author" title="Eric Casler" href="mailto:ericorange@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls:-the-disabled-attribute">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="log"></div>
+<div id="root"></div>
+<script>
+// Elements tested for in this file
+var types = ["button", "input", "select", "textarea"];
+// no tests for: optgroup, option, fieldset
+
+var root = document.getElementById("root");
+for (var element_type = 0; element_type < types.length; element_type++) {
+ test(function() {
+ root.innerHTML = "<"+types[element_type]+" + id='elem'></"+types[element_type]+">";
+
+ var elem = document.getElementById("elem");
+ assert_false(elem.disabled);
+ },
+ "Test ["+types[element_type]+"]: default behaviour is NOT disabled");
+
+ test(function() {
+ var formats = ["disabled",
+ "disabled=disabled", "disabled='disabled'",
+ "disabled='true'", "disabled=true",
+ "disabled='false'", "disabled=false"];
+
+ for (var f = 0; f < formats.length; f++) {
+ root.innerHTML = "<"+types[element_type]+" id='elem' " + formats[f] + "></"+types[element_type]+">";
+
+ var elem = document.getElementById("elem");
+ assert_true(elem.disabled);
+ }
+ },
+ "Test ["+types[element_type]+"]: verify disabled acts as boolean attribute");
+
+ test(function() {
+ root.innerHTML = "<"+types[element_type]+" id='elem'></"+types[element_type]+"><input id='other' value='no event dispatched'></input>";
+ var elem = document.getElementById("elem"),
+ other = document.getElementById("other");
+
+ assert_equals(other.value, "no event dispatched");
+
+ elem.disabled = true;
+ assert_true(elem.disabled);
+
+ elem.onclick = function () {
+ // change value of other element, to avoid *.value returning "" for disabled elements
+ document.getElementById("other").value = "event dispatched";
+ };
+
+ // Check if dispatched event executes
+ var evObj = document.createEvent('Events');
+ evObj.initEvent("click", true, false);
+ elem.dispatchEvent(evObj);
+ assert_equals(other.value, "event dispatched");
+ },
+ "Test ["+types[element_type]+"]: synthetic click events should be dispatched");
+
+ test(function() {
+ root.innerHTML = "<"+types[element_type]+" id='elem'></"+types[element_type]+"><input id='other' value='no event dispatched'></input>";
+ var elem = document.getElementById("elem"),
+ other = document.getElementById("other");
+
+ assert_equals(other.value, "no event dispatched");
+
+ elem.disabled = true;
+ assert_true(elem.disabled);
+
+ elem.onclick = function () {
+ // change value of other element, to avoid *.value returning "" for disabled elements
+ document.getElementById("other").value = "event dispatched";
+ };
+
+ // Check that click() on a disabled element doesn't dispatch a click event.
+ elem.click();
+ assert_equals(other.value, "no event dispatched");
+ },
+ "Test ["+types[element_type]+"]: click() should not dispatch a click event");
+}
+root.innerHTML = "";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/formAction_document_address.html b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/formAction_document_address.html
new file mode 100644
index 0000000000..4510807c2c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/formAction_document_address.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>HTML Test: formAction_document_address</title>
+ <link rel="author" title="Intel" href="http://www.intel.com/">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-fs-formaction">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-document's-address">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-button-element">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-input-element">
+ <meta name="assert" content="On getting the formAction IDL attribute, when the content attribute is missing or its value is the empty string, the document's address must be returned instead.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+
+ <div id="missing" style="display:none">
+ <button type="submit">Submit</button>
+ <input type="submit">
+ </div>
+
+ <div id="empty_string" style="display:none">
+ <button type="submit" formaction="">Submit</button>
+ <input type="submit" formaction="">
+ </div>
+
+ <div id="no_assigned_value" style="display:none">
+ <button type="submit" formaction>Submit</button>
+ <input type="submit" formaction>
+ </div>
+
+ <script>
+ // formaction content attribute is missing
+ test(function() {
+ var formAction = document.querySelector('#missing button').formAction;
+ var address = document.location.href;
+ assert_equals(formAction, address);
+ }, "Check if button.formAction is the document's address when formaction content attribute is missing");
+
+ test(function() {
+ var formAction = document.querySelector('#missing input').formAction;
+ var address = document.location.href;
+ assert_equals(formAction, address);
+ }, "Check if input.formAction is the document's address when formaction content attribute is missing");
+
+ // formaction content attribute value is empty string
+ test(function() {
+ var formAction = document.querySelector('#empty_string button').formAction;
+ var address = document.location.href;
+ assert_equals(formAction, address);
+ }, "Check if button.formAction is the document's address when formaction content attribute value is empty string");
+
+ test(function() {
+ var formAction = document.querySelector('#empty_string input').formAction;
+ var address = document.location.href;
+ assert_equals(formAction, address);
+ }, "Check if input.formAction is the document's address when formaction content attribute value is empty string");
+
+ // formaction content attribute value is not assigned, just for comparison with empty string above
+ test(function() {
+ var formAction = document.querySelector('#no_assigned_value button').formAction;
+ var address = document.location.href;
+ assert_equals(formAction, address);
+ }, "Check if button.formAction is the document's address when formaction content attribute value is not assigned");
+
+ test(function() {
+ var formAction = document.querySelector('#no_assigned_value input').formAction;
+ var address = document.location.href;
+ assert_equals(formAction, address);
+ }, "Check if input.formAction is the document's address when formaction content attribute value is not assigned");
+
+ var newUrl = location.href.replace(/\/[^\/]*$/,'\/dummy.html');
+ history.pushState('','','dummy.html');
+
+ test(function() {
+ assert_equals(document.location.href, newUrl);
+
+ var formAction = document.querySelector('#missing button').formAction;
+ var address = document.location.href;
+ assert_equals(formAction, address);
+ }, "Check if button.formAction is the document's new address when formaction content attribute is missing and pushState has been used");
+
+ test(function() {
+ assert_equals(document.location.href, newUrl);
+
+ var formAction = document.querySelector('#missing input').formAction;
+ var address = document.location.href;
+ assert_equals(formAction, address);
+ }, "Check if input.formAction is the document's new address when formaction content attribute is missing and pushState has been used");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/formaction.html b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/formaction.html
new file mode 100644
index 0000000000..82798eaa84
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/formaction.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html><head>
+ <title>formaction on button element</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type">
+ <meta content="formaction on button element" name="description">
+ <link href="https://html.spec.whatwg.org/multipage/#dom-fs-formaction" rel="help">
+</head>
+ <body>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <div id="log"></div>
+ <button formaction="http://www.example.com/" style="display: none" type="submit">Submit</button>
+ <input formaction="http://www.example.com/" style="display: none" type="submit" value="submit">
+ <input style="display: none" type="submit" value="submit">
+ <input formaction="" style="display: none" type="submit" value="submit">
+
+ <script type="text/javascript">
+ function relativeToAbsolute(relativeURL) {
+ var a = document.createElement('a');
+ a.href = relativeURL;
+ return a.href;
+ }
+ test(function() {assert_equals(document.getElementsByTagName("button")[0].formAction, "http://www.example.com/")}, "formAction on button support");
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].formAction, "http://www.example.com/")}, "formAction on input support");
+
+ var testElem = document.getElementsByTagName("input")[0];
+ testElem.formAction = "http://www.example.com/page2.html";
+
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].formAction, "http://www.example.com/page2.html")}, "formaction absolute URL value on input reflects correct value after being updated by the DOM");
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].getAttribute("formaction"), "http://www.example.com/page2.html")}, "formAction absolute URL value is correct using getAttribute");
+
+ var testElem = document.getElementsByTagName("input")[0];
+ testElem.formAction = "../page3.html";
+
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].formAction, relativeToAbsolute('../page3.html'))}, "formAction relative URL value on input reflects correct value after being updated by the DOM");
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].getAttribute("formaction"), "../page3.html")}, "formAction relative URL value is correct using getAttribute");
+
+ test(function() {assert_equals(document.getElementsByTagName("input")[1].formAction, document.URL)}, "On getting, when formaction is missing, the document's address must be returned");
+ test(function() {assert_equals(document.getElementsByTagName("input")[2].formAction, document.URL)}, "On getting, when formaction value is the empty string, the document's address must be returned");
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/forms/beforeinput.tentative.html b/testing/web-platform/tests/html/semantics/forms/beforeinput.tentative.html
new file mode 100644
index 0000000000..7aa51a2523
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/beforeinput.tentative.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test the onbeforeinput attribute</title>
+<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-vendor.js"></script>
+
+<div id="container"></div>
+<script>
+ const container = document.getElementById("container");
+ const events = new Map();
+
+ function handleEvent(event) {
+ if (!events.has(event.target)) {
+ events.set(event.target, []);
+ }
+ events.get(event.target).push(event);
+ }
+
+ let onInputFired = null;
+
+ const onBeforeInput = handleEvent;
+ const onInput = (event) => {
+ handleEvent(event);
+ onInputFired()
+ }
+
+ let elems = [];
+ for (let type of ["text", "search", "tel", "url", "email", "password", "number"]) {
+ elems.push(`<input type=${type} onbeforeinput="onBeforeInput(event)" oninput="onInput(event)">
+<input type=${type}>
+`);
+ }
+ elems.push(`<textarea onbeforeinput="onBeforeInput(event)" oninput="onInput(event)"></textarea>
+<textarea></textarea>`)
+ container.innerHTML = elems.join("");
+
+for (const element of container.children) {
+ promise_test(async t => {
+ if (!element.hasAttribute("onbeforeinput")) {
+ element.onbeforeinput = e => onBeforeInput(e);
+ element.oninput = e => onInput(e);
+ };
+
+ let afterOnInput = new Promise(resolve => {onInputFired = resolve});
+ await test_driver.send_keys(element, "1"); // has to be a number so <input type=number> works
+ // Ensure we're in the post-update state
+ await afterOnInput;
+
+ assert_true(events.has(element), "Got events for element");
+ let elementEvents = events.get(element);
+
+ assert_array_equals(elementEvents.map(event => event.type), ["beforeinput", "input"], "Got expected events");
+ }, `${element.outerHTML}`);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-checkValidity.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-checkValidity.html
new file mode 100644
index 0000000000..2e790c75d8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-checkValidity.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.checkValidity()</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["text", "search", "tel", "password"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {maxLength: "4", value: "abcdef"}, expected: true, name: "[target] not suffering from being too long", dirty: true},
+ {conditions: {pattern: "[A-Z]", value: "abc"}, expected: false, name: "[target] suffering from a pattern mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["url"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {maxLength: "20", value: "http://www.example.com"}, expected: true, name: "[target] suffering from being too long", dirty: true},
+ {conditions: {pattern: "http://www.example.com", value: "http://www.example.net"}, expected: false, name: "[target] suffering from a pattern mismatch"},
+ {conditions: {value: "abc"}, expected: false, name: "[target] suffering from a type mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["email"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {maxLength: "10", value: "test@example.com"}, expected: true, name: "[target] not suffering from being too long", dirty: true},
+ {conditions: {pattern: "test@example.com", value: "test@example.net"}, expected: false, name: "[target] suffering from a pattern mismatch"},
+ {conditions: {value: "abc"}, expected: false, name: "[target] suffering from a type mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["datetime-local"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {max: "2000-01-01T12:00:00", value: "2001-01-01T12:00:00"}, expected: false, name: "[target] suffering from an overflow"},
+ {conditions: {min: "2001-01-01T12:00:00", value: "2000-01-01T12:00:00"}, expected: false, name: "[target] suffering from an underflow"},
+ {conditions: {step: 2 * 60 * 1000, value: "2001-01-01T12:03:00"}, expected: false, name: "[target] suffering from a step mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["date"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {max: "2000-01-01", value: "2001-01-01"}, expected: false, name: "[target] suffering from an overflow"},
+ {conditions: {min: "2001-01-01", value: "2000-01-01"}, expected: false, name: "[target] suffering from an underflow"},
+ {conditions: {step: 2 * 1 * 86400000, value: "2001-01-03"}, expected: false, name: "[target] suffering from a step mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["month"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {max: "2000-01", value: "2001-01"}, expected: false, name: "[target] suffering from an overflow"},
+ {conditions: {min: "2001-01", value: "2000-01"}, expected: false, name: "[target] suffering from an underflow"},
+ {conditions: {step: 3 * 1 * 1, value: "2001-03"}, expected: false, name: "[target] suffering from a step mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["week"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {max: "2000-W01", value: "2001-W01"}, expected: false, name: "[target] suffering from an overflow"},
+ {conditions: {min: "2001-W01", value: "2000-W01"}, expected: false, name: "[target] suffering from an underflow"},
+ {conditions: {step: 2 * 1 * 604800000, value: "2001-W03"}, expected: false, name: "[target] suffering from a step mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["time"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {max: "12:00:00", value: "13:00:00"}, expected: false, name: "[target] suffering from an overflow"},
+ {conditions: {min: "12:00:00", value: "11:00:00"}, expected: false, name: "[target] suffering from an underflow"},
+ {conditions: {step: 2 * 60 * 1000, value: "12:03:00"}, expected: false, name: "[target] suffering from a step mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["number"],
+ testData: [
+ {conditions: {max: "5", value: "6"}, expected: false, name: "[target] suffering from an overflow"},
+ {conditions: {min: "5", value: "4"}, expected: false, name: "[target] suffering from an underflow"},
+ {conditions: {step: 2 * 1 * 1, value: "3"}, expected: false, name: "[target] suffering from a step mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["checkbox", "radio"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {required: true, checked: false, name: "test1"}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["file"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {required: true, files: null}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "select",
+ types: [],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "textarea",
+ types: [],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ }
+ ];
+
+ validator.run_test(testElements, "checkValidity");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-reportValidity.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-reportValidity.html
new file mode 100644
index 0000000000..c68e21c9d5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-reportValidity.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.reportValidity()</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["text", "search", "tel", "password"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {maxLength: "4", value: "abcdef"}, expected: true, name: "[target] not suffering from being too long", dirty: true},
+ {conditions: {pattern: "[A-Z]", value: "abc"}, expected: false, name: "[target] suffering from a pattern mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["url"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {maxLength: "20", value: "http://www.example.com"}, expected: true, name: "[target] not suffering from being too long", dirty: true},
+ {conditions: {pattern: "http://www.example.com", value: "http://www.example.net"}, expected: false, name: "[target] suffering from a pattern mismatch"},
+ {conditions: {value: "abc"}, expected: false, name: "[target] suffering from a type mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["email"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {maxLength: "10", value: "test@example.com"}, expected: true, name: "[target] not suffering from being too long", dirty: true},
+ {conditions: {pattern: "test@example.com", value: "test@example.net"}, expected: false, name: "[target] suffering from a pattern mismatch"},
+ {conditions: {value: "abc"}, expected: false, name: "[target] suffering from a type mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["datetime-local"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {max: "2000-01-01T12:00:00", value: "2001-01-01T12:00:00"}, expected: false, name: "[target] suffering from an overflow"},
+ {conditions: {min: "2001-01-01T12:00:00", value: "2000-01-01T12:00:00"}, expected: false, name: "[target] suffering from an underflow"},
+ {conditions: {step: 2 * 60 * 1000, value: "2001-01-01T12:03:00"}, expected: false, name: "[target] suffering from a step mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["date"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {max: "2000-01-01", value: "2001-01-01"}, expected: false, name: "[target] suffering from an overflow"},
+ {conditions: {min: "2001-01-01", value: "2000-01-01"}, expected: false, name: "[target] suffering from an underflow"},
+ {conditions: {step: 2 * 1 * 86400000, value: "2001-01-03"}, expected: false, name: "[target] suffering from a step mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["month"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {max: "2000-01", value: "2001-01"}, expected: false, name: "[target] suffering from an overflow"},
+ {conditions: {min: "2001-01", value: "2000-01"}, expected: false, name: "[target] suffering from an underflow"},
+ {conditions: {step: 3 * 1 * 1, value: "2001-03"}, expected: false, name: "[target] suffering from a step mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["week"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {max: "2000-W01", value: "2001-W01"}, expected: false, name: "[target] suffering from an overflow"},
+ {conditions: {min: "2001-W01", value: "2000-W01"}, expected: false, name: "[target] suffering from an underflow"},
+ {conditions: {step: 2 * 1 * 604800000, value: "2001-W03"}, expected: false, name: "[target] suffering from a step mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["time"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {max: "12:00:00", value: "13:00:00"}, expected: false, name: "[target] suffering from an overflow"},
+ {conditions: {min: "12:00:00", value: "11:00:00"}, expected: false, name: "[target] suffering from an underflow"},
+ {conditions: {step: 2 * 60 * 1000, value: "12:03:00"}, expected: false, name: "[target] suffering from a step mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["number"],
+ testData: [
+ {conditions: {max: "5", value: "6"}, expected: false, name: "[target] suffering from an overflow"},
+ {conditions: {min: "5", value: "4"}, expected: false, name: "[target] suffering from an underflow"},
+ {conditions: {step: 2 * 1 * 1, value: "3"}, expected: false, name: "[target] suffering from a step mismatch"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["checkbox", "radio"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {required: true, checked: false, name: "test1"}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["file"],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {required: true, files: null}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "select",
+ types: [],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ },
+ {
+ tag: "textarea",
+ types: [],
+ testData: [
+ {conditions: {}, expected: true, name: "[target] no constraint"},
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
+ ]
+ }
+ ];
+
+ validator.run_test(testElements, "reportValidity");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validate.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validate.html
new file mode 100644
index 0000000000..e32fd90330
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validate.html
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Constraint validation</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#constraint-validation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form id="fm1" style="display:none">
+ <fieldset id="test0">
+ <input type="text" required value="" id="test1">
+ </fieldset>
+ <input type="email" value="abc" id="test2">
+ <button id="test3">TEST</button>
+ <select id="test4"></select>
+ <textarea id="test5"></textarea>
+ <output id="test6"></output>
+</form>
+<form id="fm2" style="display:none">
+ <fieldset>
+ <input type="text" required value="abc">
+ </fieldset>
+ <input type="email" value="test@example.com">
+ <button>TEST</button>
+ <select></select>
+ <textarea></textarea>
+ <output></output>
+</form>
+<form id="fm3" style="display:none">
+ <fieldset id="fs">
+ <legend><input type="text" id="inp1"></legend>
+ <input type="text" required value="" id="inp2">
+ </fieldset>
+</form>
+
+<script>
+ var cancelable = true,
+ times1 = 0,
+ times2 = 0,
+ invalidList1 = [],
+ invalidList2 = [],
+ test1,
+ test2,
+ fm1,
+ fm2,
+ fm3;
+
+ setup(function () {
+ fm1 = document.getElementById("fm1");
+ fm2 = document.getElementById("fm2");
+ fm3 = document.getElementById("fm3");
+ test1 = document.getElementById("test1");
+ test2 = document.getElementById("test2");
+ for (var index = 0; index < fm1.elements.length; index++) {
+ var ele = fm1.elements.item(index);
+ ele.addEventListener("invalid", function (e) {
+ times1++;
+ invalidList1.push(e.target);
+ if (!e.cancelable)
+ cancelable = e.cancelable;
+ }, false);
+ }
+
+ for (var index = 0; index < fm2.elements.length; index++) {
+ var ele = fm2.elements.item(index);
+ ele.addEventListener("invalid", function (e) {
+ times2++;
+ invalidList2.push(ele);
+ }, false);
+ }
+ });
+
+ test(function(){
+ assert_false(fm1.checkValidity(), "The checkValidity method should be false.");
+ }, "If there is any invalid submittable element whose form owner is the form, the form.checkValidity must be false");
+
+ test(function(){
+ assert_true("reportValidity" in fm1, "The reportValidity method is not supported");
+ assert_false(fm1.reportValidity(), "The reportValidity method should be false.");
+ }, "If there is any invalid submittable element whose form owner is the form, the form.reportValidity must be false");
+
+ test(function(){
+ assert_true(fm2.checkValidity(), "The checkValidity method should be true.");
+ }, "If all of the submittable elements whose form owner is the form are valid, the form.checkValidity must be true");
+
+ test(function(){
+ assert_true("reportValidity" in fm2, "The reportValidity method is not supported.");
+ assert_true(fm2.reportValidity(), "The reportValidity method should be true.");
+ }, "If all of the submittable elements whose form owner is the form are valid, the form.reportValidity must be true");
+
+ test(function(){
+ assert_false(fm3.checkValidity(), "The checkValidity method should be false.");
+ document.getElementById("fs").disabled = true;
+ assert_true(fm3.checkValidity(), "The checkValidity method should be true.");
+
+ document.getElementById("inp1").value = "aaa";
+ document.getElementById("inp1").type = "url";
+ assert_false(fm3.checkValidity(), "The checkValidity method should be false.");
+ }, "Check the checkValidity method of the form element when it has a fieldset child");
+
+ test(function(){
+ // Restore the condition to default which was modified during the previous test.
+ document.getElementById("fs").disabled = false;
+ document.getElementById("inp1").value = "";
+ document.getElementById("inp1").type = "text";
+
+ assert_true("reportValidity" in fm3, "The reportValidity method is not supported.");
+ assert_false(fm3.reportValidity(), "The reportValidity method should be false.");
+ document.getElementById("fs").disabled = true;
+ assert_true(fm3.reportValidity(), "The reportValidity method should be true.");
+
+ document.getElementById("inp1").value = "aaa";
+ document.getElementById("inp1").type = "url";
+ assert_false(fm3.reportValidity(), "The reportValidity method should be false.");
+ }, "Check the reportValidity method of the form element when it has a fieldset child");
+
+ test(function () {
+ assert_equals(times1, 4, "The invalid event will be fired if the checkValidity or reportValidity method are called.");
+ assert_array_equals(invalidList1, [test1, test2, test1, test2], "The invalid event must be fired at the invalid control");
+ assert_true(cancelable, "The invalid event is cancelable.");
+ }, "The invalid event must be fired at the invalid controls");
+
+ test(function () {
+ assert_equals(times2, 0, "The invalid event should not be fired, times should be 0.");
+ assert_array_equals(invalidList2, [], "The invalid event should not be fired, invalid list should be empty");
+ }, "The invalid event must not be fired at the invalid controls");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-badInput.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-badInput.html
new file mode 100644
index 0000000000..8f6153b923
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-badInput.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.badInput</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/association-of-controls-and-forms.html#suffering-from-bad-input">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/the-button-element.html#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+var testElements = [
+ {
+ tag: "input",
+ types: ["email"],
+ testData: [
+ {conditions: {multiple: false, value: ""}, expected: false, name: "[target] The multiple attribute is false and the value attribute is empty"},
+ {conditions: {multiple: false, value: "test1@example.com"}, expected: false, name: "[target] The multiple attribute is false and the value attribute is a valid e-mail address"},
+ {conditions: {multiple: true, value: "test1@example.com,test2@eample.com"}, expected: false, name: "[target] The multiple attribute is true and the value contains valid e-mail addresses"},
+ {conditions: {multiple: true, value: "test,1@example.com"}, expected: false, name: "[target] The multiple attribute is true and the value attribute contains a ','"}
+ //TODO, the value contains characters that cannot be converted to punycode.
+ //Can not find a character that cannot be converted to punycode.
+ ]
+ },
+ {
+ tag: "input",
+ types: ["datetime-local"],
+ testData: [
+ {conditions: {value: ""}, expected: false, name: "[target] The value attribute is empty"},
+ {conditions: {value: "2000-01-01T12:00:00"}, expected: false, name: "[target] The value attribute is a valid date and time string"},
+ {conditions: {value: "abc"}, expected: false, name: "[target] The value attribute cannot convert to a valid normalized forced-UTC global date and time string"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["color"],
+ testData: [
+ {conditions: {value: ""}, expected: false, name: "[target] The value attribute is empty"},
+ {conditions: {value: "#000000"}, expected: false, name: "[target] The value attribute is a valid sample color"},
+ {conditions: {value: "#FFFFFF"}, expected: false, name: "[target] The value attribute is not a valid lowercase sample color"},
+ {conditions: {value: "abc"}, expected: false, name: "[target] The value attribute cannot convert to a valid sample color"}
+ ]
+ },
+ ];
+ validator.run_test (testElements, "badInput");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-customError.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-customError.html
new file mode 100644
index 0000000000..2ae6240ace
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-customError.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.customError</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-validitystate-customerror">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+var testElements = [
+ {
+ tag: "input",
+ types: [],
+ testData: [
+ {conditions: {message: "My custom error"}, expected: true, name: "[target] The validity.customError must be true if the custom validity error message is not empty"},
+ {conditions: {message: ""}, expected: false, name: "[target] The validity.customError must be false if the custom validity error message is empty"}
+ ]
+ },
+ {
+ tag: "button",
+ types: [],
+ testData: [
+ {conditions: {message: "My custom error"}, expected: true, name: "[target] The validity.customError must be true if the custom validity error message is not empty"},
+ {conditions: {message: ""}, expected: false, name: "[target] The validity.customError must be false if the custom validity error message is empty"}
+ ]
+ },
+ {
+ tag: "select",
+ types: [],
+ testData: [
+ {conditions: {message: "My custom error"}, expected: true, name: "[target] The validity.customError must be true if the custom validity error message is not empty"},
+ {conditions: {message: ""}, expected: false, name: "[target] The validity.customError must be false if the custom validity error message is empty"}
+ ]
+ },
+ {
+ tag: "textarea",
+ types: [],
+ testData: [
+ {conditions: {message: "My custom error"}, expected: true, name: "[target] The validity.customError must be true if the custom validity error message is not empty"},
+ {conditions: {message: ""}, expected: false, name: "[target] The validity.customError must be false if the custom validity error message is empty"}
+ ]
+ }
+ ]
+
+ validator.run_test(testElements, "customError");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-patternMismatch.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-patternMismatch.html
new file mode 100644
index 0000000000..c21271c8fc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-patternMismatch.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.patternMismatch</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-validitystate-patternmismatch">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["text", "search", "tel", "url", "email", "password"],
+ testData: [
+ {conditions: {pattern: null, value: "abc"}, expected: false, name: "[target] The pattern attribute is not set"},
+ {conditions: {pattern: "[A-Z]+", value: ""}, expected: false, name: "[target] The value attibute is empty string"},
+ {conditions: {pattern: "[A-Z]{1}", value: "A"}, expected: false, name: "[target] The value attribute matches the pattern attribute"},
+ {conditions: {pattern: "[A-Z]+", value: "\u0041\u0042\u0043"}, expected: false, name: "[target] The value(ABC) in unicode attribute matches the pattern attribute"},
+ {conditions: {pattern: "[a-z]{3,}", value: "ABCD"}, expected: true, name: "[target] The value attribute mismatches the pattern attribute"},
+ {conditions: {pattern: "[A-Z]+", value: "ABC123"}, expected: true, name: "[target] The value attribute mismatches the pattern attribute even when a subset matches"},
+ {conditions: {pattern: "(abc", value: "de"}, expected: false, name: "[target] Invalid regular expression gets ignored"},
+ {conditions: {pattern: "a)(b", value: "de"}, expected: false, name: "[target] The pattern attribute tries to escape a group"},
+ {conditions: {pattern: "a\\u{10FFFF}", value: "a\u{10FFFF}"}, expected: false, name: "[target] The pattern attribute uses Unicode features"},
+ {conditions: {pattern: "\\u1234\\cx[5-[]{2}", value: "\u1234\x18[6"}, expected: false, name: "[target] The value attribute matches JavaScript-specific regular expression"},
+ {conditions: {pattern: "\\u1234\\cx[5-[]{2}", value: "\u1234\x18[4"}, expected: true, name: "[target] The value attribute mismatches JavaScript-specific regular expression"},
+ ]
+ },
+ {
+ tag: "input",
+ types: ["email"],
+ testData: [
+ {conditions: {multiple: true, pattern: null, value: "abc,abc"}, expected: false, name: "[target] The pattern attribute is not set, if multiple is present"},
+ {conditions: {multiple: true, pattern: "[A-Z]+", value: ""}, expected: false, name: "[target] The value attibute is empty string, if multiple is present"},
+ {conditions: {multiple: true, pattern: "[A-Z]{1}", value: "A,A"}, expected: false, name: "[target] The value attribute matches the pattern attribute, if multiple is present"},
+ {conditions: {multiple: true, pattern: "[A-Z]+", value: "\u0041\u0042\u0043,\u0041\u0042\u0043"}, expected: false, name: "[target] The value(ABC) in unicode attribute matches the pattern attribute, if multiple is present"},
+ {conditions: {multiple: true, pattern: "[a-z]{3,}", value: "abcd,ABCD"}, expected: true, name: "[target] The value attribute mismatches the pattern attribute, if multiple is present"},
+ {conditions: {multiple: true, pattern: "[A-Z]+", value: "ABCD,ABC123"}, expected: true, name: "[target] The value attribute mismatches the pattern attribute even when a subset matches, if multiple is present"},
+ {conditions: {multiple: true, pattern: "(abc", value: "de,de"}, expected: false, name: "[target] Invalid regular expression gets ignored, if multiple is present"},
+ {conditions: {multiple: true, pattern: "a)(b", value: "de,de"}, expected: false, name: "[target] The pattern attribute tries to escape a group, if multiple is present"},
+ {conditions: {multiple: true, pattern: "a\\u{10FFFF}", value: "a\u{10FFFF},a\u{10FFFF}"}, expected: false, name: "[target] The pattern attribute uses Unicode features, if multiple is present"},
+ {conditions: {multiple: true, pattern: "\\u1234\\cx[5-[]{2}", value: "\u1234\x18[6,\u1234\x18[Z"}, expected: false, name: "[target] The value attribute matches JavaScript-specific regular expression, if multiple is present"},
+ {conditions: {multiple: true, pattern: "\\u1234\\cx[5-[]{2}", value: "\u1234\x18[4,\u1234\x18[6"}, expected: true, name: "[target] The value attribute mismatches JavaScript-specific regular expression, if multiple is present"},
+ {conditions: {multiple: true, pattern: "a,", value: "a,"}, expected: true, name: "[target] Commas should be stripped from regex input, if multiple is present"},
+ ]
+ }
+ ];
+
+ validator.run_test (testElements, "patternMismatch");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeOverflow-weekmonth.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeOverflow-weekmonth.html
new file mode 100644
index 0000000000..5a1478829f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeOverflow-weekmonth.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.rangeOverflow</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeoverflow">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["month"],
+ testData: [
+ {conditions: {max: "", value: "2000-01"}, expected: false, name: "[target] The max attribute is not set"},
+ {conditions: {max: "2000-01", value: ""}, expected: false, name: "[target] Value is empty string"},
+ {conditions: {max: "2000/01", value: "2001-02"}, expected: false, name: "[target] The max attribute is an invalid month string"},
+ {conditions: {max: "2000-01", value: "2000-1"}, expected: false, name: "[target] The value attribute is an invalid month string"},
+ {conditions: {max: "987-01", value: "988-01"}, expected: false, name: "[target] The value is an invalid month string(year is three digits)"},
+ {conditions: {max: "2000-01", value: "2000-13"}, expected: false, name: "[target] The value is an invalid month string(month is greater than 12)"},
+ {conditions: {max: "2000-12", value: "2000-01"}, expected: false, name: "[target] The max attribute is greater than value attribute"},
+ {conditions: {max: "2000-01", value: "2000-12"}, expected: true, name: "[target] The value attribute is greater than max attribute"},
+ {conditions: {max: "9999-01", value: "10000-01"}, expected: true, name: "[target] The value attribute is greater than max attribute(Year is 10000 should be valid)"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["week"],
+ testData: [
+ {conditions: {max: "", value: "2000-W01"}, expected: false, name: "[target] The max attribute is not set"},
+ {conditions: {max: "2000-W01", value: ""}, expected: false, name: "[target] Value is empty string"},
+ {conditions: {max: "2000/W01", value: "2001-W02"}, expected: false, name: "[target] The max attribute is an invalid week string"},
+ {conditions: {max: "2000-W01", value: "2000-W2"}, expected: false, name: "[target] The value attribute is an invalid week string"},
+ {conditions: {max: "2000-W01", value: "2000-w02"}, expected: false, name: "[target] The value attribute is an invalid week string(w is in lowercase)"},
+ {conditions: {max: "987-W01", value: "988-W01"}, expected: false, name: "[target] The value is an invalid week string(year is three digits)"},
+ {conditions: {max: "2000-W01", value: "2000-W57"}, expected: false, name: "[target] The value is an invalid week string(week is too greater)"},
+ {conditions: {max: "2000-W12", value: "2000-W01"}, expected: false, name: "[target] The max attribute is greater than value attribute"},
+ {conditions: {max: "2000-W01", value: "2000-W12"}, expected: true, name: "[target] The value attribute is greater than max attribute"},
+ {conditions: {max: "9999-W01", value: "10000-W01"}, expected: true, name: "[target] The value attribute is greater than max attribute(Year is 10000 should be valid)"}
+ ]
+ }
+ ];
+
+ validator.run_test(testElements, "rangeOverflow");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeOverflow.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeOverflow.html
new file mode 100644
index 0000000000..98847e70ff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeOverflow.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.rangeOverflow</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeoverflow">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["datetime-local"],
+ testData: [
+ {conditions: {max: "", value: "2000-01-01T12:00:00"}, expected: false, name: "[target] The max attribute is not set"},
+ {conditions: {max: "2000-01-01T12:00:00", value: ""}, expected: false, name: "[target] Value is empty string"},
+ {conditions: {max: "2000-01-01 12:00:00", value: "2001-01-01T12:00:00"}, expected: false, name: "[target] The max attribute is an invalid local date time string"},
+ {conditions: {max: "2000-01-01T12:00:00", value: "2000-01-01T11:00:00"}, expected: false, name: "[target] The max attribute is greater than the value attribute"},
+ {conditions: {max: "2000-01-01T23:59:59", value: "2001-01-01T24:00:00"}, expected: false, name: "[target] The value is an invalid local date time string(hour is greater than 23)"},
+ {conditions: {max: "1970-01-01T12:00", value: "80-01-01T12:00"}, expected: false, name: "[target] The value if an invalid local date time string(year is two digits)"},
+ {conditions: {max: "2000-01-01T12:00:00", value: "2001-01-01T13:00:00"}, expected: true, name: "[target] The value is greater than max"},
+ {conditions: {max: "2000-01-01T12:00:00.1", value: "2000-01-01T12:00:00.2"}, expected: true, name: "[target] The value is greater than max(with millisecond in 1 digit)"},
+ {conditions: {max: "2000-01-01T12:00:00.01", value: "2000-01-01T12:00:00.02"}, expected: true, name: "[target] The value is greater than max(with millisecond in 2 digits)"},
+ {conditions: {max: "2000-01-01T12:00:00.001", value: "2000-01-01T12:00:00.002"}, expected: true, name: "[target] The value is greater than max(with millisecond in 3 digits)"},
+ {conditions: {max: "2000-01-01T12:00:00", value: "10000-01-01T12:00:00"}, expected: true, name: "[target] The value is greater than max(Year is 10000 should be valid)"},
+ ]
+ },
+ {
+ tag: "input",
+ types: ["date"],
+ testData: [
+ {conditions: {max: "", value: "2000-01-01"}, expected: false, name: "[target] The max attribute is not set"},
+ {conditions: {max: "2000-01-01", value: ""}, expected: false, name: "[target] Value is empty string"},
+ {conditions: {max: "2000/01/01", value: "2002-01-01"}, expected: false, name: "[target] The max attribute is an invalid date"},
+ {conditions: {max: "2000-01-01", value: "2000-2-2"}, expected: false, name: "[target] The value attribute is an invalid date"},
+ {conditions: {max: "987-01-01", value: "988-01-01"}, expected: false, name: "[target] The value is an invalid date(year is three digits)"},
+ {conditions: {max: "2000-01-01", value: "2000-13-01"}, expected: false, name: "[target] The value is an invalid date(month is greater than 12)"},
+ {conditions: {max: "2000-01-01", value: "2000-02-30"}, expected: false, name: "[target] The value is an invalid date(date is greater than 29 for Feb)"},
+ {conditions: {max: "2000-12-01", value: "2000-01-01"}, expected: false, name: "[target] The max attribute is greater than value attribute"},
+ {conditions: {max: "2000-12-01", value: "2001-01-01"}, expected: true, name: "[target] The value attribute is greater than max attribute"},
+ {conditions: {max: "9999-01-01", value: "10000-01-01"}, expected: true, name: "[target] The value attribute is greater than max attribute(Year is 10000 should be valid)"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["time"],
+ testData: [
+ {conditions: {max: "", value: "12:00:00"}, expected: false, name: "[target] The max attribute is not set"},
+ {conditions: {max: "12:00:00", value: ""}, expected: false, name: "[target] Value is empty string"},
+ {conditions: {max: "12.00.00", value: "12:00:01"}, expected: false, name: "[target] The max attribute is an invalid time string"},
+ {conditions: {max: "12:00:00", value: "12.00.01"}, expected: false, name: "[target] The value attribute is an invalid time string"},
+ {conditions: {max: "23:59:59", value: "24:00:00"}, expected: false, name: "[target] The value attribute is an invalid time string(hour is greater than 23)"},
+ {conditions: {max: "23:59:59", value: "23:60:00"}, expected: false, name: "[target] The value attribute is an invalid time string(minute is greater than 59)"},
+ {conditions: {max: "23:59:59", value: "23:59:60"}, expected: false, name: "[target] The value attribute is an invalid time string(second is greater than 59)"},
+ {conditions: {max: "13:00:00", value: "12:00:00"}, expected: false, name: "[target] The max attribute is greater than value attribute"},
+ {conditions: {max: "12:00:00", value: "13"}, expected: false, name: "[target] The time missing second and minute parts is invalid"},
+ {conditions: {max: "12:00:00", value: "12:00:02"}, expected: true, name: "[target] The value attribute is greater than max attribute"},
+ {conditions: {max: "12:00:00.1", value: "12:00:00.2"}, expected: true, name: "[target] The value is greater than max(with millisecond in 1 digit)"},
+ {conditions: {max: "12:00:00.01", value: "12:00:00.02"}, expected: true, name: "[target] The value is greater than max(with millisecond in 2 digit)"},
+ {conditions: {max: "12:00:00.001", value: "12:00:00.002"}, expected: true, name: "[target] The value is greater than max(with millisecond in 3 digit)"},
+ {conditions: {max: "12:00:00", value: "12:01"}, expected: true, name: "[target] The time missing second part is valid"},
+ {conditions: {max: "12:00:00", min: "14:00:00", value: "12:00:00"}, expected: false, name: "[target] The time is max for reversed range"},
+ {conditions: {max: "12:00:00", min: "14:00:00", value: "13:00:00"}, expected: true, name: "[target] The time is outside the accepted range for reversed range"},
+ {conditions: {max: "12:00:00", min: "14:00:00", value: "14:00:00"}, expected: false, name: "[target] The time is min for reversed range"},
+ {conditions: {max: "12:00:00", min: "14:00:00", value: "15:00:00"}, expected: false, name: "[target] The time is inside the accepted range for reversed range"},
+ ]
+ },
+ {
+ tag: "input",
+ types: ["number"],
+ testData: [
+ {conditions: {max: "", value: "10"}, expected: false, name: "[target] The max attribute is not set"},
+ {conditions: {max: "5", value: ""}, expected: false, name: "[target] Value is empty string"},
+ {conditions: {max: "5", value: "4"}, expected: false, name: "[target] The max is greater than value(integer)"},
+ {conditions: {max: "-5.5", value: "-5.6"}, expected: false, name: "[target] The max is greater than value(floating number)"},
+ {conditions: {max: "-0", value: "0"}, expected: false, name: "[target] The max equals to value"},
+ {conditions: {max: "5", value: "1abc"}, expected: false, name: "[target] The value is not a number"},
+ {conditions: {max: "5", value: "6"}, expected: true, name: "[target] The value is greater than max(integer)"},
+ {conditions: {max: "-5.5", value: "-5.4"}, expected: true, name: "[target] The value is greater than max(floating number)"},
+ {conditions: {max: "-1", value: "-.8"}, expected: true, name: "[target] The value is greater than max(special floating number)"},
+ {conditions: {max: "-5e-1", value: "5e+2"}, expected: true, name: "[target] The value is greater than max(scientific notation)"},
+ ]
+ }
+ ];
+
+ validator.run_test(testElements, "rangeOverflow");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow-weekmonth.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow-weekmonth.html
new file mode 100644
index 0000000000..9ddf2565c8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow-weekmonth.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.rangeUnderflow</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeunderflow">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["month"],
+ testData: [
+ {conditions: {min: "", value: "2000-01"}, expected: false, name: "[target] The min attribute is not set"},
+ {conditions: {min: "2000-01", value: ""}, expected: false, name: "[target] Value is empty string"},
+ {conditions: {min: "2001/01", value: "2000-02"}, expected: false, name: "[target] The min attribute is an invalid month string"},
+ {conditions: {min: "2000-02", value: "2000-1"}, expected: false, name: "[target] The value attribute is an invalid month string"},
+ {conditions: {min: "988-01", value: "987-01"}, expected: false, name: "[target] The value is an invalid month string(year is three digits)"},
+ {conditions: {min: "2001-01", value: "2000-13"}, expected: false, name: "[target] The value is an invalid month string(month is less than 12)"},
+ {conditions: {min: "2000-01", value: "2000-12"}, expected: false, name: "[target] The min attribute is less than value attribute"},
+ {conditions: {min: "2001-01", value: "2000-12"}, expected: true, name: "[target] The value attribute is less than min attribute"},
+ {conditions: {min: "10000-01", value: "2000-01"}, expected: true, name: "[target] The value attribute is less than min attribute(Year is 10000 should be valid)"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["week"],
+ testData: [
+ {conditions: {min: "", value: "2000-W01"}, expected: false, name: "[target] The min attribute is not set"},
+ {conditions: {min: "2000-W01", value: ""}, expected: false, name: "[target] Value is empty string"},
+ {conditions: {min: "2001/W02", value: "2000-W01"}, expected: false, name: "[target] The min attribute is an invalid week string"},
+ {conditions: {min: "2001-W02", value: "2000-W1"}, expected: false, name: "[target] The value attribute is an invalid week string"},
+ {conditions: {min: "2001-W02", value: "2000-w01"}, expected: false, name: "[target] The value attribute is an invalid week string(w is in lowercase)"},
+ {conditions: {min: "988-W01", value: "987-W01"}, expected: false, name: "[target] The value is an invalid week string(year is three digits)"},
+ {conditions: {min: "2001-W01", value: "2000-W57"}, expected: false, name: "[target] The value is an invalid week string(week is too greater)"},
+ {conditions: {min: "2000-W01", value: "2000-W12"}, expected: false, name: "[target] The min attribute is less than value attribute"},
+ {conditions: {min: "2000-W12", value: "2000-W01"}, expected: true, name: "[target] The value attribute is less than min attribute"},
+ {conditions: {min: "10000-W01", value: "2000-W01"}, expected: true, name: "[target] The value attribute is less than min attribute(Year is 10000 should be valid)"}
+ ]
+ }
+ ];
+
+ validator.run_test(testElements, "rangeUnderflow");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow.html
new file mode 100644
index 0000000000..c97af1b671
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.rangeUnderflow</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeunderflow">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["datetime-local"],
+ testData: [
+ {conditions: {min: "", value: "2000-01-01T12:00:00"}, expected: false, name: "[target] The min attribute is not set"},
+ {conditions: {min: "2000-01-01T12:00:00", value: ""}, expected: false, name: "[target] Value is empty string"},
+ {conditions: {min: "2001-01-01 12:00:00", value: "2000-01-01T12:00:00"}, expected: false, name: "[target] The min attribute is an invalid local date time string"},
+ {conditions: {min: "2000-01-01T11:00:00", value: "2000-01-01T12:00:00"}, expected: false, name: "[target] The min attribute is less than the value attribute"},
+ {conditions: {min: "2001-01-01T23:59:59", value: "2000-01-01T24:00:00"}, expected: false, name: "[target] The value is an invalid local date time string(hour is greater than 23)"},
+ {conditions: {min: "1980-01-01T12:00", value: "79-01-01T12:00"}, expected: false, name: "[target] The value is an invalid local date time string(year is two digits)"},
+ {conditions: {min: "2000-01-01T13:00:00", value: "2000-01-01T12:00:00"}, expected: true, name: "[target] The value is less than min"},
+ {conditions: {min: "2000-01-01T12:00:00.2", value: "2000-01-01T12:00:00.1"}, expected: true, name: "[target] The value is less than min(with millisecond in 1 digit)"},
+ {conditions: {min: "2000-01-01T12:00:00.02", value: "2000-01-01T12:00:00.01"}, expected: true, name: "[target] The value is less than min(with millisecond in 2 digits)"},
+ {conditions: {min: "2000-01-01T12:00:00.002", value: "2000-01-01T12:00:00.001"}, expected: true, name: "[target] The value is less than min(with millisecond in 3 digits)"},
+ {conditions: {min: "10000-01-01T12:00:00", value: "2000-01-01T12:00:00"}, expected: true, name: "[target] The value is less than min(Year is 10000 should be valid)"},
+ {conditions: {max: "8593-01-01T02:09", value: "8592-01-01T02:09"}, expected: false, name: "[target] The value is greater than max"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["date"],
+ testData: [
+ {conditions: {min: "", value: "2000-01-01"}, expected: false, name: "[target] The min attribute is not set"},
+ {conditions: {min: "2000-01-01", value: ""}, expected: false, name: "[target] Value is empty string"},
+ {conditions: {min: "2001/01/01", value: "2000-01-01"}, expected: false, name: "[target] The min attribute is an invalid date"},
+ {conditions: {min: "2000-02-02", value: "2000-1-1"}, expected: false, name: "[target] The value attribute is an invalid date"},
+ {conditions: {min: "988-01-01", value: "987-01-01"}, expected: false, name: "[target] The value is an invalid date(year is three digits)"},
+ {conditions: {min: "2001-01-01", value: "2000-13-01"}, expected: false, name: "[target] The value is an invalid date(month is less than 12)"},
+ {conditions: {min: "2001-01-01", value: "2000-02-30"}, expected: false, name: "[target] The value is an invalid date(date is less than 29 for Feb)"},
+ {conditions: {min: "2000-01-01", value: "2000-12-01"}, expected: false, name: "[target] The min attribute is less than value attribute"},
+ {conditions: {min: "2000-12-01", value: "2000-01-01"}, expected: true, name: "[target] The value attribute is less than min attribute"},
+ {conditions: {min: "10000-01-01", value: "9999-01-01"}, expected: true, name: "[target] The value attribute is less than min attribute(Year is 10000 should be valid)"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["time"],
+ testData: [
+ {conditions: {min: "", value: "12:00:00"}, expected: false, name: "[target] The min attribute is not set"},
+ {conditions: {min: "12:00:00", value: ""}, expected: false, name: "[target] Value is empty string"},
+ {conditions: {min: "12.00.01", value: "12:00:00"}, expected: false, name: "[target] The min attribute is an invalid time string"},
+ {conditions: {min: "12:00:01", value: "12.00.00"}, expected: false, name: "[target] The value attribute is an invalid time string"},
+ {conditions: {min: "12:00:00", value: "13:00:00"}, expected: false, name: "[target] The min attribute is less than value attribute"},
+ {conditions: {min: "13:00:00", value: "12"}, expected: false, name: "[target] The time missing second and minute parts is invalid"},
+ {conditions: {min: "12:00:02", value: "12:00:00"}, expected: true, name: "[target] The value attribute is less than min attribute"},
+ {conditions: {min: "12:00:00.2", value: "12:00:00.1"}, expected: true, name: "[target] The value is less than min(with millisecond in 1 digit)"},
+ {conditions: {min: "12:00:00.02", value: "12:00:00.01"}, expected: true, name: "[target] The value is less than min(with millisecond in 2 digit)"},
+ {conditions: {min: "12:00:00.002", value: "12:00:00.001"}, expected: true, name: "[target] The value is less than min(with millisecond in 3 digit)"},
+ {conditions: {min: "12:00:00", value: "11:59"}, expected: true, name: "[target] The time missing second part is valid"},
+ {conditions: {min: "14:00:00", max: "12:00:00", value: "12:00:00"}, expected: false, name: "[target] The time is max for reversed range"},
+ {conditions: {min: "14:00:00", max: "12:00:00", value: "13:00:00"}, expected: true, name: "[target] The time is outside the accepted range for reversed range"},
+ {conditions: {min: "14:00:00", max: "12:00:00", value: "14:00:00"}, expected: false, name: "[target] The time is min for reversed range"},
+ {conditions: {min: "14:00:00", max: "12:00:00", value: "15:00:00"}, expected: false, name: "[target] The time is inside the accepted range for reversed range"},
+ ]
+ },
+ {
+ tag: "input",
+ types: ["number"],
+ testData: [
+ {conditions: {min: "", value: "10"}, expected: false, name: "[target] The min attribute is not set"},
+ {conditions: {min: "5", value: ""}, expected: false, name: "[target] Value is empty string"},
+ {conditions: {min: "4", value: "5"}, expected: false, name: "[target] The min is less than value(integer)"},
+ {conditions: {min: "-5.6", value: "-5.5"}, expected: false, name: "[target] The min is less than value(floating number)"},
+ {conditions: {min: "0", value: "-0"}, expected: false, name: "[target] The min equals to value"},
+ {conditions: {min: "5", value: "6abc"}, expected: false, name: "[target] The value is not a number"},
+ {conditions: {min: "6", value: "5"}, expected: true, name: "[target] The value is less than min(integer)"},
+ {conditions: {min: "-5.4", value: "-5.5"}, expected: true, name: "[target] The value is less than min(floating number)"},
+ {conditions: {min: "1", value: "-.8"}, expected: true, name: "[target] The value is less than min(special floating number)"},
+ {conditions: {min: "5e+2", value: "-5e-1"}, expected: true, name: "[target] The value is less than min(scientific notation)"}
+ ]
+ }
+ ];
+
+ validator.run_test(testElements, "rangeUnderflow");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-stepMismatch.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-stepMismatch.html
new file mode 100644
index 0000000000..4d4b4328f4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-stepMismatch.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.stepMismatch</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-validitystate-stepmismatch">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ //set step = 2 * default step * factor
+ var testElements = [
+ {
+ tag: "input",
+ types: ["date"],
+ testData: [
+ {conditions: {step: "", value: "2000-01-01"}, expected: false, name: "[target] The step attribute is not set"},
+ {conditions: {step: 2, value: ""}, expected: false, name: "[target] The value attibute is empty string"},
+ {conditions: {step: 2, value: "1970-01-03"}, expected: false, name: "[target] The value must match the step"},
+ {conditions: {step: 2, value: "1970-01-02"}, expected: true, name: "[target] The value must mismatch the step"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["month"],
+ testData: [
+ {conditions: {step: "", value: "2000-01"}, expected: false, name: "[target] The step attribute is not set"},
+ {conditions: {step: 2, value: ""}, expected: false, name: "[target] The value attibute is empty string"},
+ {conditions: {step: 2, value: "1970-03"}, expected: false, name: "[target] The value must match the step"},
+ {conditions: {step: 2, value: "1970-04"}, expected: true, name: "[target] The value must mismatch the step"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["week"],
+ testData: [
+ {conditions: {step: "", value: "1970-W01"}, expected: false, name: "[target] The step attribute is not set"},
+ {conditions: {step: 2, value: ""}, expected: false, name: "[target] The value attibute is empty string"},
+ {conditions: {step: 2, value: "1970-W03"}, expected: false, name: "[target] The value must match the step"},
+ {conditions: {step: 2, value: "1970-W04"}, expected: true, name: "[target] The value must mismatch the step"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["time"],
+ testData: [
+ {conditions: {step: "", value: "12:00:00"}, expected: false, name: "[target] The step attribute is not set"},
+ {conditions: {step: 2 * 60, value: ""}, expected: false, name: "[target] The value attibute is empty string"},
+ {conditions: {step: 2 * 60, value: "12:02:00"}, expected: false, name: "[target] The value must match the step"},
+ {conditions: {step: 2 * 60, value: "12:03:00"}, expected: true, name: "[target] The value must mismatch the step"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["datetime-local"],
+ testData: [
+ {conditions: {step: "", value: "2000-01-01T12:00:00"}, expected: false, name: "[target] The step attribute is not set"},
+ {conditions: {step: 2 * 60, value: ""}, expected: false, name: "[target] The value attibute is empty string"},
+ {conditions: {step: 2 * 60, value: "1970-01-01T12:02:00"}, expected: false, name: "[target] The value must match the step"},
+ {conditions: {step: 2 * 60, value: "1970-01-01T12:03:00"}, expected: true, name: "[target] The value must mismatch the step"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["number"],
+ testData: [
+ {conditions: {step: "", value: "1"}, expected: false, name: "[target] The step attribute is not set"},
+ {conditions: {step: "", value: "-.8"}, expected: true, name: "[target] The step attribute is not set and the value attribute is a floating number"},
+ {conditions: {step: 2 * 1 * 1, value: ""}, expected: false, name: "[target] The value attribute is empty string"},
+ {conditions: {step: 2 * 1 * 1, value: "2"}, expected: false, name: "[target] The value must match the step"},
+ {conditions: {step: 2 * 1 * 1, value: "3"}, expected: true, name: "[target] The value must mismatch the step"},
+ {conditions: {step: 0.003, value: "3.6"}, expected: false, name: "[target] No step mismatch when step is a floating number and value is its integral multiple"},
+ {conditions: {step: 1e-12, value: "-12345678.9"}, expected: false, name: "[target] No step mismatch when step is a floating number in exponent format and value is its integral multiple"},
+ {conditions: {step: 3e-15, value: "17"}, expected: true, name: "[target] Step mismatch when step is a very small floating number and value is not its integral multiple"},
+ ]
+ }
+ ];
+
+ validator.run_test(testElements, "stepMismatch");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-tooLong.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-tooLong.html
new file mode 100644
index 0000000000..aa787d471d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-tooLong.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.tooLong</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-validitystate-toolong">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["text", "search", "tel", "url", "email", "password"],
+ testData: [ // Non-dirty value
+ {conditions: {maxLength: "", value: "abc"}, expected: false, name: "[target] Non-dirty value - maxlength is not set"},
+ {conditions: {maxLength: "4", value: ""}, expected: false, name: "[target] Non-dirty value - value is empty string"},
+ {conditions: {maxLength: "4", value: "abc"}, expected: false, name: "[target] Non-dirty value - length of value is less than maxlength"},
+ {conditions: {maxLength: "4", value: "abcd"}, expected: false, name: "[target] Non-dirty value - length of value equals to maxlength"},
+ {conditions: {maxLength: "4", value: "abcde"}, expected: false, name: "[target] Non-dirty value - length of value is greater than maxlength"},
+ //Dirty value
+ {conditions: {maxLength: "4", value: "abc"}, expected: false, name: "[target] Dirty value - value is less than maxlength", dirty: true},
+ {conditions: {maxLength: "4", value: "\u0041\u0041\u0041"}, expected: false, name: "[target] Dirty value - length of value(AAA) in unicode is less than maxlength", dirty: true},
+ {conditions: {maxLength: "4", value: "abcd"}, expected: false, name: "[target] Dirty value - value equals to maxlength", dirty: true},
+ // False due to lack of required interactive editing by the user
+ {conditions: {maxLength: "4", value: "abcde"}, expected: false, name: "[target] Dirty value - length of value is greater than maxlength", dirty: true}
+ ]
+ },
+ {
+ tag: "textarea",
+ types: [],
+ testData: [
+ {conditions: {maxLength: "", value: "abc"}, expected: false, name: "[target] Non-dirty value - maxlength is not set"},
+ {conditions: {maxLength: "4", value: ""}, expected: false, name: "[target] Non-dirty value - value is empty string"},
+ {conditions: {maxLength: "4", value: "abc"}, expected: false, name: "[target] Non-dirty value - length of value is less than maxlength"},
+ {conditions: {maxLength: "4", value: "abcd"}, expected: false, name: "[target] Non-dirty value - length of value equals to maxlength"},
+ {conditions: {maxLength: "4", value: "abcde"}, expected: false, name: "[target] Non-dirty value - length of value is greater than maxlength"},
+ //Dirty value
+ {conditions: {maxLength: "4", value: "abc"}, expected: false, name: "[target] Dirty value - value is less than maxlength", dirty: true},
+ {conditions: {maxLength: "4", value: "\u000D\u000A"}, expected: false, name: "[target] Dirty value - length of value(LF, CRLF) in unicode is less than maxlength", dirty: true},
+ {conditions: {maxLength: "4", value: "abcd"}, expected: false, name: "[target] Dirty value - length of value equals to maxlength", dirty: true},
+ // False due to lack of required interactive editing by the user
+ {conditions: {maxLength: "4", value: "abcde"}, expected: false, name: "[target] Dirty value - length of value is greater than maxlength", dirty: true}
+ ]
+ }
+ ];
+
+ validator.run_test (testElements, "tooLong");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-tooShort.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-tooShort.html
new file mode 100644
index 0000000000..b6c0e43992
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-tooShort.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.tooShort</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#suffering-from-being-too-short">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["text", "search", "tel", "url", "email", "password"],
+ testData: [
+ // Non-dirty value
+ {conditions: {minLength: "", value: "abc"}, expected: false, name: "[target] Non-dirty value - minLength is not set"},
+ {conditions: {minLength: "4", value: ""}, expected: false, name: "[target] Non-dirty value - value is empty string"},
+ {conditions: {minLength: "4", value: "abcde"}, expected: false, name: "[target] Non-dirty value - length of value is greater than minLength"},
+ {conditions: {minLength: "4", value: "abcd"}, expected: false, name: "[target] Non-dirty value - length of value equals to minLength"},
+ {conditions: {minLength: "4", value: "abc"}, expected: false, name: "[target] Non-dirty value - length of value is less than minLength"},
+ //Dirty value
+ {conditions: {minLength: "4", value: "abcde"}, expected: false, name: "[target] Dirty value - value is greater than minLength", dirty: true},
+ {conditions: {minLength: "4", value: "\u0041\u0041\u0041\u0041\u0041"}, expected: false, name: "[target] Dirty value - length of value(AAAAA) in unicode is greater than minLength", dirty: true},
+ {conditions: {minLength: "4", value: "abcd"}, expected: false, name: "[target] Dirty value - value equals to minLength", dirty: true},
+ // False due to lack of required interactive editing by the user
+ {conditions: {minLength: "4", value: "abc"}, expected: false, name: "[target] Dirty value - length of value is less than minLength", dirty: true}
+ ]
+ },
+ {
+ tag: "textarea",
+ types: [],
+ testData: [
+ // Non-dirty value
+ {conditions: {minLength: "", value: "abc"}, expected: false, name: "[target] Non-dirty value - minLength is no set"},
+ {conditions: {minLength: "4", value: ""}, expected: false, name: "[target] Non-dirty value - value is empty string"},
+ {conditions: {minLength: "4", value: "abcde"}, expected: false, name: "[target] Non-dirty value - length of value is greater than minLength"},
+ {conditions: {minLength: "4", value: "abcd"}, expected: false, name: "[target] Non-dirty value - length of value equals to minLength"},
+ {conditions: {minLength: "4", value: "abc"}, expected: false, name: "[target] Non-dirty value - length of length of value is greater than minLength"},
+ //Dirty value
+ {conditions: {minLength: "4", value: "abcde"}, expected: false, name: "[target] Dirty value - value is less than minLength", dirty: true},
+ {conditions: {minLength: "4", value: "\u000D\u000A\u000D\u000A\u000D\u000A"}, expected: false, name: "[target] Dirty value - length of value(LF, CRLF) in unicode is less than minLength", dirty: true},
+ {conditions: {minLength: "4", value: "abcd"}, expected: false, name: "[target] Dirty value - length of value equals to minLength", dirty: true},
+ // False due to lack of required interactive editing by the user
+ {conditions: {minLength: "4", value: "abc"}, expected: false, name: "[target] Dirty value - length of value is greater than minLength", dirty: true}
+ ]
+ }
+ ];
+
+ validator.run_test (testElements, "tooShort");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-typeMismatch.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-typeMismatch.html
new file mode 100644
index 0000000000..40444277cd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-typeMismatch.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.typeMismatch</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-validitystate-typemismatch">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["email"],
+ testData: [
+ // multiple is false
+ {conditions: {multiple: false, value: ""}, expected: false, name: "[target] The value is empty"},
+ {conditions: {multiple: false, value: "test@example.com"}, expected: false, name: "[target] The value is a valid email address"},
+ {conditions: {multiple: false, value: "\u000A\u000D\u0020\u0009 test@example.com \u000A\u000D\u0020\u0009"}, expected: false, name: "[target] The value is a valid email address with some white spaces."},
+ {conditions: {multiple: false, value: "abc"}, expected: true, name: "[target] The value is not an email address"},
+ {conditions: {multiple: false, value: "test1@example.com,test2@example.com"}, expected: true, name: "[target] The value contains multiple email addresses"},
+ //multiple is true
+ {conditions: {multiple: true, value: "test1@example.com,test2@example.com"}, expected: false, name: "[target] The value is valid email addresses"},
+ {conditions: {multiple: true, value: "test1@example.com;test2@example.com"}, expected: true, name: "[target] The value contains invalid separator"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["url"],
+ testData: [
+ {conditions: {multiple: false, value: ""}, expected: false, name: "[target] The value is empty"},
+ {conditions: {multiple: false, value: "http://www.example.com"}, expected: false, name: "[target] The value is a valid url"},
+ {conditions: {multiple: false, value: "\u000A\u000D\u0020\u0009 http://www.example.com \u000A\u000D\u0020\u0009 "}, expected: false, name: "[target] The value is a valid url with some white spaces."},
+ {conditions: {multiple: false, value: "abc"}, expected: true, name: "[target] The value is not an url"}
+ ]
+ }
+ ];
+
+ validator.run_test(testElements, "typeMismatch");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valid-weekmonth.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valid-weekmonth.html
new file mode 100644
index 0000000000..3f47d391de
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valid-weekmonth.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.valid</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-validitystate-valid">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["month"],
+ testData: [
+ {conditions: {max: "2000-01", value: "2001-01"}, expected: false, name: "[target] validity.valid must be false if validity.rangeOverflow is true"},
+ {conditions: {min: "2001-01", value: "2000-01"}, expected: false, name: "[target] validity.valid must be false if validity.rangeUnderflow is true"},
+ // Step checks that "months since Jan 1970" is evenly divisible by `step`
+ {conditions: {step: 3, value: "2001-02"}, expected: false, name: "[target] validity.valid must be false if validity.stepMismatch is true"},
+ {conditions: {required: true, value: ""}, expected: false, expectedImmutable: true, name: "[target] validity.valid must be false if validity.valueMissing is true"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["week"],
+ testData: [
+ {conditions: {max: "2000-W01", value: "2001-W01"}, expected: false, name: "[target] validity.valid must be false if validity.rangeOverflow is true"},
+ {conditions: {min: "2001-W01", value: "2000-W01"}, expected: false, name: "[target] validity.valid must be false if validity.rangeUnderflow is true"},
+ {conditions: {step: 2 * 1 * 604800000, value: "2001-W03"}, expected: false, name: "[target] validity.valid must be false if validity.stepMismatch is true"},
+ {conditions: {required: true, value: ""}, expected: false, expectedImmutable: true, name: "[target] validity.valid must be false if validity.valueMissing is true"}
+ ]
+ }
+ ];
+
+ validator.run_test(testElements, "isValid");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valid.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valid.html
new file mode 100644
index 0000000000..009b7a8e5d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valid.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.valid</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-validitystate-valid">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["text", "search", "tel", "password"],
+ testData: [
+ {conditions: {pattern: "[A-Z]", value: "abc"}, expected: false, name: "[target] validity.valid must be false if validity.patternMismatch is true"},
+ {conditions: {required: true, value: ""}, expected: false, expectedImmutable: true, name: "[target] validity.valid must be false if validity.valueMissing is true"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["url"],
+ testData: [
+ {conditions: {pattern: "http://www.example.com", value: "http://www.example.net"}, expected: false, name: "[target] validity.valid must be false if validity.patternMismatch is true"},
+ {conditions: {value: "abc"}, expected: false, name: "[target] validity.valid must be false if validity.typeMismatch is true"},
+ {conditions: {required: true, value: ""}, expected: false, expectedImmutable: true, name: "[target] validity.valid must be false if validity.valueMissing is true"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["email"],
+ testData: [
+ {conditions: {pattern: "test@example.com", value: "test@example.net"}, expected: false, name: "[target] validity.valid must be false if validity.patternMismatch is true"},
+ {conditions: {value: "abc"}, expected: false, name: "[target] validity.valid must be false if validity.typeMismatch is true"},
+ {conditions: {required: true, value: ""}, expected: false, expectedImmutable: true, name: "[target] validity.valid must be false if validity.valueMissing is true"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["datetime-local"],
+ testData: [
+ {conditions: {max: "2000-01-01T12:00:00", value: "2001-01-01T12:00:00"}, expected: false, name: "[target] validity.valid must be false if validity.rangeOverflow is true"},
+ {conditions: {min: "2001-01-01T12:00:00", value: "2000-01-01T12:00:00"}, expected: false, name: "[target] validity.valid must be false if validity.rangeUnderflow is true"},
+ {conditions: {step: 2 * 60 * 1000, value: "2001-01-01T12:03:00"}, expected: false, name: "[target] validity.valid must be false if validity.stepMismatch is true"},
+ {conditions: {required: true, value: ""}, expected: false, expectedImmutable: true, name: "[target] validity.valid must be false if validity.valueMissing is true"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["date"],
+ testData: [
+ {conditions: {max: "2000-01-01", value: "2001-01-01"}, expected: false, name: "[target] validity.valid must be false if validity.rangeOverflow is true"},
+ {conditions: {min: "2001-01-01", value: "2000-01-01"}, expected: false, name: "[target] validity.valid must be false if validity.rangeUnderflow is true"},
+ {conditions: {step: 2 * 1 * 86400000, value: "2000-01-03"}, expected: false, name: "[target] validity.valid must be false if validity.stepMismatch is true"},
+ {conditions: {required: true, value: ""}, expected: false, expectedImmutable: true, name: "[target] validity.valid must be false if validity.valueMissing is true"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["time"],
+ testData: [
+ {conditions: {max: "12:00:00", value: "13:00:00"}, expected: false, name: "[target] validity.valid must be false if validity.rangeOverflow is true"},
+ {conditions: {min: "12:00:00", value: "11:00:00"}, expected: false, name: "[target] validity.valid must be false if validity.rangeUnderflow is true"},
+ {conditions: {step: 2 * 60 * 1000, value: "12:03:00"}, expected: false, name: "[target] validity.valid must be false if validity.stepMismatch is true"},
+ {conditions: {required: true, value: ""}, expected: false, expectedImmutable: true, name: "[target] validity.valid must be false if validity.valueMissing is true"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["number"],
+ testData: [
+ {conditions: {max: "5", value: "6"}, expected: false, name: "[target] validity.valid must be false if validity.rangeOverflow is true"},
+ {conditions: {min: "5", value: "4"}, expected: false, name: "[target] validity.valid must be false if validity.rangeUnderflow is true"},
+ {conditions: {step: 2 * 1 * 1, value: "3"}, expected: false, name: "[target] validity.valid must be false if validity.stepMismatch is true"},
+ {conditions: {required: true, value: ""}, expected: false, expectedImmutable: true, name: "[target] validity.valid must be false if validity.valueMissing is true"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["checkbox", "radio"],
+ testData: [
+ {conditions: {required: true, checked: false, name: "test1"}, expected: false, name: "[target] validity.valid must be false if validity.valueMissing is true"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["file"],
+ testData: [
+ {conditions: {required: true, files: null}, expected: false, name: "[target] validity.valid must be false if validity.valueMissing is true"}
+ ]
+ },
+ {
+ tag: "select",
+ types: [],
+ testData: [
+ {conditions: {required: true, value: ""}, expected: false, name: "[target] validity.valid must be false if validity.valueMissing is true"}
+ ]
+ },
+ {
+ tag: "textarea",
+ types: [],
+ testData: [
+ {conditions: {required: true, value: ""}, expected: false, expectedImmutable: true, name: "[target] validity.valid must be false if validity.valueMissing is true"}
+ ]
+ }
+ ];
+
+ validator.run_test(testElements, "isValid");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valueMissing-weekmonth.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valueMissing-weekmonth.html
new file mode 100644
index 0000000000..2078c2ec13
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valueMissing-weekmonth.html
@@ -0,0 +1,55 @@
+
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.valueMissing</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-validitystate-valuemissing">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<form>
+ <input id="messagetest" type="checkbox" required="" disabled="">
+</form>
+
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["month"],
+ testData: [
+ {conditions: {required: false, value: ""}, expected: false, name: "[target] The required attribute is not set"},
+ {conditions: {required: true, value: "2000-12"}, expected: false, name: "[target] Valid month string(2000-12)"},
+ {conditions: {required: true, value: "9999-01"}, expected: false, name: "[target] Valid month string(9999-01)"},
+ {conditions: {required: true, value: 1234567}, expected: true, expectedImmutable: false, name: "[target] The value attribute is a number(1234567)"},
+ {conditions: {required: true, value: new Date()}, expected: true, expectedImmutable: false, name: "[target] The value attribute is a Date object"},
+ {conditions: {required: true, value: "2000-99"}, expected: true, expectedImmutable: false, name: "[target] Invalid month string(2000-99)"},
+ {conditions: {required: true, value: "37-01"}, expected: true, expectedImmutable: false, name: "[target] Invalid month string(37-01)"},
+ {conditions: {required: true, value: "2000/01"}, expected: true, expectedImmutable: false, name: "[target] Invalid month string(2000/01)"},
+ {conditions: {required: true, value: ""}, expected: true, expectedImmutable: false, name: "[target] The value attribute is empty string"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["week"],
+ testData: [
+ {conditions: {required: false, value: ""}, expected: false, name: "[target] The required attribute is not set"},
+ {conditions: {required: true, value: "2000-W12"}, expected: false, name: "[target] Valid week string(2000-W12)"},
+ {conditions: {required: true, value: "9999-W01"}, expected: false, name: "[target] Valid week string(9999-W01)"},
+ {conditions: {required: true, value: 1234567}, expected: true, expectedImmutable: false, name: "[target] The value attribute is a number(1234567)"},
+ {conditions: {required: true, value: new Date()}, expected: true, expectedImmutable: false, name: "[target] The value attribute is a Date object"},
+ {conditions: {required: true, value: "2000-W99"}, expected: true, expectedImmutable: false, name: "[target] Invalid week string(2000-W99)"},
+ {conditions: {required: true, value: "2000-W00"}, expected: true, expectedImmutable: false, name: "[target] invalid week string(2000-W00)"},
+ {conditions: {required: true, value: "2000-w01"}, expected: true, expectedImmutable: false, name: "[target] invalid week string(2000-w01)"},
+ {conditions: {required: true, value: ""}, expected: true, expectedImmutable: false, name: "[target] The value attribute is empty string"}
+ ]
+ }
+ ];
+
+ validator.run_test(testElements, "valueMissing");
+
+ test(() => {
+ assert_equals(document.getElementById("messagetest").validationMessage, '');
+ }, 'validationMessage should return empty string when willValidate is false and valueMissing is true');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valueMissing.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valueMissing.html
new file mode 100644
index 0000000000..c586c6debe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-valueMissing.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.validity.valueMissing</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-validitystate-valuemissing">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<form>
+ <input id="messagetest" type="checkbox" required="" disabled="">
+</form>
+
+<script>
+ var testElements = [
+ {
+ tag: "input",
+ types: ["text", "search", "tel", "url", "email", "password"],
+ testData: [
+ {conditions: {required: false, value: ""}, expected: false, name: "[target] The required attribute is not set"},
+ {conditions: {required: true, value: "abc"}, expected: false, name: "[target] The value is not empty and required is true"},
+ {conditions: {required: true, value: ""}, expected: true, expectedImmutable: false, name: "[target] The value is empty and required is true"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["datetime-local"],
+ testData: [
+ {conditions: {required: false, value: ""}, expected: false, name: "[target] The required attribute is not set"},
+ {conditions: {required: true, value: "2000-12-10T12:00:00"}, expected: false, name: "[target] Valid local date and time string(2000-12-10T12:00:00)"},
+ {conditions: {required: true, value: "2000-12-10 12:00"}, expected: false, name: "[target] Valid local date and time string(2000-12-10 12:00)"},
+ {conditions: {required: true, value: "1979-10-14T12:00:00.001"}, expected: false, name: "[target] Valid local date and time string(1979-10-14T12:00:00.001)"},
+ {conditions: {required: true, value: 1234567}, expected: true, expectedImmutable: false, name: "[target] The value attribute is a number(1234567)"},
+ {conditions: {required: true, value: new Date()}, expected: true, expectedImmutable: false, name: "[target] The value attribute is a Date object"},
+ {conditions: {required: true, value: "1979-10-99 99:99"}, expected: true, expectedImmutable: false, name: "[target] Invalid local date and time string(1979-10-99 99:99)"},
+ {conditions: {required: true, value: "1979-10-14 12:00:00"}, expected: false, name: "[target] Valid local date and time string(1979-10-14 12:00:00)"},
+ {conditions: {required: true, value: "2001-12-21 12:00"}, expected: true, expectedImmutable: false, name: "[target] Invalid local date and time string(2001-12-21 12:00)-two white space"},
+ {conditions: {required: true, value: "abc"}, expected: true, expectedImmutable: false, name: "[target] the value attribute is a string(abc)"},
+ {conditions: {required: true, value: ""}, expected: true, expectedImmutable: false, name: "[target] The value attribute is empty string"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["date"],
+ testData: [
+ {conditions: {required: false, value: ""}, expected: false, name: "[target] The required attribute is not set"},
+ {conditions: {required: true, value: "2000-12-10"}, expected: false, name: "[target] Valid date string(2000-12-10)"},
+ {conditions: {required: true, value: "9999-01-01"}, expected: false, name: "[target] Valid date string(9999-01-01)"},
+ {conditions: {required: true, value: 1234567}, expected: true, expectedImmutable: false, name: "[target] The value attribute is a number(1234567)"},
+ {conditions: {required: true, value: new Date()}, expected: true, expectedImmutable: false, name: "[target] The value attribute is a Date object"},
+ {conditions: {required: true, value: "9999-99-99"}, expected: true, expectedImmutable: false, name: "[target] Invalid date string(9999-99-99)"},
+ {conditions: {required: true, value: "37/01/01"}, expected: true, expectedImmutable: false, name: "[target] Invalid date string(37-01-01)"},
+ {conditions: {required: true, value: "2000/01/01"}, expected: true, expectedImmutable: false, name: "[target] Invalid date string(2000/01/01)"},
+ {conditions: {required: true, value: ""}, expected: true, expectedImmutable: false, name: "[target] The value attribute is empty string"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["time"],
+ testData: [
+ {conditions: {required: false, value: ""}, expected: false, name: "[target] The required attribute is not set"},
+ {conditions: {required: true, value: "12:00:00"}, expected: false, name: "[target] Validtime string(12:00:00)"},
+ {conditions: {required: true, value: "12:00"}, expected: false, name: "[target] Validtime string(12:00)"},
+ {conditions: {required: true, value: "12:00:00.001"}, expected: false, name: "[target] Valid time string(12:00:60.001)"},
+ {conditions: {required: true, value: "12:00:00.01"}, expected: false, name: "[target] Valid time string(12:00:60.01)"},
+ {conditions: {required: true, value: "12:00:00.1"}, expected: false, name: "[target] Valid time string(12:00:60.1)"},
+ {conditions: {required: true, value: 1234567}, expected: true, expectedImmutable: false, name: "[target] The value attribute is a number(1234567)"},
+ {conditions: {required: true, value: new Date()}, expected: true, expectedImmutable: false, name: "[target] The value attribute is a time object"},
+ {conditions: {required: true, value: "25:00:00"}, expected: true, expectedImmutable: false, name: "[target] Invalid time string(25:00:00)"},
+ {conditions: {required: true, value: "12:60:00"}, expected: true, expectedImmutable: false, name: "[target] Invalid time string(12:60:00)"},
+ {conditions: {required: true, value: "12:00:60"}, expected: true, expectedImmutable: false, name: "[target] Invalid time string(12:00:60)"},
+ {conditions: {required: true, value: "12:00:00:001"}, expected: true, expectedImmutable: false, name: "[target] Invalid time string(12:00:00:001)"},
+ {conditions: {required: true, value: ""}, expected: true, expectedImmutable: false, name: "[target] The value attribute is empty string"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["number"],
+ testData: [
+ {conditions: {required: false, value: ""}, expected: false, name: "[target] The required attribute is not set"},
+ {conditions: {required: true, value: "123"}, expected: false, name: "[target] Value is an integer with a leading symbol '+'"},
+ {conditions: {required: true, value: "-123.45"}, expected: false, name: "[target] Value is a number with a '-' symbol"},
+ {conditions: {required: true, value: "123.01e-10"}, expected: false, name: "[target] Value is a number in scientific notation form(e is in lowercase)"},
+ {conditions: {required: true, value: "123.01E+10"}, expected: false, name: "[target] Value is a number in scientific notation form(E is in uppercase)"},
+ {conditions: {required: true, value: "-0"}, expected: false, name: "[target] Value is -0"},
+ {conditions: {required: true, value: " 123 "}, expected: true, expectedImmutable: false, name: "[target] Value is a number with some white spaces"},
+ {conditions: {required: true, value: Math.pow(2, 1024)}, expected: true, expectedImmutable: false, name: "[target] Value is Math.pow(2, 1024)"},
+ {conditions: {required: true, value: Math.pow(-2, 1024)}, expected: true, expectedImmutable: false, name: "[target] Value is Math.pow(-2, 1024)"},
+ {conditions: {required: true, value: "abc"}, expected: true, expectedImmutable: false, name: "[target] Value is a string that cannot be converted to a number"},
+ {conditions: {required: true, value: ""}, expected: true, expectedImmutable: false, name: "[target] The value attribute is empty string"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["checkbox"],
+ testData: [
+ {conditions: {required: false, checked: false, name: "test1"}, expected: false, name: "[target] The required attribute is not set"},
+ {conditions: {required: true, checked: true, name: "test2"}, expected: false, name: "[target] The checked attribute is true"},
+ {conditions: {required: true, checked: false, name: "test3"}, expected: true, name: "[target] The checked attribute is false"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["radio"],
+ testData: [
+ {conditions: {required: false, checked: false, name: "test4"}, expected: false, name: "[target] The required attribute is not set"},
+ {conditions: {required: true, checked: true, name: "test5"}, expected: false, name: "[target] The checked attribute is true"},
+ {conditions: {required: true, checked: false, name: "test6"}, expected: true, name: "[target] The checked attribute is false"},
+ {conditions: {required: true, checked: false, name: ""}, expected: false, name: "[target] The checked attribute is false and the name attribute is empty"}
+ ]
+ },
+ {
+ tag: "input",
+ types: ["file"],
+ testData: [
+ {conditions: {required: false, files: null}, expected: false, name: "[target] The required attribute is not set"},
+ {conditions: {required: true, files: null}, expected: true, name: "[target] The Files attribute is null"}
+ //ToDo: Add a case to test the files is not null
+ ]
+ },
+ {
+ tag: "select",
+ types: [],
+ testData: [
+ {conditions: {required: false, value: ""}, expected: false, name: "[target] The required attribute is not set"},
+ {conditions: {required: true, value: 1}, expected: false, name: "[target] Selected the option with value equals to 1"},
+ {conditions: {required: true, value: ""}, expected: true, name: "[target] Selected the option with value equals to empty"}
+ ]
+ },
+ {
+ tag: "textarea",
+ types: [],
+ testData: [
+ {conditions: {required: false, value: ""}, expected: false, name: "[target] The required attribute is not set"},
+ {conditions: {required: true, value: "abc"}, expected: false, name: "[target] The value is not empty"},
+ {conditions: {required: true, value: ""}, expected: true, expectedImmutable: false, name: "[target] The value is empty"}
+ ]
+ }
+ ];
+
+ validator.run_test(testElements, "valueMissing");
+
+ test(() => {
+ assert_equals(document.getElementById("messagetest").validationMessage, '');
+ }, 'validationMessage should return empty string when willValidate is false and valueMissing is true');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-willValidate-datalist.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-willValidate-datalist.html
new file mode 100644
index 0000000000..b6b14c6304
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-willValidate-datalist.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.willValidate</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<datalist></datalist>
+<script>
+ var dl = document.querySelector("datalist");
+ function runTest(element, name) {
+ test(function () {
+ assert_true(element.willValidate, "The willValidate attribute should be true initially.");
+
+ dl.appendChild(element);
+ assert_false(element.willValidate, "The willValidate attribute should be false if element has datalist parent.");
+
+ element.remove();
+ assert_true(element.willValidate, "The willValidate attribute should be true if element has no parent.");
+
+ let div = document.createElement("div");
+ div.appendChild(element);
+ let foo = document.createElementNS('some-random-namespace', 'foo');
+ foo.appendChild(div);
+ dl.appendChild(foo);
+ assert_false(element.willValidate, "The willValidate attribute should be false if element has datalist ancestor.");
+
+ foo.remove();
+ assert_true(element.willValidate, "The willValidate attribute should be true if element has no datalist ancestor.");
+ }, name);
+ }
+
+ var testElements = [
+ {
+ tag: "input",
+ types: ["text", "search", "tel", "url", "email", "password", "datetime-local", "date", "month", "week", "time", "color", "file", "submit"]
+ },
+ {
+ tag: "button",
+ types: ["submit"],
+ },
+ {
+ tag: "select",
+ types: [],
+ },
+ {
+ tag: "textarea",
+ types: [],
+ }
+ ].forEach(function(testData) {
+ if (testData.types.length > 0) {
+ testData.types.forEach(function(type) {
+ let ele = document.createElement(testData.tag);
+ try {
+ ele.type = type;
+ } catch (e) {
+ //Do nothing, avoid the runtime error breaking the test
+ }
+ runTest(ele, `Test ${testData.tag} element with ${type} type`);
+ });
+ } else {
+ runTest(document.createElement(testData.tag), `Test ${testData.tag} element`);
+ }
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-willValidate.html b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-willValidate.html
new file mode 100644
index 0000000000..c1abf76b65
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-willValidate.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The constraint validation API Test: element.willValidate</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/validator.js"></script>
+<div id="log"></div>
+<script>
+ var testElements = [
+ //input in hidden, button and reset status must be barred from the constraint validation
+ {
+ tag: "input",
+ types: ["hidden", "button", "reset"],
+ testData: [{conditions: {}, expected: false, name: "[target] Must be barred from the constraint validation"}]
+ },
+ //button in button and reset status must be barred from the constraint validation
+ {
+ tag: "button",
+ types: ["button", "reset"],
+ testData: [{conditions: {}, expected: false, name: "[target] Must be barred from the constraint validation"}]
+ },
+ // FIELDSET and OUTPUT elements are not "submittable elements" and therefore never validate.
+ {
+ tag: "fieldset",
+ types: [],
+ testData: [{conditions: {}, expected: false, name: "[target] The willValidate attribute must be false since FIELDSET is not a submittable element"}]
+ },
+ {
+ tag: "output",
+ types: [],
+ testData: [{conditions: {}, expected: false, name: "[target] The willValidate attribute must be false since OUTPUT is not a submittable element"}]
+ },
+ //OBJECT, KEYGEN, elements must be barred from the constraint validation
+ {
+ tag: "object",
+ types: [],
+ testData: [{conditions: {}, expected: false, name: "[target] Must be barred from the constraint validation"}]
+ },
+ //If an element is disabled, it is barred from constraint validation.
+ //The willValidate attribute must be true if an element is mutable
+ //If the readonly attribute is specified on an INPUT element, the element is barred from constraint validation.
+ {
+ tag: "input",
+ types: ["text", "search", "tel", "url", "email", "password", "datetime-local", "date", "month", "week", "time"],
+ testData: [
+ {conditions: {disabled: true}, expected: false, name: "[target] Must be barred from the constraint validation if it is disabled"},
+ {conditions: {disabled: false, readOnly: false}, expected: true, name: "[target] The willValidate attribute must be true if an element is mutable"},
+ {conditions: {readOnly: true}, expected: false, name: "[target] Must be barred from the constraint validation if it is readonly"},
+ {conditions: {disabled: false, readOnly: false}, expected: false, name: "[target] The willValidate attribute must be false if it has a datalist ancestor", ancestor: "datalist"},
+ ]
+ },
+ //In the following cases, the readonly attribute does not apply, however we should still bar the element from constraint validation.
+ {
+ tag: "input",
+ types: ["color", "file", "submit"],
+ testData: [
+ {conditions: {disabled: true}, expected: false, name: "[target] Must be barred from the constraint validation if it is disabled"},
+ {conditions: {disabled: false, readOnly: false}, expected: true, name: "[target] The willValidate attribute must be true if an element is mutable"},
+ {conditions: {readOnly: true}, expected: false, name: "[target] Must be barred from the constraint validation if it is readonly"},
+ {conditions: {disabled: false, readOnly: false}, expected: false, name: "[target] The willValidate attribute must be false if it has a datalist ancestor", ancestor: "datalist"},
+ ]
+ },
+ {
+ tag: "button",
+ types: ["submit"],
+ testData: [
+ {conditions: {disabled: true}, expected: false, name: "[target] Must be barred from the constraint validation"},
+ {conditions: {disabled: false}, expected: true, name: "[target] The willValidate attribute must be true if an element is mutable"},
+ {conditions: {disabled: false}, expected: false, name: "[target] The willValidate attribute must be false if it has a datalist ancestor", ancestor: "datalist"}
+ ]
+ },
+ {
+ tag: "select",
+ types: [],
+ testData: [
+ {conditions: {disabled: true}, expected: false, name: "[target] Must be barred from the constraint validation"},
+ {conditions: {disabled: false}, expected: true, name: "[target] The willValidate attribute must be true if an element is mutable"},
+ {conditions: {disabled: false}, expected: false, name: "[target] The willValidate attribute must be false if it has a datalist ancestor", ancestor: "datalist"}
+ ]
+ },
+ {
+ tag: "textarea",
+ types: [],
+ testData: [,
+ {conditions: {disabled: true}, expected: false, name: "[target] Must be barred from the constraint validation"},
+ {conditions: {disabled: false}, expected: true, name: "[target] The willValidate attribute must be true if an element is mutable"},
+ {conditions: {disabled: false}, expected: false, name: "[target] The willValidate attribute must be false if it has a datalist ancestor", ancestor: "datalist"}
+ ]
+ }
+ ];
+
+ validator.run_test(testElements, "willValidate");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/infinite_backtracking.html b/testing/web-platform/tests/html/semantics/forms/constraints/infinite_backtracking.html
new file mode 100644
index 0000000000..52f6e316b1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/infinite_backtracking.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The infinite pattern validation test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<input type=text id=badinput value="12345678901234567890123456789123456789z" pattern="(\d+)*$">
+<script>
+ test(function(){
+ var elements = document.querySelectorAll(":invalid");
+ assert_array_equals(elements, [document.getElementById('badinput')]);
+ }, "Infinite backtracking pattern terminates");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/input-number-validity-dynamic-value-no-change.html b/testing/web-platform/tests/html/semantics/forms/constraints/input-number-validity-dynamic-value-no-change.html
new file mode 100644
index 0000000000..2100fb7fb6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/input-number-validity-dynamic-value-no-change.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Number input step dynamic value attribute change</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1621273">
+<input type="number" value="9999" step="1">
+<script>
+test(function() {
+ let input = document.querySelector("input");
+ input.value = "113.90";
+ assert_true(input.matches(":invalid"), "Input should be invalid because step base is @value");
+ assert_false(input.validity.valid, "Input should be invalid because step base is @value");
+ input.setAttribute("value", "113.90");
+ assert_true(input.matches(":valid"), "Input should be valid because step base is @value");
+ assert_true(input.validity.valid, "Input should be valid because step base is @value");
+}, "number input number validation is updated correctly after value attribute change which doesn't change input value");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/input-pattern-dynamic-value.html b/testing/web-platform/tests/html/semantics/forms/constraints/input-pattern-dynamic-value.html
new file mode 100644
index 0000000000..58e566c738
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/input-pattern-dynamic-value.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Pattern dynamic value attribute change</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1636495">
+<input pattern="a" value="a">
+<script>
+test(function() {
+ let i = document.querySelector("input");
+ assert_false(i.matches(":invalid"));
+ i.pattern = "b";
+ assert_true(i.matches(":invalid"));
+ i.pattern = "(";
+ assert_false(i.matches(":invalid"));
+}, "input validation is updated after pattern attribute change");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/inputwillvalidate.html b/testing/web-platform/tests/html/semantics/forms/constraints/inputwillvalidate.html
new file mode 100644
index 0000000000..909fd889bb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/inputwillvalidate.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html><head>
+ <title>willValidate property on the input element</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type">
+ <meta content="willValidate property on the input element" name="description">
+ <link href="https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate" rel="help">
+ </head>
+ <body>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <div id="log"></div>
+
+ <form action="http://www.example.com/" style="display : none">
+ <input required="" type="text">
+ <input disabled="" type="text">
+ </form>
+
+ <script type="text/javascript">
+
+ test(function() {assert_true(document.getElementsByTagName("input")[0].willValidate)}, "willValidate property returns true when required attribute exists");
+ test(function() {assert_false(document.getElementsByTagName("input")[1].willValidate)}, "willValidate property returns false when disabled attribute exists");
+
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/number-input-lang-validationMessage-crash.html b/testing/web-platform/tests/html/semantics/forms/constraints/number-input-lang-validationMessage-crash.html
new file mode 100644
index 0000000000..7affb1d65b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/number-input-lang-validationMessage-crash.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>Should not assert or crash</title>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1649569">
+<script>
+window.onload = () => {
+ var a = document.getElementById("a")
+ var c = document.createElement("m")
+ a.valueAsNumber = 0.165
+ c.insertBefore(b, c.childNodes[0])
+ a.validationMessage
+}
+</script>
+<pre lang="nb">
+<form id="b">
+<input id="a" type="number">
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/radio-valueMissing.html b/testing/web-platform/tests/html/semantics/forms/constraints/radio-valueMissing.html
new file mode 100644
index 0000000000..a02b6b9645
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/radio-valueMissing.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>valueMissing property on the input[type=radio] element</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type">
+ <meta content="valueMissing property on the input[type=radio] element" name="description">
+ <link href="https://html.spec.whatwg.org/multipage/#dom-validitystate-valuemissing" rel="help">
+ <link href="https://html.spec.whatwg.org/multipage/#radio-button-state-(type%3Dradio)%3Asuffering-from-being-missing" rel="help">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id="log"></div>
+
+ <form style="display: none">
+ <input type="radio" name="group1" id="radio1">
+ <input type="radio" name="group1" id="radio2">
+
+ <input type="radio" name="group2" required id="radio3">
+ <input type="radio" name="group2" id="radio4">
+
+ <input type="radio" name="group3" required checked id="radio5">
+ <input type="radio" name="group3" id="radio6">
+
+ <input type="radio" name="group4" required id="radio7">
+ <input type="radio" name="group4" checked id="radio8">
+ <input type="radio" name="group4" id="radio9">
+
+ <input type="radio" name="group5" required disabled id="radio10">
+ <input type="radio" name="group5" id="radio11">
+
+ <input type="radio" name="group6" required checked disabled id="radio12">
+ <input type="radio" name="group6" id="radio13">
+ </form>
+
+ <script type="text/javascript">
+ var cases = [
+ {
+ name: "The required attribute is not set",
+ group: ["radio1", "radio2"],
+ expected: false
+ },
+ {
+ name: "One of the radios is required, but none checked",
+ group: ["radio3", "radio4"],
+ expected: true
+ },
+ {
+ name: "One of the radios is required and checked",
+ group: ["radio5", "radio6"],
+ expected: false
+ },
+ {
+ name: "One of the radios is required and another one is checked",
+ group: ["radio7", "radio8", "radio9"],
+ expected: false
+ },
+ {
+ name: "One of the radios is required and disabled, but none checked",
+ group: ["radio10", "radio11"],
+ expected: true
+ },
+ {
+ name: "One of the radios is required, checked and disabled",
+ group: ["radio12", "radio13"],
+ expected: false
+ }
+ ];
+
+ for (var info of cases) {
+ test(function () {
+ for (var id of info.group) {
+ var radio = document.getElementById(id);
+
+ assert_class_string(radio.validity, "ValidityState",
+ "HTMLInputElement.validity must be a ValidityState instance");
+
+ if (info.expected) {
+ assert_true(radio.validity.valueMissing,
+ "The " + id + ".validity.valueMissing should be true");
+ } else {
+ assert_false(radio.validity.valueMissing,
+ "The " + id + ".validity.valueMissing should be false");
+ }
+ }
+ }, info.name);
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/reportValidity-crash.html b/testing/web-platform/tests/html/semantics/forms/constraints/reportValidity-crash.html
new file mode 100644
index 0000000000..d6bab924ad
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/reportValidity-crash.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<script>
+Object.prototype.__defineGetter__('then', prom);
+var prom_count = 0;
+function prom() {
+prom_count++;
+if (prom_count > 2) return;
+var v14 = x37.animate({},100);
+v14.reverse();
+v14.ready;
+v14.currentTime = 0;
+x57.reportValidity();
+}
+function f0() {
+var v38 = x37.animate({},300);
+v38.ready;
+x57.prepend(x78);
+}
+function f1() {
+var x57 = document.getElementById("x57");
+x57.disabled = false;
+}
+</script>
+</head>
+
+<body>
+<fieldset id="x37">
+<canvas onfocusin="f0()" >
+<input id="x78" autofocus="" onfocusout="f1()" >
+</canvas>
+<select id="x57" disabled="" required=""></select>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/support/validator.js b/testing/web-platform/tests/html/semantics/forms/constraints/support/validator.js
new file mode 100644
index 0000000000..aa43b3a2f6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/support/validator.js
@@ -0,0 +1,481 @@
+var validator = {
+
+ test_tooLong: function(ctl, data) {
+ var self = this;
+ test(function() {
+ self.pre_check(ctl, 'tooLong');
+ self.iterate_over(ctl, data).forEach(function(val) {
+ const {ctl, expected, condStr} = val;
+ if (expected)
+ assert_true(
+ ctl.validity.tooLong,
+ 'The validity.tooLong should be true' + condStr);
+ else
+ assert_false(
+ ctl.validity.tooLong,
+ 'The validity.tooLong should be false' + condStr);
+ });
+ }, data.name);
+ },
+
+ test_tooShort: function(ctl, data) {
+ var self = this;
+ test(function () {
+ self.pre_check(ctl, "tooShort");
+ self.iterate_over(ctl, data).forEach(function(val) {
+ const {ctl, expected, condStr} = val;
+ if (expected)
+ assert_true(
+ ctl.validity.tooShort,
+ 'The validity.tooShort should be true' + condStr);
+ else
+ assert_false(
+ ctl.validity.tooShort,
+ 'The validity.tooShort should be false' + condStr);
+ });
+ }, data.name);
+ },
+
+ test_patternMismatch: function(ctl, data) {
+ var self = this;
+ test(function () {
+ self.pre_check(ctl, "patternMismatch");
+ self.iterate_over(ctl, data).forEach(function(val) {
+ const {ctl, expected, condStr} = val;
+ if (expected)
+ assert_true(
+ ctl.validity.patternMismatch,
+ 'The validity.patternMismatch should be true' + condStr);
+ else
+ assert_false(
+ ctl.validity.patternMismatch,
+ 'The validity.patternMismatch should be false' + condStr);
+ });
+ }, data.name);
+ },
+
+ test_valueMissing: function(ctl, data) {
+ var self = this;
+ test(function () {
+ self.pre_check(ctl, "valueMissing");
+ self.iterate_over(ctl, data).forEach(function(val) {
+ const {ctl, expected, condStr} = val;
+ if (expected)
+ assert_true(
+ ctl.validity.valueMissing,
+ 'The validity.valueMissing should be true' + condStr);
+ else
+ assert_false(
+ ctl.validity.valueMissing,
+ 'The validity.valueMissing should be false' + condStr);
+ });
+ }, data.name);
+ },
+
+ test_typeMismatch: function(ctl, data) {
+ var self = this;
+ test(function () {
+ self.pre_check(ctl, "typeMismatch");
+ self.iterate_over(ctl, data).forEach(function(val) {
+ const {ctl, expected, condStr} = val;
+ if (expected)
+ assert_true(
+ ctl.validity.typeMismatch,
+ 'The validity.typeMismatch should be true' + condStr);
+ else
+ assert_false(
+ ctl.validity.typeMismatch,
+ 'The validity.typeMismatch should be false' + condStr);
+ });
+ }, data.name);
+ },
+
+ test_rangeOverflow: function(ctl, data) {
+ var self = this;
+ test(function () {
+ self.pre_check(ctl, "rangeOverflow");
+ self.iterate_over(ctl, data).forEach(function(val) {
+ const {ctl, expected, condStr} = val;
+ if (expected)
+ assert_true(
+ ctl.validity.rangeOverflow,
+ 'The validity.rangeOverflow should be true' + condStr);
+ else
+ assert_false(
+ ctl.validity.rangeOverflow,
+ 'The validity.rangeOverflow should be false' + condStr);
+ });
+ }, data.name);
+ },
+
+ test_rangeUnderflow: function(ctl, data) {
+ var self = this;
+ test(function () {
+ self.pre_check(ctl, "rangeUnderflow");
+ self.iterate_over(ctl, data).forEach(function(val) {
+ const {ctl, expected, condStr} = val;
+ if (expected)
+ assert_true(
+ ctl.validity.rangeUnderflow,
+ 'The validity.rangeUnderflow should be true' + condStr);
+ else
+ assert_false(
+ ctl.validity.rangeUnderflow,
+ 'The validity.rangeUnderflow should be false' + condStr);
+ });
+ }, data.name);
+ },
+
+ test_stepMismatch: function(ctl, data) {
+ var self = this;
+ test(function () {
+ self.pre_check(ctl, "stepMismatch");
+ self.iterate_over(ctl, data).forEach(function(val) {
+ const {ctl, expected, condStr} = val;
+ if (expected)
+ assert_true(
+ ctl.validity.stepMismatch,
+ 'The validity.stepMismatch should be true' + condStr);
+ else
+ assert_false(
+ ctl.validity.stepMismatch,
+ 'The validity.stepMismatch should be false' + condStr);
+ });
+ }, data.name);
+ },
+
+ test_badInput: function(ctl, data) {
+ var self = this;
+ test(function () {
+ self.pre_check(ctl, "badInput");
+ self.iterate_over(ctl, data).forEach(function(val) {
+ const {ctl, expected, condStr} = val;
+ if (expected)
+ assert_true(
+ ctl.validity.badInput,
+ 'The validity.badInput should be true' + condStr);
+ else
+ assert_false(
+ ctl.validity.badInput,
+ 'The validity.badInput should be false' + condStr);
+ });
+ }, data.name);
+ },
+
+ test_customError: function(ctl, data) {
+ var self = this;
+ test(function () {
+ self.pre_check(ctl, "customError");
+ self.iterate_over(ctl, data).forEach(function(val) {
+ const {ctl, expected, condStr} = val;
+ if (expected) {
+ assert_true(
+ ctl.validity.customError,
+ 'The validity.customError attribute should be true' + condStr);
+ // validationMessage returns the empty string if ctl is barred from
+ // constraint validation, which happens if ctl is disabled or readOnly.
+ if (ctl.disabled || ctl.readOnly) {
+ assert_equals(
+ ctl.validationMessage, '',
+ 'The validationMessage attribute must be empty' + condStr);
+ } else {
+ assert_equals(
+ ctl.validationMessage, data.conditions.message,
+ 'The validationMessage attribute should be \'' +
+ data.conditions.message + '\'' + condStr);
+ }
+ } else {
+ assert_false(
+ ctl.validity.customError,
+ 'The validity.customError attribute should be false' + condStr);
+ assert_equals(
+ ctl.validationMessage, '',
+ 'The validationMessage attribute must be empty' + condStr);
+ }
+ });
+ }, data.name);
+ },
+
+ test_isValid: function(ctl, data) {
+ var self = this;
+ test(function () {
+ self.iterate_over(ctl, data).forEach(function(val) {
+ const {ctl, expected, condStr} = val;
+ if (expected)
+ assert_true(
+ ctl.validity.valid,
+ 'The validity.valid should be true' + condStr);
+ else
+ assert_false(
+ ctl.validity.valid,
+ 'The validity.valid should be false' + condStr);
+ });
+ }, data.name);
+ },
+
+ test_willValidate: function(ctl, data) {
+ var self = this;
+ test(function () {
+ self.pre_check(ctl, "willValidate");
+ self.set_conditions(ctl, data.conditions);
+ if (data.ancestor) {
+ var dl = document.createElement("datalist");
+ dl.appendChild(ctl);
+ }
+
+ if (data.expected)
+ assert_true(ctl.willValidate, "The willValidate attribute should be true.");
+ else
+ assert_false(ctl.willValidate, "The willValidate attribute should be false.");
+ }, data.name);
+ },
+
+ test_checkValidity: function(ctl, data) {
+ var self = this;
+ test(function () {
+ var eventFired = false;
+ self.pre_check(ctl, "checkValidity");
+ self.set_conditions(ctl, data.conditions);
+ if (data.dirty)
+ self.set_dirty(ctl);
+
+ on_event(ctl, "invalid", function(e){
+ assert_equals(e.type, "invalid", "The invalid event should be fired.");
+ eventFired = true;
+ });
+
+ if (data.expected) {
+ assert_true(ctl.checkValidity(), "The checkValidity method should be true.");
+ assert_false(eventFired, "The invalid event should not be fired.");
+ } else {
+ assert_false(ctl.checkValidity(), "The checkValidity method should be false.");
+ assert_true(eventFired, "The invalid event should be fired.");
+ }
+ }, data.name);
+
+ test(function () {
+ var fm = document.createElement("form");
+ var ctl2 = ctl.cloneNode(true);
+
+ self.pre_check(ctl, "checkValidity");
+ self.set_conditions(ctl2, data.conditions);
+ fm.appendChild(ctl2);
+ document.body.appendChild(fm);
+ if (data.dirty)
+ self.set_dirty(ctl2);
+
+ var result = fm.checkValidity();
+ document.body.removeChild(fm);
+
+ if (data.expected)
+ assert_true(result, "The checkValidity method of the element's form owner should return true.");
+ else
+ assert_false(result, "The checkValidity method of the element's form owner should return false.");
+ }, data.name + " (in a form)");
+ },
+
+ test_reportValidity: function(ctl, data) {
+ var self = this;
+ test(function () {
+ var eventFired = false;
+
+ self.pre_check(ctl, "reportValidity");
+ self.set_conditions(ctl, data.conditions);
+ if (data.dirty)
+ self.set_dirty(ctl);
+
+ on_event(ctl, "invalid", function(e){
+ assert_equals(e.type, "invalid", "The invalid event should be fired.");
+ eventFired = true;
+ });
+
+ if (data.expected) {
+ assert_true(ctl.reportValidity(), "The reportValidity method should be true.");
+ assert_false(eventFired, "The invalid event should not be fired.");
+ } else {
+ assert_false(ctl.reportValidity(), "The reportValidity method should be false.");
+ assert_true(eventFired, "The invalid event should be fired.");
+ }
+ }, data.name);
+
+ test(function () {
+ var fm = document.createElement("form");
+ var ctl2 = ctl.cloneNode(true);
+
+ self.pre_check(ctl, "reportValidity");
+ self.set_conditions(ctl2, data.conditions);
+ fm.appendChild(ctl2);
+ document.body.appendChild(fm);
+ if (data.dirty)
+ self.set_dirty(ctl2);
+
+ var result = fm.reportValidity();
+ document.body.removeChild(fm);
+
+ if (data.expected)
+ assert_true(result, "The reportValidity method of the element's form owner should return true.");
+ else
+ assert_false(result, "The reportValidity method of the element's form owner should return false.");
+ }, data.name + " (in a form)");
+ },
+
+ set_conditions: function(ctl, obj) {
+ [
+ "checked",
+ "disabled",
+ "max",
+ "maxlength",
+ "min",
+ "minlength",
+ "multiple",
+ "pattern",
+ "readonly",
+ "required",
+ "selected",
+ "step",
+ "value"
+ ].forEach(function(item) {
+ ctl.removeAttribute(item);
+ });
+ for (var attr in obj) {
+ if (attr === "message")
+ ctl.setCustomValidity(obj[attr]);
+ else if (attr === "checked" || obj[attr] || obj[attr] === "")
+ ctl[attr] = obj[attr];
+ }
+ },
+
+ set_dirty: function(ctl) {
+ ctl.focus();
+ var old_value = ctl.value;
+ ctl.value = "a";
+ ctl.value = old_value;
+ },
+
+ pre_check: function(ctl, item) {
+ switch (item) {
+ case "willValidate":
+ assert_true(item in ctl, "The " + item + " attribute doesn't exist.");
+ break;
+ case "checkValidity":
+ case "reportValidity":
+ assert_true(item in ctl, "The " + item + " method doesn't exist.");
+ break;
+ case "tooLong":
+ case "tooShort":
+ case "patternMismatch":
+ case "typeMismatch":
+ case "stepMismatch":
+ case "rangeOverflow":
+ case "rangeUnderflow":
+ case "valueMissing":
+ case "badInput":
+ case "valid":
+ assert_true("validity" in ctl, "The validity attribute doesn't exist.");
+ assert_true(item in ctl.validity, "The " + item + " attribute doesn't exist.");
+ break;
+ case "customError":
+ assert_true("validity" in ctl, "The validity attribute doesn't exist.");
+ assert_true("setCustomValidity" in ctl, "The validity attribute doesn't exist.");
+ assert_true("validationMessage" in ctl, "The validity attribute doesn't exist.");
+ assert_true(item in ctl.validity, "The " + item + " attribute doesn't exist.");
+ break;
+ }
+ },
+
+ iterate_over: function(ctl, data) {
+ // Iterate over normal, disabled, readonly, and both (if applicable).
+ var ctlNormal = ctl.cloneNode(true);
+ this.set_conditions(ctlNormal, data.conditions);
+ if (data.dirty)
+ this.set_dirty(ctlNormal);
+
+ var ctlDisabled = ctl.cloneNode(true);
+ this.set_conditions(ctlDisabled, data.conditions);
+ if (data.dirty)
+ this.set_dirty(ctlDisabled);
+ ctlDisabled.disabled = true;
+
+ var expectedImmutable =
+ data.expectedImmutable !== undefined ? data.expectedImmutable : data.expected;
+
+ var variants = [
+ {ctl: ctlNormal, expected: data.expected, condStr: '.'},
+ {ctl: ctlDisabled, expected: expectedImmutable, condStr: ', when control is disabled.'},
+ ];
+
+ if ('readOnly' in ctl) {
+ var ctlReadonly = ctl.cloneNode(true);
+ this.set_conditions(ctlReadonly, data.conditions);
+ if (data.dirty)
+ this.set_dirty(ctlReadonly);
+ ctlReadonly.readOnly = true;
+
+ var ctlBoth = ctl.cloneNode(true);
+ this.set_conditions(ctlBoth, data.conditions);
+ if (data.dirty)
+ this.set_dirty(ctlBoth);
+ ctlBoth.disabled = true;
+ ctlBoth.readOnly = true;
+
+ variants.push({
+ ctl: ctlReadonly,
+ expected: expectedImmutable,
+ condStr: ', when control is readonly.'
+ });
+
+ variants.push({
+ ctl: ctlBoth,
+ expected: expectedImmutable,
+ condStr: ', when control is disabled & readonly.'
+ });
+ }
+
+ return variants;
+ },
+
+ run_test: function(testee, method) {
+ var testMethod = "test_" + method;
+ if (typeof this[testMethod] !== "function") {
+ return false;
+ }
+
+ var ele = null,
+ prefix = "";
+
+ for (var i = 0; i < testee.length; i++) {
+ if (testee[i].types.length > 0) {
+ for (var typ in testee[i].types) {
+ ele = document.createElement(testee[i].tag);
+ document.body.appendChild(ele);
+ try {
+ ele.type = testee[i].types[typ];
+ } catch (e) {
+ //Do nothing, avoid the runtime error breaking the test
+ }
+
+ prefix = "[" + testee[i].tag.toUpperCase() + " in " + testee[i].types[typ].toUpperCase() + " status] ";
+
+ for (var j = 0; j < testee[i].testData.length; j++) {
+ testee[i].testData[j].name = testee[i].testData[j].name.replace(/\[.*\]\s/g, prefix);
+ this[testMethod](ele, testee[i].testData[j]);
+ }
+ }
+ } else {
+ ele = document.createElement(testee[i].tag);
+ document.body.appendChild(ele);
+ prefix = "[" + testee[i].tag + "] ";
+
+ if (testElements[i].tag === "select") {
+ ele.add(new Option('test1', '')); // Placeholder
+ ele.add(new Option("test2", 1));
+ }
+
+ for (var item in testee[i].testData) {
+ testee[i].testData[item].name = testee[i].testData[item].name.replace("[target]", prefix);
+ this[testMethod](ele, testee[i].testData[item]);
+ }
+ }
+ }
+ }
+}
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-email-delete-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-email-delete-manual.html
new file mode 100644
index 0000000000..008089f39a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-email-delete-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>input[type="email"], ValidityState.tooLong and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#limiting-user-input-length:-the-maxlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, an input whose value was edited by the user but still exceeds the input's maxlength should suffer from being too long.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Delete one character from the following text input:</p>
+ <input type="email" value="jane.doe@example.com" maxlength="5" autocomplete="off" id="testinput">
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLInputElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooLong, "tooLong must be true since the user just changed the input's value and the value exceeds the maxlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-password-delete-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-password-delete-manual.html
new file mode 100644
index 0000000000..353d9466dd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-password-delete-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>input[type="password"], ValidityState.tooLong and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#limiting-user-input-length:-the-maxlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, an input whose value was edited by the user but still exceeds the input's maxlength should suffer from being too long.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Delete one character from the following text input:</p>
+ <input type="password" value="swordfish" maxlength="5" autocomplete="off" id="testinput">
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLInputElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooLong, "tooLong must be true since the user just changed the input's value and the value exceeds the maxlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-search-delete-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-search-delete-manual.html
new file mode 100644
index 0000000000..73be3b6d83
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-search-delete-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>input[type="search"], ValidityState.tooLong and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#limiting-user-input-length:-the-maxlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, an input whose value was edited by the user but still exceeds the input's maxlength should suffer from being too long.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Delete one character from the following text input:</p>
+ <input type="search" value="abcdefghi" maxlength="5" autocomplete="off" id="testinput">
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLInputElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooLong, "tooLong must be true since the user just changed the input's value and the value exceeds the maxlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-tel-delete-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-tel-delete-manual.html
new file mode 100644
index 0000000000..bf7682af3e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-tel-delete-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>input[type="tel"], ValidityState.tooLong and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#limiting-user-input-length:-the-maxlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, an input whose value was edited by the user but still exceeds the input's maxlength should suffer from being too long.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Delete one character from the following text input:</p>
+ <input type="tel" value="123-456-7890" maxlength="7" autocomplete="off" id="testinput">
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLInputElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooLong, "tooLong must be true since the user just changed the input's value and the value exceeds the maxlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-text-delete-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-text-delete-manual.html
new file mode 100644
index 0000000000..2eea2b7245
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-text-delete-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>input[type="text"], ValidityState.tooLong and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#limiting-user-input-length:-the-maxlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, an input whose value was edited by the user but still exceeds the input's maxlength should suffer from being too long.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Delete one character from the following text input:</p>
+ <input type="text" value="0123456789" maxlength="5" autocomplete="off" id="testinput">
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLInputElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooLong, "tooLong must be true since the user just changed the input's value and the value exceeds the maxlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-url-delete-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-url-delete-manual.html
new file mode 100644
index 0000000000..17039a71a8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-input-url-delete-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>input[type="url"], ValidityState.tooLong and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#limiting-user-input-length:-the-maxlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, an input whose value was edited by the user but still exceeds the input's maxlength should suffer from being too long.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Delete one character from the following text input:</p>
+ <input type="url" value="http://example.com/foo" maxlength="12" autocomplete="off" id="testinput">
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLInputElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooLong, "tooLong must be true since the user just changed the input's value and the value exceeds the maxlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-textarea-delete-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-textarea-delete-manual.html
new file mode 100644
index 0000000000..2212a1ca90
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooLong-textarea-delete-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>textarea, ValidityState.tooLong and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#limiting-user-input-length:-the-maxlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, a textarea whose value was edited by the user but still exceeds the textarea's maxlength should suffer from being too long.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Delete one character from the following text area:</p>
+ <textarea maxlength="5" autocomplete="off" id="testinput">0123456789</textarea>
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLTextAreaElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooLong, "tooLong must be true since the user just changed the input's value and the value exceeds the maxlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-email-add-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-email-add-manual.html
new file mode 100644
index 0000000000..366f028e77
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-email-add-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>input[type="email"], ValidityState.tooShort and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements:-the-minlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, an input whose value was edited by the user but still falls below the input's minlength should suffer from being too short.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Type one additional character into the following text input:</p>
+ <input type="email" value="jane.doe@example.com" minlength="25" autocomplete="off" id="testinput">
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLInputElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooShort, "tooShort must be true since the user just changed the input's value and the value falls below the minlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-password-add-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-password-add-manual.html
new file mode 100644
index 0000000000..cdfd05152b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-password-add-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>input[type="password"], ValidityState.tooShort and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements:-the-minlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, an input whose value was edited by the user but still falls below the input's minlength should suffer from being too short.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Type one additional character into the following text input:</p>
+ <input type="password" value="swordfish" minlength="15" autocomplete="off" id="testinput">
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLInputElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooShort, "tooShort must be true since the user just changed the input's value and the value falls below the minlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-search-add-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-search-add-manual.html
new file mode 100644
index 0000000000..86af374c37
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-search-add-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>input[type="search"], ValidityState.tooShort and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements:-the-minlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, an input whose value was edited by the user but still falls below the input's minlength should suffer from being too short.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Type one additional character into the following text input:</p>
+ <input type="search" value="abcdefghi" minlength="15" autocomplete="off" id="testinput">
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLInputElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooShort, "tooShort must be true since the user just changed the input's value and the value falls below the minlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-tel-add-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-tel-add-manual.html
new file mode 100644
index 0000000000..08573f892a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-tel-add-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>input[type="tel"], ValidityState.tooShort and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements:-the-minlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, an input whose value was edited by the user but still falls below the input's minlength should suffer from being too short.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Type one additional character into the following text input:</p>
+ <input type="tel" value="123-456-7890" minlength="20" autocomplete="off" id="testinput">
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLInputElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooShort, "tooShort must be true since the user just changed the input's value and the value falls below the minlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-text-add-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-text-add-manual.html
new file mode 100644
index 0000000000..129ed93533
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-text-add-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>input[type="text"], ValidityState.tooShort and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements:-the-minlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, an input whose value was edited by the user but still falls below the input's minlength should suffer from being too short.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Type one additional character into the following text input:</p>
+ <input type="text" value="1234" minlength="10" autocomplete="off" id="testinput">
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLInputElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooShort, "tooShort must be true since the user just changed the input's value and the value falls below the minlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-url-add-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-url-add-manual.html
new file mode 100644
index 0000000000..418c75b8f3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-input-url-add-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>input[type="url"], ValidityState.tooShort and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements:-the-minlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, an input whose value was edited by the user but still falls below the input's minlength should suffer from being too short.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Type one additional character into the following text input:</p>
+ <input type="url" value="http://example.com" minlength="25" autocomplete="off" id="testinput">
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLInputElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooShort, "tooShort must be true since the user just changed the input's value and the value falls below the minlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-textarea-add-manual.html b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-textarea-add-manual.html
new file mode 100644
index 0000000000..73005977df
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/tooShort-textarea-add-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>textarea, ValidityState.tooShort and user editing</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements:-the-minlength-attribute">
+ <meta name="flags" content="interact">
+ <meta name="assert" content="Per the 'Constraint validation' definition in the referenced section, a textarea whose value was edited by the user but still falls below the textarea's minlength should suffer from being too short.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>Type one additional character into the following text area:</p>
+ <textarea minlength="15" autocomplete="off" id="testinput">123456789</textarea>
+
+ <div id="log"></div>
+ <script>
+var input = document.getElementById('testinput');
+setup({explicit_timeout: true, explicit_done: true});
+on_event(input, "input", function () {
+ test(function() {
+ assert_class_string(input.validity, 'ValidityState', 'HTMLTextAreaElement.validity must be a ValidityState instance');
+ assert_true(input.validity.tooShort, "tooShort must be true since the user just changed the input's value and the value falls below the minlength");
+ });
+ done();
+});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/association.window.js b/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/association.window.js
new file mode 100644
index 0000000000..4d84e7d3f0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/association.window.js
@@ -0,0 +1,7 @@
+test(() => {
+ const form = document.createElement("form"),
+ input = document.createElement("input");
+
+ form.appendChild(input);
+ assert_equals(input.form, form);
+}, "Ensure input and form get associated when not in a document");
diff --git a/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form.html b/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form.html
new file mode 100644
index 0000000000..518de35863
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLInputElement#form</title>
+<link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form id="form">
+<p><button id="button">button</button>
+<p><fieldset id="fieldset">fieldset</fieldset>
+<p><input id="input">
+<p><object id="object">object</object>
+<p><output id="output">output</output>
+<p><select id="select"><option>select</option></select>
+<p><textarea id="textarea">textarea</textarea>
+
+<!-- label is special: label.form is an alias for label.control.form -->
+<p><label id="label">label</label>
+<p><label id="label-form" form="form">label-form</label>
+<p><label id="label-form-form2" form="form2">label-form-form2</label>
+<p><label id="label-with-control">label-with-control <input></label>
+<p><label id="label-for" for="control-for-label">label-for</label> <input id="control-for-label">
+<p>
+ <input id="input-with-form-attr-in-form" form="form2">
+ <label id="label-for-control-form-in-form" for="input-with-form-attr-in-form">label-for-control-form-in-form</label>
+</p>
+</form>
+<form id="form2"></form>
+<p>
+ <input id="input-with-form-attr" form="form2">
+ <label id="label-for-control-form" for="input-with-form-attr">label-for-control-form</label>
+</p>
+<!-- misnested tags where form-association is set by the HTML parser -->
+<table>
+ <form id="form3"><!-- self-closes but sets the form element pointer -->
+ <tr>
+ <td><label id="label-in-table">label-in-table</label>
+ <td><label id="label-in-table-with-control">label-in-table <input></label>
+ <td><label id="label-in-table-for" for="input-in-table">label-in-table-for</label>
+ <td><input id="input-in-table"><!-- is associated with form3 -->
+ </tr>
+ </form>
+</table>
+<script>
+var form;
+setup(function() {
+ form = document.getElementById("form");
+ form2 = document.getElementById("form2");
+ form3 = document.getElementById("form3");
+ if (!form || !form2 || !form3) {
+ throw new TypeError("Didn't find all forms");
+ }
+});
+
+var listedElements = [
+ "button",
+ "fieldset",
+ "input",
+ "object",
+ "output",
+ "select",
+ "textarea",
+];
+
+listedElements.forEach(function(localName) {
+ test(function() {
+ var control = document.getElementById(localName);
+ assert_equals(control.form, form);
+ }, localName + ".form");
+});
+
+// label
+function testLabel(id, expected) {
+ test(function() {
+ var label = document.getElementById(id);
+ assert_equals(label.control && label.control.form, expected, 'Sanity check: label.control.form');
+ assert_equals(label.form, expected, 'label.form');
+ }, id + ".form");
+}
+
+testLabel("label", null);
+testLabel("label-form", null);
+testLabel("label-form-form2", null);
+testLabel("label-with-control", form);
+testLabel("label-for", form);
+testLabel("label-for-control-form-in-form", form2);
+testLabel("label-for-control-form", form2);
+testLabel("label-in-table", null);
+testLabel("label-in-table-with-control", form3);
+testLabel("label-in-table-for", form3);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_attribute.html b/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_attribute.html
new file mode 100644
index 0000000000..dde3250dbf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_attribute.html
@@ -0,0 +1,233 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div>
+ <form id="f1"></form>
+ <form id="f2">
+ <input id="i1" />
+ <input id="i2" form="f1" />
+ <input id="i3" />
+ </form>
+ <script>
+ test(function() {
+ var i1 = document.getElementById("i1");
+ var i2 = document.getElementById("i2");
+ var i3 = document.getElementById("i3");
+ var f1 = document.getElementById("f1");
+ var f2 = document.getElementById("f2");
+
+ assert_equals(i1.form, f2,
+ "i1 must be associated with f2 by the parser");
+ assert_equals(i2.form, f1,
+ "i2 is not associated with f2 by the parser " +
+ "since it has the form attribute set to f1");
+
+ f1.appendChild(i1);
+ i3.setAttribute("form", "f1");
+
+ assert_equals(i1.form, f1,
+ "i1's form owner must be reset when parent changes");
+ assert_equals(i3.form, f1,
+ "i3's form owner must be reset when the form" +
+ "attribute is set");
+
+ assert_equals(i2.form, f1);
+ }, "Tests for parser inserted controls");
+ </script>
+ </div>
+
+ <div id="placeholder">
+ </div>
+
+ <script>
+ var reassociateableElements = [
+ "button",
+ "fieldset",
+ "input",
+ "object",
+ "output",
+ "select",
+ "textarea",
+ ];
+
+ var form1 = null;
+ var form2 = null;
+ var placeholder = document.getElementById("placeholder");
+
+ reassociateableElements.forEach(function(localName) {
+ function testControl(test_, desc) {
+ test(function() {
+ var control = document.createElement(localName);
+
+ while(placeholder.firstChild)
+ placeholder.removeChild(placeholder.firstChild);
+
+ form1 = document.createElement("form");
+ form2 = document.createElement("form");
+ form1.id = "form1";
+ form2.id = "form2";
+ placeholder.appendChild(form1);
+ placeholder.appendChild(form2);
+
+ test_.call(control);
+ }, "[" + localName.toUpperCase() + "] " + desc);
+ }
+
+ testControl(function() {
+ form1.appendChild(this);
+ assert_equals(this.form, form1);
+ }, "Basic form association - control with no form attribute is associated with ancestor");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+ form1.appendChild(this);
+ assert_equals(this.form, form1);
+
+ form1.id = "form-one";
+ assert_equals(this.form, null);
+ }, "Form owner is reset to null when control's form attribute is set to an ID " +
+ "that does not exist in the document");
+
+ testControl(function() {
+ this.setAttribute("form", "");
+ form1.appendChild(this);
+ assert_equals(this.form, null);
+ }, "Control whose form attribute is an empty string has no form owner");
+
+ testControl(function() {
+ form1.id = "";
+ this.setAttribute("form", "");
+ form1.appendChild(this);
+ assert_equals(this.form, null);
+ }, "Control whose form attribute is an empty string has no form owner " +
+ "even when form with empty attribute is present");
+
+ testControl(function() {
+ form1.id = "FORM1";
+ this.setAttribute("form", "form1");
+ form1.appendChild(this);
+ assert_equals(this.form, null);
+ }, "Control's form attribute must be a case sensitive match for the form's id");
+
+ testControl(function() {
+ form1.appendChild(this);
+ assert_equals(this.form, form1);
+
+ this.setAttribute("form", "form2");
+ assert_equals(this.form, form2);
+ }, "Setting the form attribute of a control to the id of a non-ancestor form works");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+
+ form2.appendChild(this);
+ assert_equals(this.form, form1);
+
+ this.removeAttribute("form");
+ assert_equals(this.form, form2);
+ }, "Removing form id from a control resets the form owner to ancestor");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+
+ form2.appendChild(this);
+ assert_equals(this.form, form1);
+
+ placeholder.removeChild(form1);
+
+ assert_equals(this.form, null);
+ }, "Removing the form owner of a control with form attribute resets " +
+ "the form owner to null");
+
+ testControl(function() {
+ var form3 = document.createElement("form");
+ form3.id = "form3";
+ placeholder.appendChild(form3);
+ form3.appendChild(this);
+ assert_equals(this.form, form3);
+
+ this.setAttribute("form", "form2");
+ assert_equals(this.form, form2);
+
+ this.setAttribute("form", "form1");
+ assert_equals(this.form, form1);
+ }, "Changing form attibute of control resets form owner to correct form");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+ var form3 = document.createElement("form");
+ form3.id = "form3";
+
+ placeholder.appendChild(form3);
+ placeholder.appendChild(this);
+ assert_equals(this.form, form1);
+
+ form1.appendChild(this);
+ assert_equals(this.form, form1);
+
+ form2.appendChild(this);
+ assert_equals(this.form, form1);
+ }, "Moving a control with form attribute within the document " +
+ "does not change the form owner");
+
+ testControl(function() {
+ form1.id = "form-one";
+ this.setAttribute("form", "form1");
+ form2.appendChild(this);
+ assert_equals(this.form, null);
+
+ form1.id = "form1";
+ assert_equals(this.form, form1);
+ }, "When the id of a non-ancestor form changes from not being a match for the " +
+ "form attribute to being a match, the control's form owner is reset");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+ form1.appendChild(this);
+ assert_equals(this.form, form1);
+
+ form2.id = "form1";
+ form1.parentNode.insertBefore(form2, form1);
+ assert_equals(this.form, form2);
+
+ form2.parentNode.removeChild(form2);
+ assert_equals(this.form, form1);
+
+ form1.parentNode.appendChild(form2);
+ assert_equals(this.form, form1);
+ }, "When form element with same ID as the control's form attribute is inserted " +
+ "earlier in tree order, the form owner is changed to the inserted form");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+ form2.appendChild(this);
+ assert_equals(this.form, form1);
+
+ var span = document.createElement("span");
+ span.id = "form1";
+ form1.parentNode.insertBefore(span, form1);
+ assert_equals(this.form, null);
+
+ form1.parentNode.appendChild(span);
+ assert_equals(this.form, form1);
+ }, "When non-form element with same ID as the control's form attribute is " +
+ "inserted earlier in tree order, the control does not have a form owner");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+ form1.appendChild(this);
+ assert_equals(this.form, form1);
+
+ form1.parentNode.removeChild(form1);
+ assert_equals(this.form, form1);
+ }, "A control that is not in the document but has the form attribute set " +
+ "is associated with the nearest ancestor form if one exists");
+
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table.html b/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table.html
new file mode 100644
index 0000000000..1aa75c27b3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id="root">
+ <form id='form1'></form>
+ <table id='table1'>
+ <form id='form2'>
+ <tr><td><input id='input1'></td></tr>
+ <tr><td><input id='input2' form='form1'></td></tr>
+ </table>
+ <form id="form3">
+ <input id="input3" />
+ </form>
+ </div>
+
+ <script>
+ test(function() {
+ var input1 = document.getElementById('input1');
+ var input2 = document.getElementById('input2');
+ var input3 = document.getElementById('input3');
+ var form1 = document.getElementById('form1');
+ var form2 = document.getElementById('form2');
+ var form3 = document.getElementById('form3');
+
+ var root = document.getElementById('root');
+
+ assert_equals(input1.form, form2,
+ "input1's form owner must be form2 as per the parsing rules");
+ assert_equals(input2.form, form1,
+ "input2's form owner must be the form with id 'form1'");
+ assert_equals(input3.form, form2,
+ "input3's form owner must be form2 as per the parsing rules");
+
+ root.parentNode.removeChild(root);
+
+ assert_equals(input1.form, form2,
+ "input1's form owner must not have changed since they are both in the same subtree");
+ assert_equals(input2.form, null,
+ "input2 must not have a form owner since it has the form attribute set");
+ assert_equals(input3.form, form2,
+ "input3's form owner must not have changed since they are both in the same subtree");
+
+ }, "Form element and form controls nested inside a table are correctly handled");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table_2.html b/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table_2.html
new file mode 100644
index 0000000000..d9aee12b5f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table_2.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div>
+ <form id='form1'></form>
+ <table id='table1'>
+ <form id='form2'>
+ <script>
+ var t = document.getElementById('table1');
+ var f = document.getElementById('form2');
+ t.removeChild(f);
+ </script>
+ <tr><td><input id='input1'></td></tr>
+ <tr><td><input id='input2' form='form1'></td></tr>
+ </table>
+ <form id="form3">
+ <input id="input3" />
+ </form>
+ </div>
+
+ <script>
+ test(function() {
+ var form1 = document.getElementById('form1');
+
+ assert_equals(document.getElementById('input1').form, null,
+ "input1's form owner must be null since form2 is not in the" +
+ "same home subtree");
+
+ assert_equals(document.getElementById('input2').form, form1,
+ "input2's form owner must be the form with id 'form1'");
+
+ assert_equals(document.getElementById('input3').form, null,
+ "input3's form owner must be null instead of form2 (as per parsing rules)" +
+ "since form2 is not in the same home subtree");
+
+ }, "Controls nested in tables are not associated with form element inside the " +
+ "table if the form had been removed by script before the controls were " +
+ "inserted by the parser");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table_3.html b/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table_3.html
new file mode 100644
index 0000000000..db70b34b1a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table_3.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<table><form><tr><td><input></table>
+<div id=2></div>
+<script>
+test(() => {
+ const input = document.querySelector("input"),
+ form = document.querySelector("form");
+ assert_equals(input.form, form);
+ document.getElementById("2").appendChild(form.parentNode);
+ assert_equals(input.form, form);
+ document.getElementById("2").appendChild(input);
+ assert_equals(input.form, null);
+}, "parser inserted flag is not reset by insertions with the owner form, but reset by by removal from the owner form");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/FormDataEvent.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/FormDataEvent.window.js
new file mode 100644
index 0000000000..830d536a66
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/FormDataEvent.window.js
@@ -0,0 +1,20 @@
+// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-formdataevent-interface
+
+test(() => {
+ let fd = new FormData();
+ assert_throws_js(TypeError, () => { new FormDataEvent() }, '0 arguments');
+ assert_throws_js(TypeError, () => { new FormDataEvent('foo') }, '1 argument');
+ assert_throws_js(TypeError, () => { new FormDataEvent(fd, fd) }, '2 invalid arguments');
+ assert_throws_js(TypeError, () => { new FormDataEvent('foo', null) }, 'Null dictionary');
+ assert_throws_js(TypeError, () => { new FormDataEvent('foo', undefined) }, 'Undefined dictionary');
+ assert_throws_js(TypeError, () => { new FormDataEvent('foo', { formData: null }) }, 'Null formData');
+ assert_throws_js(TypeError, () => { new FormDataEvent('foo', { formData: undefined }) }, 'Undefined formData');
+ assert_throws_js(TypeError, () => { new FormDataEvent('foo', { formData: 'bar' }) }, 'Wrong type of formData');
+}, 'Failing FormDataEvent constructor');
+
+test(() => {
+ let fd = new FormData();
+ let event = new FormDataEvent('bar', { formData: fd, bubbles: true });
+ assert_equals(event.formData, fd);
+ assert_true(event.bubbles);
+}, 'Successful FormDataEvent constructor');
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/SubmitEvent.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/SubmitEvent.window.js
new file mode 100644
index 0000000000..3821815515
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/SubmitEvent.window.js
@@ -0,0 +1,41 @@
+// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-submitevent-interface
+
+test(() => {
+ assert_throws_js(TypeError, () => SubmitEvent(""), "Calling SubmitEvent constructor without 'new' must throw");
+ assert_throws_js(TypeError, () => { new SubmitEvent() }, '0 arguments');
+ assert_throws_js(TypeError, () => { new SubmitEvent('foo', { submitter: 'bar' }) }, 'Wrong type of submitter');
+}, 'Failing SubmitEvent constructor');
+
+test(() => {
+ let button = document.createElement('button');
+ let event = new SubmitEvent('bar', { submitter: button, bubbles: true });
+ assert_equals(event.submitter, button);
+ assert_true(event.bubbles);
+}, 'Successful SubmitEvent constructor');
+
+test(() => {
+ let event1 = new SubmitEvent('bar', {submitter: null});
+ assert_equals(event1.submitter, null);
+ let event2 = new SubmitEvent('baz', {submitter: undefined});
+ assert_equals(event2.submitter, null);
+}, 'Successful SubmitEvent constructor; null/undefined submitter');
+
+test(() => {
+ let event1 = new SubmitEvent('bar', null);
+ assert_equals(event1.submitter, null);
+ let event2 = new SubmitEvent('baz', undefined);
+ assert_equals(event2.submitter, null);
+}, 'Successful SubmitEvent constructor; null/undefined dictionary');
+
+test(() => {
+ let event1 = new SubmitEvent('bar', {});
+ assert_equals(event1.submitter, null);
+ let button = document.createElement('button');
+ let event2 = new SubmitEvent("bax", button);
+ assert_equals(event2.submitter, null);
+}, 'Successful SubmitEvent constructor; empty dictionary');
+
+test(() => {
+ let event = new SubmitEvent('bar');
+ assert_equals(event.submitter, null);
+}, 'Successful SubmitEvent constructor; missing dictionary');
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/constructing-form-data-set.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/constructing-form-data-set.html
new file mode 100644
index 0000000000..c27270ea30
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/constructing-form-data-set.html
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set">
+<link ref="help" href="https://xhr.spec.whatwg.org/#dom-formdata">
+<link rel="help" href="https://fetch.spec.whatwg.org/#concept-bodyinit-extract">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/targetted-form.js"></script>
+
+<iframe name="frame1" id="frame1"></iframe>
+<form accept-charset="iso-8859-1" target="frame1" action="/common/blank.html" id="form1">
+ <input type="hidden" name="_charset_">
+</form>
+
+<iframe name="frame2" id="frame2"></iframe>
+<form target="frame2" action="/common/blank.html" id="form2">
+ <input type="text" name="foo">
+ <button type="close" name="close" value="true">close</button>
+ <button type="button" name="button" value="true">button</button>
+ <button type="reset" name="reset" value="true">reset</button>
+ <button type="submit" name="submit" value="true">submit</button>
+</form>
+
+<script>
+
+const form1 = document.getElementById('form1'),
+ form2 = document.getElementById('form2'),
+ frame1 = document.getElementById('frame1'),
+ frame2 = document.getElementById('frame2');
+
+test(() => {
+ const formData = new FormData(form1);
+ assert_equals(formData.get('_charset_'), 'UTF-8');
+}, 'FormData constructor always produces UTF-8 _charset_ value.');
+
+async_test(t => {
+ frame1.onload = t.step_func(() => {
+ if (frame1.contentWindow.location.href == "about:blank") { return; }
+ assert_not_equals(frame1.contentDocument.URL.indexOf('_charset_=windows-1252'), -1,"should see _charset_=windows-1252 in "+frame1.contentDocument.URL);
+ t.done();
+ });
+ form1.submit();
+},'_charset_ control sets the expected encoding name.');
+
+async_test(t => {
+ frame2.onload = t.step_func_done(() => {
+ assert_equals(frame2.contentDocument.URL.split("?")[1], 'foo=&submit=true');
+ });
+ form2.submit.click();
+}, 'The button cannot be setted if it is not a submitter.');
+
+test(() => {
+ let didCallHandler = false;
+ let wasBubbles = false;
+ let wasCancelable = true;
+ let form = populateForm();
+ document.addEventListener('formdata', e => {
+ didCallHandler = true;
+ wasBubbles = e.bubbles;
+ wasCancelable = e.cancelable;
+ });
+ new FormData(form);
+ assert_true(didCallHandler);
+ assert_true(wasBubbles);
+ assert_false(wasCancelable);
+}, '"formdata" event bubbles, and is not cancelable.');
+
+test(() => {
+ let didCallHandler = false;
+ let form = populateForm();
+ let orphanRoot = document.createElement('div');
+ orphanRoot.appendChild(form);
+ orphanRoot.addEventListener('formdata', e => {
+ didCallHandler = true;
+ });
+ new FormData(form);
+ assert_true(didCallHandler);
+}, '"formdata" event bubbles in an orphan tree.');
+
+for (const enctype of ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"]) {
+ test((t) => {
+ let form = populateForm('<input name=file type=file><input name=empty type=file>');
+ form.enctype = enctype;
+
+ const file = new File([], "filename");
+ const dataTransfer = new DataTransfer();
+ dataTransfer.items.add(file);
+ form.querySelector('input[name=file]').files = dataTransfer.files;
+
+ form.addEventListener('formdata', t.step_func(e => {
+ assert_true(e.formData.has('file'));
+ assert_equals(e.formData.get('file'), file);
+ assert_true(e.formData.has('empty'));
+ assert_true(e.formData.get('empty') instanceof File);
+ }));
+ form.submit();
+ }, `Files in a ${enctype} form show up as File objects in the "formData" IDL attribute`);
+}
+
+test(() => {
+ let listener1ok = false;
+ let listeern2ok = false;
+ let form = populateForm('<input name=n1 value=v1>');
+ form.addEventListener('formdata', e => {
+ listener1ok = e.formData.get('n1') == 'v1';
+ e.formData.append('h1', 'vh1');
+ e.formData.append('h2', 'vh2');
+ });
+ form.addEventListener('formdata', e => {
+ if (e.formData.get('h1') == 'vh1' && e.formData.get('h2') == 'vh2')
+ listener2ok = true;
+ });
+ form.submit();
+ assert_true(listener1ok);
+ assert_true(listener2ok);
+}, '"formData" IDL attribute should have entries for form-associated elements' +
+ ' in the first event handler, and the second handler can read entries ' +
+ 'set by the first handler.');
+
+let t1 = async_test('Entries added to "formData" IDL attribute should be submitted.');
+t1.step(() => {
+ let form = populateForm('<input name=n1 value=v1>');
+ form.addEventListener('formdata', e => {
+ e.formData.append('h1', 'vh1');
+ });
+ let iframe = form.previousSibling;
+ iframe.onload = t1.step_func(() => {
+ // The initial about:blank load event can be fired before the form navigation occurs.
+ // See https://github.com/whatwg/html/issues/490 for more information.
+ if (iframe.contentWindow.location.href == "about:blank") { return; }
+ assert_true(iframe.contentWindow.location.search.indexOf('n1=v1&h1=vh1') != -1);
+ t1.done();
+ });
+ form.submit();
+});
+
+test(() => {
+ const form = populateForm('');
+ form.addEventListener('formdata', e => {
+ e.formData.append('a\nb', 'c\rd');
+ });
+ const formData = new FormData(form);
+ const [name, value] = [...formData][0];
+ assert_equals(name, 'a\nb');
+ assert_equals(value, 'c\rd');
+}, 'Entries added to the "formdata" IDL attribute shouldn\'t be newline normalized in the resulting FormData');
+
+test(() => {
+ let form = populateForm('<input name=n1 value=v1><button type=submit name=n2 value=v2></button>');
+ let formDataInEvent = null;
+ let submitter = form.querySelector('button[type=submit]');
+ form.addEventListener('submit', e => {
+ e.preventDefault();
+ formDataInEvent = new FormData(e.target);
+ });
+
+ submitter.click();
+ assert_equals(formDataInEvent.get('n1'), 'v1');
+ assert_false(formDataInEvent.has('n2'));
+}, 'The constructed FormData object should not contain an entry for the submit button that was used to submit the form.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/enctypes-helper.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/enctypes-helper.js
new file mode 100644
index 0000000000..0f0d68163d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/enctypes-helper.js
@@ -0,0 +1,187 @@
+// This file exposes the `formSubmissionTemplate` function, which can be used
+// to create tests for the form submission encoding of various enctypes:
+//
+// const urlencodedTest = formSubmissionTemplate(
+// "application/x-www-form-urlencoded",
+// (expected, _actualFormBody) => expected
+// );
+//
+// urlencodedTest({
+// name: "a",
+// value: "b",
+// expected: "a=b",
+// formEncoding: "UTF-8", // optional
+// description: "Simple urlencoded test"
+// });
+//
+// The above call to `urlencodedTest` tests the urlencoded form submission for a
+// form whose entry list contains a single entry with name "a" and value "b",
+// and it checks that the form payload matches the `expected` property after
+// isomorphic-encoding.
+//
+// Since per the spec no normalization of the form entries should happen before
+// the actual form encoding, each call to `urlencodedTest` will in fact add two
+// tests: one submitting the entry as a form control (marked "normal form"), and
+// one adding the entry through the `formdata` event (marked "formdata event").
+// Both cases are compared against the same expected value.
+//
+// Since multipart/form-data boundary strings can't be predicted ahead of time,
+// the second parameter of `formSubmissionTemplate` allows transforming the
+// expected value passed to each test. The second argument of that callback
+// is the actual form body (isomorphic-decoded). When this callback is used, the
+// `expected` property doesn't need to be a string.
+
+(() => {
+ // Using echo-content-escaped.py rather than
+ // /fetch/api/resources/echo-content.py to work around WebKit not
+ // percent-encoding \x00, which causes the response to be detected as
+ // a binary file and served as a download.
+ const ACTION_URL = "/FileAPI/file/resources/echo-content-escaped.py";
+
+ const IFRAME_NAME = "formtargetframe";
+
+ // Undoes the escapes from echo-content-escaped.py
+ function unescape(str) {
+ return str
+ .replace(/\r\n?|\n/g, "\r\n")
+ .replace(
+ /\\x[0-9A-Fa-f]{2}/g,
+ (escape) => String.fromCodePoint(parseInt(escape.substring(2), 16)),
+ )
+ .replace(/\\\\/g, "\\");
+ }
+
+ // Tests the form submission of an entry list containing a single entry.
+ //
+ // `expectedBuilder` is a function that takes in the actual form body
+ // (necessary to get the multipart/form-data payload) and returns the form
+ // body that should be expected.
+ //
+ // If `testFormData` is false, the form entry will be submitted in for
+ // controls. If it is true, it will submitted by modifying the entry list
+ // during the `formdata` event.
+ async function formSubmissionTest({
+ name,
+ value,
+ expectedBuilder,
+ enctype,
+ formEncoding,
+ testFormData = false,
+ testCase,
+ }) {
+ if (document.readyState !== "complete") {
+ await new Promise((resolve) => addEventListener("load", resolve));
+ }
+
+ const formTargetFrame = Object.assign(document.createElement("iframe"), {
+ name: IFRAME_NAME,
+ });
+ document.body.append(formTargetFrame);
+ testCase.add_cleanup(() => {
+ document.body.removeChild(formTargetFrame);
+ });
+
+ const form = Object.assign(document.createElement("form"), {
+ acceptCharset: formEncoding,
+ action: ACTION_URL,
+ method: "POST",
+ enctype,
+ target: IFRAME_NAME,
+ });
+ document.body.append(form);
+ testCase.add_cleanup(() => {
+ document.body.removeChild(form);
+ });
+
+ if (!testFormData) {
+ const input = document.createElement("input");
+ input.name = name;
+ if (value instanceof File) {
+ input.type = "file";
+ const dataTransfer = new DataTransfer();
+ dataTransfer.items.add(value);
+ input.files = dataTransfer.files;
+ } else {
+ input.type = "hidden";
+ input.value = value;
+ }
+ form.append(input);
+ } else {
+ form.addEventListener("formdata", (evt) => {
+ evt.formData.append(name, value);
+ });
+ }
+
+ await new Promise((resolve) => {
+ form.submit();
+ formTargetFrame.onload = resolve;
+ });
+
+ const serialized = unescape(
+ formTargetFrame.contentDocument.body.textContent,
+ );
+ const expected = expectedBuilder(serialized);
+ assert_equals(serialized, expected);
+ }
+
+ // This function returns a function to add individual form tests corresponding
+ // to some enctype.
+ // `expectedBuilder` is an optional callback that takes two parameters:
+ // `expected` (the `expected` property passed to a test) and `actualFormBody`
+ // (the actual form body submitted by the browser, isomorphic-decoded). It
+ // must return the correct form body that should have been submitted,
+ // isomorphic-encoded. This is necessary in order to account for the
+ // multipart/form-data boundary.
+ //
+ // The returned function takes an object with the following properties:
+ // - `name`, the form entry's name. Must be a string.
+ // - `value`, the form entry's value, either a string or a `File` object.
+ // - `expected`, the expected form body. Usually a string, but it can be
+ // anything depending on `expectedBuilder`.
+ // - `formEncoding` (optional), the character encoding used for submitting the
+ // form.
+ // - `description`, used as part of the testharness test's description.
+ window.formSubmissionTemplate = (
+ enctype,
+ expectedBuilder = (expected) => expected
+ ) => {
+ function form({
+ name,
+ value,
+ expected,
+ formEncoding = "utf-8",
+ description,
+ }) {
+ const commonParams = {
+ name,
+ value,
+ expectedBuilder: expectedBuilder.bind(null, expected),
+ enctype,
+ formEncoding,
+ };
+
+ // Normal form
+ promise_test(
+ (testCase) =>
+ formSubmissionTest({
+ ...commonParams,
+ testCase,
+ }),
+ `${enctype}: ${description} (normal form)`,
+ );
+
+ // formdata event
+ promise_test(
+ (testCase) =>
+ formSubmissionTest({
+ ...commonParams,
+ testFormData: true,
+ testCase,
+ }),
+ `${enctype}: ${description} (formdata event)`,
+ );
+ }
+
+ return form;
+ };
+})();
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js
new file mode 100644
index 0000000000..7df128515c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js
@@ -0,0 +1,99 @@
+test(t => {
+ const form = document.body.appendChild(document.createElement("form")),
+ input = form.appendChild(document.createElement("input"));
+ input.type = "file";
+ input.name = "hi";
+ t.add_cleanup(() => {
+ document.body.removeChild(form);
+ });
+ const fd = new FormData(form),
+ value = fd.get(input.name);
+ assert_true(value instanceof File, "value is a File");
+ assert_equals(value.name, "", "name");
+ assert_equals(value.type, "application/octet-stream", "type");
+ assert_equals(value.size, 0, "expected value to be an empty File");
+}, "Empty <input type=file> is still added to the form's entry list");
+
+async_test((t) => {
+ const form = document.body.appendChild(document.createElement("form")),
+ input = form.appendChild(document.createElement("input")),
+ target = document.createElement("iframe");
+ target.name = "target1";
+ document.body.appendChild(target);
+ form.method = "POST";
+ form.action = "/fetch/api/resources/echo-content.py";
+ form.enctype = "application/x-www-form-urlencoded";
+ form.target = target.name;
+ input.type = "file";
+ input.name = "hi";
+ t.add_cleanup(() => {
+ document.body.removeChild(form);
+ document.body.removeChild(target);
+ });
+
+ target.addEventListener("load", t.step_func_done(() => {
+ assert_equals(target.contentDocument.body.textContent, "hi=");
+ }));
+ form.submit();
+}, "Empty <input type=file> shows up in the urlencoded serialization");
+
+async_test((t) => {
+ const form = document.body.appendChild(document.createElement("form")),
+ input = form.appendChild(document.createElement("input")),
+ target = document.createElement("iframe");
+ target.name = "target2";
+ document.body.appendChild(target);
+ form.method = "POST";
+ form.action = "/fetch/api/resources/echo-content.py";
+ form.enctype = "multipart/form-data";
+ form.target = target.name;
+ input.type = "file";
+ input.name = "hi";
+ t.add_cleanup(() => {
+ document.body.removeChild(form);
+ document.body.removeChild(target);
+ });
+
+ target.addEventListener("load", t.step_func_done(() => {
+ // We use \n rather than \r\n because newlines get normalized as a result
+ // of HTML parsing.
+ const found = target.contentDocument.body.textContent;
+ const boundary = found.split("\n")[0];
+ const expected = [
+ boundary,
+ 'Content-Disposition: form-data; name="hi"; filename=""',
+ "Content-Type: application/octet-stream",
+ "",
+ "",
+ boundary + "--",
+ "",
+ ].join("\n");
+ assert_equals(found, expected);
+ }));
+ form.submit();
+}, "Empty <input type=file> shows up in the multipart/form-data serialization");
+
+async_test((t) => {
+ const form = document.body.appendChild(document.createElement("form")),
+ input = form.appendChild(document.createElement("input")),
+ target = document.createElement("iframe");
+ target.name = "target3";
+ document.body.appendChild(target);
+ form.method = "POST";
+ form.action = "/fetch/api/resources/echo-content.py";
+ form.enctype = "text/plain";
+ form.target = target.name;
+ input.type = "file";
+ input.name = "hi";
+ t.add_cleanup(() => {
+ document.body.removeChild(form);
+ document.body.removeChild(target);
+ });
+
+ target.addEventListener("load", t.step_func_done(() => {
+ // The actual result is "hi=\r\n"; the newline gets normalized as a side
+ // effect of the HTML parsing.
+ assert_equals(target.contentDocument.body.textContent, "hi=\n");
+ }));
+ form.submit();
+}, "Empty <input type=file> shows up in the text/plain serialization");
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv-form.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv-form.html
new file mode 100644
index 0000000000..ce87abd957
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv-form.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>This is the form that will be submitted</title>
+
+<form action="form-echo.py" method="post" enctype="text/plain">
+ <input id="input1" type="text">
+ <select id="input2">
+ <option selected>option
+ </select>
+ <input id="input3" type="radio" checked>
+ <input id="input4" type="checkbox" checked>
+</form>
+
+<script>
+"use strict";
+
+const form = document.querySelector("form");
+
+for (let el of Array.from(form.querySelectorAll("input"))) { // Firefox/Edge support
+ el.name = el.id + "\uDC01";
+ el.value = el.id + "\uDC01";
+}
+
+const select = document.querySelector("select");
+select.name = select.id + "\uDC01";
+select.firstElementChild.value = select.id + "\uDC01";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv.html
new file mode 100644
index 0000000000..4ac26092ee
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Submitting a form data set that contains unpaired surrogates must convert to Unicode scalar values</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#constructing-form-data-set">
+<link rel="help" href="https://github.com/whatwg/html/issues/1490">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe id="testframe" src="form-data-set-usv-form.html"></iframe>
+<iframe id="testframe2" src="form-data-set-usv-form.html"></iframe>
+
+<script>
+"use strict";
+
+async_test(t => {
+ window.addEventListener("load", t.step_func(() => {
+ const iframe = document.querySelector("#testframe");
+ const form = iframe.contentWindow.document.querySelector("form");
+
+ iframe.onload = t.step_func_done(() => {
+ const result = iframe.contentWindow.document.body.textContent;
+
+ assert_equals(result,
+ "69 6e 70 75 74 31 ef bf bd 3d 69 6e 70 75 74 31 ef bf bd " + // input1\uFFFD=input1\uFFFD
+ "0d 0a " + // \r\n
+ "69 6e 70 75 74 32 ef bf bd 3d 69 6e 70 75 74 32 ef bf bd " + // input2\uFFFD=input2\uFFFD
+ "0d 0a " + // \r\n
+ "69 6e 70 75 74 33 ef bf bd 3d 69 6e 70 75 74 33 ef bf bd " + // input3\uFFFD=input3\uFFFD
+ "0d 0a " + // \r\n
+ "69 6e 70 75 74 34 ef bf bd 3d 69 6e 70 75 74 34 ef bf bd " + // input4\uFFFD=input4\uFFFD
+ "0d 0a" // \r\n
+ );
+
+ // ef bf bd is the UTF-8 encoding of U+FFFD
+ });
+
+ form.submit();
+ }));
+}, 'Strings from form controls should be converted to Unicode scalar values in form submission');
+
+async_test(t => {
+ window.addEventListener("load", t.step_func_done(() => {
+ const iframe = document.querySelector("#testframe2");
+ const formData = new FormData(iframe.contentWindow.document.querySelector("form"));
+ assert_equals(formData.get("input1\uFFFD"), "input1\uFFFD");
+ assert_equals(formData.get("input2\uFFFD"), "input2\uFFFD");
+ assert_equals(formData.get("input3\uFFFD"), "input3\uFFFD");
+ assert_equals(formData.get("input4\uFFFD"), "input4\uFFFD");
+ }));
+}, 'Strings from form controls should be converted to Unicode scalar values in FormData');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-2.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-2.html
new file mode 100644
index 0000000000..f7939e0fa3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-2.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- The onclick submit() should *not* get superseded in this case by the
+ default action submit(), because onclick here calls preventDefault().
+ -->
+
+
+
+
+<label for=frame1 style="display:block">This frame should stay blank</label>
+<iframe name=frame1 id=frame1></iframe>
+<label for=frame2 style="display:block">This frame should navigate (to 404)</label>
+<iframe name=frame2 id=frame2></iframe>
+<form id="form1" target="frame2" action="nonexistent.html">
+ <input type=hidden name=navigated value=1>
+ <input id=submitbutton type=submit>
+</form>
+
+<script>
+let frame1 = document.getElementById('frame1');
+let frame2 = document.getElementById('frame2');
+let form1 = document.getElementById('form1');
+let submitbutton = document.getElementById('submitbutton');
+
+async_test(t => {
+ window.addEventListener('load', () => {
+ frame1.addEventListener('load', t.step_func_done(() => {
+ assert_unreached("Frame1 should not get navigated by this test.");
+ }));
+ frame2.addEventListener('load', t.step_func_done(() => {
+ let params = (new URL(frame2.contentWindow.location)).searchParams;
+ let wasNavigated = !!params.get("navigated");
+ assert_true(wasNavigated);
+ }));
+ form1.addEventListener('click', t.step_func(() => {
+ form1.submit();
+ form1.target='frame1';
+ event.preventDefault(); // Prevent default here
+ }));
+ submitbutton.click();
+ });
+}, 'preventDefault should allow onclick submit() to succeed');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-3.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-3.html
new file mode 100644
index 0000000000..fbb6a42577
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-3.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- <button> should have the same double-submit protection that
+ <input type=submit> has.
+ -->
+
+
+
+
+<label for=frame1 style="display:block">This frame should stay blank</label>
+<iframe name=frame1 id=frame1></iframe>
+<label for=frame2 style="display:block">This frame should navigate (to 404)</label>
+<iframe name=frame2 id=frame2></iframe>
+<form id="form1" target="frame1" action="nonexistent.html">
+ <input type=hidden name=navigated value=1>
+ <button id=submitbutton>submit</button>
+</form>
+
+<script>
+let frame1 = document.getElementById('frame1');
+let frame2 = document.getElementById('frame2');
+let form1 = document.getElementById('form1');
+let submitbutton = document.getElementById('submitbutton');
+
+async_test(t => {
+ window.addEventListener('load', () => {
+ frame1.addEventListener('load', t.step_func_done(() => {
+ assert_unreached("Frame1 should not get navigated by this test.");
+ }));
+ frame2.addEventListener('load', t.step_func_done(() => {
+ let params = (new URL(frame2.contentWindow.location)).searchParams;
+ let wasNavigated = !!params.get("navigated");
+ assert_true(wasNavigated)
+ }));
+ form1.addEventListener('click', t.step_func(() => {
+ form1.submit();
+ form1.target='frame2';
+
+ }));
+ submitbutton.click();
+ });
+}, '<button> should have the same double-submit protection as <input type=submit>');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-default-action.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-default-action.html
new file mode 100644
index 0000000000..a14cfe7afa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-default-action.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/targetted-form.js"></script>
+<!--
+ The submit() in event handler should get superseded by the default action
+ submit(), which isn't preventDefaulted. This is per the Form Submission
+ Algorithm [1], step 24, which says that new planned navigations replace old
+ planned navigations.
+ [1] https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm
+-->
+<body>
+<script>
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ submitter.addEventListener('click', () => {
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v4");
+}, 'default submit action should supersede input onclick submit()');
+
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><button>submit</button>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("button");
+ submitter.addEventListener('click', (e) => {
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v4");
+}, 'default submit action should supersede button onclick submit()');
+
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ form.addEventListener('click', () => {
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v4");
+}, 'default submit action should supersede form onclick submit()');
+
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ form.addEventListener('submit', () => {
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v4");
+}, 'default submit action should supersede form onsubmit submit()');
+
+promise_test(async (t) => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ form.addEventListener('click', () => {
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ form.addEventListener('submit', () => {
+ input.value = 'v5';
+ form.submit();
+ input.value = 'v6';
+ form.submit();
+ input.value = 'v7';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v7");
+}, 'default submit action should supersede form onclick/onsubmit submit()');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-multiple-targets.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-multiple-targets.html
new file mode 100644
index 0000000000..2b5a589b53
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-multiple-targets.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+
+<!-- The expected behavior of this test is not explicitly specified. -->
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<form id=myform name=myform action="/formaction.html"></form>
+<iframe id=frame1 name=target1></iframe>
+<iframe id=frame2 name=target2></iframe>
+<iframe id=frame3 name=target3></iframe>
+
+<script>
+
+promise_test(async () => {
+ const frame1LoadPromise = new Promise(resolve => frame1.onload = resolve);
+ const frame2LoadPromise = new Promise(resolve => frame2.onload = resolve);
+ const frame3LoadPromise = new Promise(resolve => frame3.onload = resolve);
+
+ myform.target = 'target1';
+ myform.submit();
+ myform.target = 'target2';
+ myform.submit();
+ myform.target = 'target3';
+ myform.submit();
+
+ await Promise.all([frame1LoadPromise, frame2LoadPromise, frame3LoadPromise]);
+
+ assert_equals(frame1.contentDocument.location.pathname, '/formaction.html');
+ assert_equals(frame2.contentDocument.location.pathname, '/formaction.html');
+ assert_equals(frame3.contentDocument.location.pathname, '/formaction.html');
+
+}, 'Verifies that one form used to target multiple frames in succession navigates all of them.');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault-click.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault-click.html
new file mode 100644
index 0000000000..68dc9c10a8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault-click.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/targetted-form.js"></script>
+<!--
+ The submit() in event handler should *not* get superseded in this case by the
+ default action submit(), because event handler here calls preventDefault().
+-->
+<body>
+<script>
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ submitter.addEventListener('click', (e) => {
+ e.preventDefault();
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v3");
+}, 'PreventDefaulting input onclick should allow submit() to succeed');
+
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><button>submit</button>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("button");
+ submitter.addEventListener('click', (e) => {
+ e.preventDefault();
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v3");
+}, 'PreventDefaulting button onclick should allow submit() to succeed');
+
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ form.addEventListener('click', (e) => {
+ e.preventDefault();
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v3");
+}, 'PreventDefaulting form onclick should allow submit() to succeed');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault.html
new file mode 100644
index 0000000000..b63b78916c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/targetted-form.js"></script>
+<!--
+ The submit() in event handler should *not* get superseded in this case by the
+ default action submit(), because event handler here calls preventDefault().
+-->
+<body>
+<script>
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ form.addEventListener('click', () => {
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ form.addEventListener('submit', (e) => {
+ e.preventDefault();
+ input.value = 'v5';
+ form.submit();
+ input.value = 'v6';
+ form.submit();
+ input.value = 'v7';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v6");
+}, 'PreventDefaulting form onsubmit should allow submit() to succeed');
+
+promise_test(async () => {
+ let form = populateForm('<input type=submit><input name=n1 value=v1>');
+ let iframe = form.previousSibling;
+ let input = form['n1'];
+ let submitter = form.querySelector('input[type=submit]');
+ form.addEventListener('submit', e => {
+ e.preventDefault();
+ input.value = 'v2';
+ form.submit();
+
+ input.value = 'v3';
+ form.remove();
+ form.submit();
+ document.body.insertBefore(form, iframe.nextSibling);
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v2");
+}, 'PreventDefaulting form onsubmit should allow submit() to succeed and the second submit() which is invalid should not supersede first one');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-to-different-origin-frame.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-to-different-origin-frame.html
new file mode 100644
index 0000000000..00a46bfd43
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-to-different-origin-frame.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<!-- The onclick submit() should get superseded by the default
+ action submit(), which isn't preventDefaulted by onclick here.
+ This is per the Form Submission Algorithm [1], step 22.3, which
+ says that new planned navigations replace old planned navigations.
+ [1] https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm
+ -->
+
+<label for=frame1 style="display:block">This frame should stay blank</label>
+<iframe name=frame1 id=frame1></iframe>
+<label for=frame2 style="display:block">This frame should navigate (to 404)</label>
+<iframe name=frame2 id=frame2></iframe>
+<form id="form1" target="frame1" action="nonexistent.html">
+ <input type=hidden name=navigated value=1>
+ <input id=submitbutton type=submit>
+</form>
+
+<script>
+promise_test(async () => {
+ function getLoadPromise(frame) {
+ return new Promise(resolve => {
+ frame.addEventListener('load', resolve);
+ });
+ }
+
+ const frame1 = document.getElementById('frame1');
+ const frame2 = document.getElementById('frame2');
+ await getLoadPromise(window);
+
+ const frame1LoadPromise = getLoadPromise(frame1);
+ let frame2LoadPromise = getLoadPromise(frame2);
+ const subframeUrl = get_host_info().REMOTE_ORIGIN;
+ frame1.src = subframeUrl;
+ frame2.src = subframeUrl;
+ await frame1LoadPromise;
+ await frame2LoadPromise;
+ assert_false(!!frame1.contentDocument, 'frame1 should have a different origin.');
+ assert_false(!!frame2.contentDocument, 'frame2 should have a different origin.');
+
+ window.frame1Navigated = false;
+ frame1.addEventListener('load', () => {
+ window.frame1Navigated = true;
+ });
+ frame2LoadPromise = getLoadPromise(frame2);
+
+ form1.addEventListener('click', () => {
+ form1.submit();
+ form1.target = 'frame2';
+ });
+ submitbutton.click();
+ await frame2LoadPromise;
+
+ assert_false(window.frame1Navigated, 'frame1 should not be navigated by the form submission.');
+}, 'default submit action should supersede onclick submit() for cross-origin iframes');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit.html
new file mode 100644
index 0000000000..b6ea0cefab
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- The onclick submit() should get superseded by the default
+ action submit(), which isn't preventDefaulted by onclick here.
+ This is per the Form Submission Algorithm [1], step 22.3, which
+ says that new planned navigations replace old planned navigations.
+ [1] https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm
+ -->
+
+<label for=frame1 style="display:block">This frame should stay blank</label>
+<iframe name=frame1 id=frame1></iframe>
+<label for=frame2 style="display:block">This frame should navigate (to 404)</label>
+<iframe name=frame2 id=frame2></iframe>
+<form id="form1" target="frame1" action="nonexistent.html">
+ <input type=hidden name=navigated value=1>
+ <input id=submitbutton type=submit>
+</form>
+
+<script>
+let frame1 = document.getElementById('frame1');
+let frame2 = document.getElementById('frame2');
+let form1 = document.getElementById('form1');
+let submitbutton = document.getElementById('submitbutton');
+
+async_test(t => {
+ window.addEventListener('load', () => {
+ frame1.addEventListener('load', t.step_func_done(() => {
+ assert_unreached("Frame1 should not get navigated by this test.");
+ }));
+ frame2.addEventListener('load', t.step_func_done(() => {
+ let params = (new URL(frame2.contentWindow.location)).searchParams;
+ let wasNavigated = !!params.get("navigated");
+ assert_true(wasNavigated)
+ }));
+ form1.addEventListener('click', t.step_func(() => {
+ form1.submit();
+ form1.target='frame2';
+
+ }));
+ submitbutton.click();
+ });
+}, 'default submit action should supersede onclick submit()');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-echo.py b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-echo.py
new file mode 100644
index 0000000000..72f1f51ce5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-echo.py
@@ -0,0 +1,7 @@
+def main(request, response):
+ bytes = bytearray(request.raw_input.read())
+ bytes_string = b" ".join(b"%02x" % b for b in bytes)
+ return (
+ [(b"Content-Type", b"text/plain")],
+ bytes_string
+ )
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submission-algorithm.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submission-algorithm.html
new file mode 100644
index 0000000000..0f0fd4ede0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submission-algorithm.html
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/targetted-form.js"></script>
+<body>
+<script>
+test(() => {
+ let form = populateForm('<input name=n10 value=v10>');
+ let counter = 0;
+ form.addEventListener('formdata', e => {
+ ++counter;
+ form.submit();
+ });
+ form.submit();
+ assert_equals(counter, 1);
+ new FormData(form);
+ assert_equals(counter, 2);
+}, 'If constructing entry list flag of form is true, then return');
+
+test(() => {
+ let form = populateForm('<input><input type=submit>');
+ let submitter1 = form.querySelector('input[type=submit]');
+ let valid = form.elements[0];
+ let counter = 0;
+ valid.oninvalid = () => {
+ ++counter;
+ };
+ form.onsubmit = () => {
+ valid.required = true;
+ submitter1.dispatchEvent(new MouseEvent("click"));
+ };
+ submitter1.dispatchEvent(new MouseEvent("click"));
+ assert_equals(counter, 0);
+}, "If firing submission events flag of form is true, then return");
+
+test(() => {
+ let form = populateForm('<input required><input type=submit><button type=submit></button>');
+ let submitter1 = form.querySelector('input[type=submit]');
+ let submitter2 = form.querySelector('button[type=submit]');
+ let invalid = form.querySelector('[required]');
+ let counter = 0;
+ invalid.oninvalid = () => {
+ ++counter;
+ // Needs to click different one because click() has reentrancy protection.
+ submitter2.click();
+ };
+ submitter1.click();
+ assert_equals(counter, 1);
+}, "If form's firing submission events is true, then return; 'invalid' event");
+
+promise_test(async () => {
+ let form = populateForm('<input type=submit name=n value=i><button type=submit name=n value=b>');
+ let submitter1 = form.querySelector('input[type=submit]');
+ let submitter2 = form.querySelector('button[type=submit]');
+ let iframe = form.previousSibling;
+ form.onsubmit = () => {
+ // Needs to click different one because click() has reentrancy protection.
+ submitter2.click();
+ };
+ submitter1.click();
+ // We actually submit the form in order to check which 'click()' submits it.
+ await loadPromise(iframe);
+ assert_not_equals(iframe.contentWindow.location.search.indexOf('n=i'), -1);
+}, "If form's firing submission events is true, then return; 'submit' event");
+
+promise_test(async () => {
+ let form = populateForm('<button type=submit></button><input name=n1 value=submit type=submit>');
+ let iframe = form.previousSibling;
+ let submitter = form.querySelector('input[type=submit]');
+ let event;
+ form.addEventListener('submit', e => { event = e; });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, submitter);
+ assert_true(event instanceof SubmitEvent);
+}, 'firing an event named submit; clicking a submit button');
+
+promise_test(async () => {
+ let form = populateForm('<input type=image name=n1>');
+ let iframe = form.previousSibling;
+ let submitter = form.querySelector('input[type=image]');
+ let event;
+ form.addEventListener('submit', e => { event = e; });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, submitter);
+ assert_true(event instanceof SubmitEvent);
+}, 'firing an event named submit; clicking an image button');
+
+promise_test(async () => {
+ let form = populateForm('');
+ let iframe = form.previousSibling;
+ let event;
+ form.addEventListener('submit', e => { event = e; });
+ form.requestSubmit();
+ await loadPromise(iframe);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, null);
+ assert_true(event instanceof SubmitEvent);
+}, 'firing an event named submit; form.requestSubmit()');
+
+promise_test(async () => {
+ let form = populateForm('');
+ let iframe = form.previousSibling;
+ let event;
+ form.addEventListener('submit', e => { event = e; });
+ form.requestSubmit(null);
+ await loadPromise(iframe);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, null);
+ assert_true(event instanceof SubmitEvent);
+}, 'firing an event named submit; form.requestSubmit(null)');
+
+promise_test(async () => {
+ let form = populateForm('<input type=submit><button type=submit></button>');
+ let iframe = form.previousSibling;
+ let submitter = form.querySelector('button');
+ let event;
+ form.addEventListener('submit', e => { event = e; });
+ form.requestSubmit(submitter);
+ await loadPromise(iframe);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, submitter);
+ assert_true(event instanceof SubmitEvent);
+}, 'firing an event named submit; form.requestSubmit(submitter)');
+
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1>');
+ form.onformdata = (e) => { e.target.remove(); };
+ let wasLoaded = false;
+ let iframe = form.previousSibling;
+ // Request to load '/common/dummy.xhtml', and immediately submit the form to
+ // the same frame. If the form submission is aborted, the first request
+ // will be completed.
+ iframe.addEventListener("load", () => {
+ // This may be complicated by loads of the initial about:blank;
+ // we need to ignore them and only look at a load that isn't about:blank.
+ if (iframe.contentWindow.location == "about:blank") { return; }
+ wasLoaded = true;
+ });
+ iframe.src = '/common/dummy.xhtml';
+ assert_false(wasLoaded, 'Make sure the first loading is ongoing.');
+ form.submit();
+ await loadPromise(iframe);
+ assert_true(iframe.contentWindow.location.search.indexOf('n1=v1') == -1);
+}, 'Cannot navigate (after constructing the entry list)');
+
+promise_test(async () => {
+ let form = populateForm('<input type=submit>');
+ let iframe = form.previousSibling;
+ let event;
+ form.submit();
+ await loadPromise(iframe);
+ assert_true(iframe.contentWindow.location.href.includes("?"));
+}, 'Submission URL should always have a non-null query part');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submit-iframe-then-location-navigate.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submit-iframe-then-location-navigate.html
new file mode 100644
index 0000000000..ad2943e2bb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submit-iframe-then-location-navigate.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- This behavior is not explicitly specified. -->
+
+<iframe id=myframe name=framename></iframe>
+<form id=myform target=framename action="resources/form.html"></form>
+
+<script>
+async_test(t => {
+ myframe.onload = t.step_func_done(() => {
+ assert_equals(
+ myframe.contentDocument.location.pathname,
+ '/html/semantics/forms/form-submission-0/resources/location.html');
+ });
+ myform.submit();
+ myframe.contentDocument.location = 'resources/location.html';
+}, 'Verifies that location navigations take precedence when following form submissions.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/getactionurl.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/getactionurl.html
new file mode 100644
index 0000000000..2e21828ae6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/getactionurl.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe src="resources/getactionurl-iframe.html"></iframe>
+<script>
+async_test(t => {
+ window.onmessage = t.step_func_done(event => assert_equals(event.data, 'PASS'));
+}, `Verifies that a form element's target can be a data url.`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/historical.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/historical.window.js
new file mode 100644
index 0000000000..fcc47d90f6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/historical.window.js
@@ -0,0 +1,19 @@
+// META: script=./resources/targetted-form.js
+
+test(t => {
+ const form = populateForm('<input required><input type=submit>');
+ t.add_cleanup(() => {
+ form.previousElementSibling.remove();
+ form.remove();
+ });
+ const submitter = form.querySelector('input[type=submit]');
+ let invalid = form.querySelector('[required]');
+ let targets = [];
+ const listener = e => targets.push(e.target.localName);
+ form.addEventListener("invalid", t.step_func(listener));
+ form.oninvalid = t.step_func(listener);
+ invalid.addEventListener("invalid", t.step_func(listener));
+ invalid.oninvalid = t.step_func(listener);
+ submitter.click();
+ assert_array_equals(targets, ["input", "input"]);
+}, "invalid event is only supported for form controls");
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/implicit-submission.optional.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/implicit-submission.optional.html
new file mode 100644
index 0000000000..379a4396ac
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/implicit-submission.optional.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/C/#implicit-submission">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="./resources/targetted-form.js"></script>
+<body>
+<script>
+// This test file is "optional" because triggering implicit submission by
+// "Enter" key is not standardized.
+
+const ENTER = '\uE007';
+
+promise_test(async () => {
+ let form = populateForm('<input name=text value=abc><input name=submitButton type=submit>');
+ let event;
+ form.text.focus();
+ form.addEventListener('submit', e => { event = e; });
+ await test_driver.send_keys(form.text, ENTER);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, form.submitButton);
+ assert_true(event instanceof SubmitEvent);
+}, 'Submit event with a submit button');
+
+promise_test(async () => {
+ let form = populateForm('<input name=text value=abc>');
+ let event;
+ form.text.focus();
+ form.addEventListener('submit', e => { event = e; });
+ await test_driver.send_keys(form.text, ENTER);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, null);
+ assert_true(event instanceof SubmitEvent);
+}, 'Submit event with no submit button');
+
+promise_test(async (test) => {
+ let form = populateForm('<input name=text value=abc><input name=submitButton type=submit disabled>');
+ form.text.focus();
+ form.addEventListener('submit', test.unreached_func('submit event should not be dispatched'));
+ await test_driver.send_keys(form.text, ENTER);
+}, 'Submit event with disabled submit button');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-form-submit.tentative.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-form-submit.tentative.html
new file mode 100644
index 0000000000..f476308b7d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-form-submit.tentative.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- This behavior is not explicitly specified. -->
+
+<iframe id=frameid name=framename></iframe>
+<form id=formid target=framename action="resources/form.html"></form>
+
+<script>
+async_test(t => {
+ frameid.src = 'resources/jsurl-form-submit-iframe.html';
+
+ frameid.onload = t.step_func(() => {
+ assert_equals(
+ frameid.contentDocument.location.pathname,
+ '/html/semantics/forms/form-submission-0/resources/jsurl-form-submit-iframe.html');
+
+ frameid.onload = t.step_func_done(() => {
+ assert_equals(
+ frameid.contentDocument.location.pathname,
+ '/html/semantics/forms/form-submission-0/resources/form.html');
+ });
+
+ frameid.contentDocument.getElementById('anchorid').click();
+ });
+
+}, `Verifies that form submissions scheduled inside javascript: urls take precedence over the javascript: url's return value.`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-navigation-then-form-submit.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-navigation-then-form-submit.html
new file mode 100644
index 0000000000..93a4ea6004
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-navigation-then-form-submit.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+
+<!-- The expected behavior of this test is not explicitly specified. -->
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+async_test(t => {
+ window.onload = t.step_func(() => {
+ const iframe = document.createElement('iframe');
+ iframe.name = 'myframe';
+
+ iframe.onload = t.step_func_done(() => {
+ assert_equals(iframe.contentDocument.location.pathname, '/formaction.html');
+ });
+
+ const form = document.createElement('form');
+ form.target = iframe.name;
+ form.action = '/formaction.html';
+ document.body.appendChild(form);
+
+ iframe.src = 'javascript:false';
+ document.body.appendChild(iframe);
+ form.submit();
+ });
+}, 'Verifies that form submissions cancel javascript navigations to prevent duplicate load events.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/multipart-formdata.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/multipart-formdata.window.js
new file mode 100644
index 0000000000..d21eec9eeb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/multipart-formdata.window.js
@@ -0,0 +1,346 @@
+// META: script=enctypes-helper.js
+
+// Form submissions in multipart/form-data are also tested in
+// /FileAPI/file/send-file*
+
+// The `expected` property of objects passed to `formTest` must be an object
+// with `name`, `value` and optionally `filename` properties, which represent
+// the corresponding data in a multipart/form-data part.
+const formTest = formSubmissionTemplate(
+ "multipart/form-data",
+ ({ name, filename, value }, serialized) => {
+ let headers;
+ if (filename === undefined) {
+ headers = [`Content-Disposition: form-data; name="${name}"`];
+ } else {
+ headers = [
+ `Content-Disposition: form-data; name="${name}"; filename="${filename}"`,
+ "Content-Type: text/plain",
+ ];
+ }
+
+ const boundary = serialized.split("\r\n")[0];
+
+ return [
+ boundary,
+ ...headers,
+ "",
+ value,
+ boundary + "--",
+ "",
+ ].join("\r\n");
+ },
+);
+
+formTest({
+ name: "basic",
+ value: "test",
+ expected: {
+ name: "basic",
+ value: "test",
+ },
+ description: "Basic test",
+});
+
+formTest({
+ name: "basic",
+ value: new File([], "file-test.txt", { type: "text/plain" }),
+ expected: {
+ name: "basic",
+ filename: "file-test.txt",
+ value: "",
+ },
+ description: "Basic File test",
+});
+
+formTest({
+ name: "a\0b",
+ value: "c",
+ expected: {
+ name: "a\0b",
+ value: "c",
+ },
+ description: "0x00 in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\0c",
+ expected: {
+ name: "a",
+ value: "b\0c",
+ },
+ description: "0x00 in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\0c", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b\0c",
+ value: "",
+ },
+ description: "0x00 in filename",
+});
+
+formTest({
+ name: "a\nb",
+ value: "c",
+ expected: {
+ name: "a%0D%0Ab",
+ value: "c",
+ },
+ description: "\\n in name",
+});
+
+formTest({
+ name: "a\rb",
+ value: "c",
+ expected: {
+ name: "a%0D%0Ab",
+ value: "c",
+ },
+ description: "\\r in name",
+});
+
+formTest({
+ name: "a\r\nb",
+ value: "c",
+ expected: {
+ name: "a%0D%0Ab",
+ value: "c",
+ },
+ description: "\\r\\n in name",
+});
+
+formTest({
+ name: "a\n\rb",
+ value: "c",
+ expected: {
+ name: "a%0D%0A%0D%0Ab",
+ value: "c",
+ },
+ description: "\\n\\r in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\nc",
+ expected: {
+ name: "a",
+ value: "b\r\nc",
+ },
+ description: "\\n in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\rc",
+ expected: {
+ name: "a",
+ value: "b\r\nc",
+ },
+ description: "\\r in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\r\nc",
+ expected: {
+ name: "a",
+ value: "b\r\nc",
+ },
+ description: "\\r\\n in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\n\rc",
+ expected: {
+ name: "a",
+ value: "b\r\n\r\nc",
+ },
+ description: "\\n\\r in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\nc", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b%0Ac",
+ value: "",
+ },
+ description: "\\n in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\rc", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b%0Dc",
+ value: "",
+ },
+ description: "\\r in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\r\nc", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b%0D%0Ac",
+ value: "",
+ },
+ description: "\\r\\n in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\n\rc", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b%0A%0Dc",
+ value: "",
+ },
+ description: "\\n\\r in filename",
+});
+
+formTest({
+ name: 'a"b',
+ value: "c",
+ expected: {
+ name: "a%22b",
+ value: "c",
+ },
+ description: "double quote in name",
+});
+
+formTest({
+ name: "a",
+ value: 'b"c',
+ expected: {
+ name: "a",
+ value: 'b"c',
+ },
+ description: "double quote in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], 'b"c', { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b%22c",
+ value: "",
+ },
+ description: "double quote in filename",
+});
+
+formTest({
+ name: "a'b",
+ value: "c",
+ expected: {
+ name: "a'b",
+ value: "c",
+ },
+ description: "single quote in name",
+});
+
+formTest({
+ name: "a",
+ value: "b'c",
+ expected: {
+ name: "a",
+ value: "b'c",
+ },
+ description: "single quote in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b'c", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b'c",
+ value: "",
+ },
+ description: "single quote in filename",
+});
+
+formTest({
+ name: "a\\b",
+ value: "c",
+ expected: {
+ name: "a\\b",
+ value: "c",
+ },
+ description: "backslash in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\\c",
+ expected: {
+ name: "a",
+ value: "b\\c",
+ },
+ description: "backslash in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\\c", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b\\c",
+ value: "",
+ },
+ description: "backslash in filename",
+});
+
+formTest({
+ name: "áb",
+ value: "ç",
+ expected: {
+ name: "\xC3\xA1b",
+ value: "\xC3\xA7",
+ },
+ description: "non-ASCII in name and value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "ə.txt", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "\xC9\x99.txt",
+ value: "",
+ },
+ description: "non-ASCII in filename",
+});
+
+formTest({
+ name: "aəb",
+ value: "c\uFFFDd",
+ formEncoding: "windows-1252",
+ expected: {
+ name: "a&#601;b",
+ value: "c&#65533;d",
+ },
+ description: "characters not in encoding in name and value",
+});
+
+formTest({
+ name: "á",
+ value: new File([], "💩", { type: "text/plain" }),
+ formEncoding: "windows-1252",
+ expected: {
+ name: "\xE1",
+ filename: "&#128169;",
+ value: "",
+ },
+ description: "character not in encoding in filename",
+});
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/newline-normalization.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/newline-normalization.html
new file mode 100644
index 0000000000..2c83c5a1e9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/newline-normalization.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>
+ Constructing the entry list shouldn't perform newline normalization
+ </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ function createForm(testCase, name, value) {
+ const form = document.createElement("form");
+ const input = document.createElement("input");
+ input.type = "hidden";
+ input.name = name;
+ input.value = value;
+ form.appendChild(input);
+ document.body.appendChild(form);
+ testCase.add_cleanup(() => {
+ document.body.removeChild(form);
+ });
+ return form;
+ }
+
+ function createFormWithFile(testCase, name, filename) {
+ const form = document.createElement("form");
+ const input = document.createElement("input");
+ input.type = "file";
+ input.name = name;
+ const dataTransfer = new DataTransfer();
+ dataTransfer.items.add(new File([], filename, { type: "text/plain" }));
+ input.files = dataTransfer.files;
+ form.appendChild(input);
+ document.body.appendChild(form);
+ testCase.add_cleanup(() => {
+ document.body.removeChild(form);
+ });
+ return form;
+ }
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a", "b\nc"));
+ assert_equals(formData.get("a"), "b\nc");
+ }, document.title + ": \\n in the value");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a", "b\rc"));
+ assert_equals(formData.get("a"), "b\rc");
+ }, document.title + ": \\r in the value");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a", "b\r\nc"));
+ assert_equals(formData.get("a"), "b\r\nc");
+ }, document.title + ": \\r\\n in the value");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a", "b\n\rc"));
+ assert_equals(formData.get("a"), "b\n\rc");
+ }, document.title + ": \\n\\r in the value");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a\nb", "c"));
+ assert_equals([...formData][0][0], "a\nb");
+ }, document.title + ": \\n in the name");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a\rb", "c"));
+ assert_equals([...formData][0][0], "a\rb");
+ }, document.title + ": \\r in the name");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a\r\nb", "c"));
+ assert_equals([...formData][0][0], "a\r\nb");
+ }, document.title + ": \\r\\n in the name");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a\n\rb", "c"));
+ assert_equals([...formData][0][0], "a\n\rb");
+ }, document.title + ": \\n\\r in the name");
+
+ test((testCase) => {
+ const formData = new FormData(
+ createFormWithFile(testCase, "a", "b\nc")
+ );
+ assert_equals(formData.get("a").name, "b\nc");
+ }, document.title + ": \\n in the filename");
+
+ test((testCase) => {
+ const formData = new FormData(
+ createFormWithFile(testCase, "a", "b\rc")
+ );
+ assert_equals(formData.get("a").name, "b\rc");
+ }, document.title + ": \\r in the filename");
+
+ test((testCase) => {
+ const formData = new FormData(
+ createFormWithFile(testCase, "a", "b\r\nc")
+ );
+ assert_equals(formData.get("a").name, "b\r\nc");
+ }, document.title + ": \\r\\n in the filename");
+
+ test((testCase) => {
+ const formData = new FormData(
+ createFormWithFile(testCase, "a", "b\n\rc")
+ );
+ assert_equals(formData.get("a").name, "b\n\rc");
+ }, document.title + ": \\n\\r in the filename");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html
new file mode 100644
index 0000000000..6b50bf599b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html
@@ -0,0 +1,15 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<iframe id="i" src="about:blank"></iframe>
+<script>
+async_test(t => {
+ var form = i.contentDocument.createElement('form');
+ form.action = '/common/blank.html';
+ i.contentDocument.body.appendChild(form);
+ i.onload = t.step_func_done(() => {});
+ form.submit();
+ new Document().prepend(form);
+});
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/request-submit-activation.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/request-submit-activation.html
new file mode 100644
index 0000000000..0d1e54daf3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/request-submit-activation.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/targetted-form.js"></script>
+<body>
+<script>
+promise_test(async () => {
+ let form = populateForm('<input type=submit name=n1 value=v1><button type=submit name=n2 value=v2></button>');
+ let submitter = form.querySelector('button');
+ let iframe = form.previousSibling;
+ let event;
+ form.requestSubmit(submitter);
+ await loadPromise(iframe);
+ assert_true(iframe.contentWindow.location.search.indexOf('n1=v1') == -1, "n1=v1");
+ assert_true(iframe.contentWindow.location.search.indexOf('n2=v2') > 0), "n2=v2";
+}, 'Test activation of submitter for requestSubmit');
+
+promise_test(async () => {
+ let form = populateForm('<input type=submit name=n1 value=v1><button type=submit name=n2 value=v2></button>');
+ let submitter = form.querySelector('input');
+ let iframe = form.previousSibling;
+ let event;
+ form.requestSubmit(submitter);
+ await loadPromise(iframe);
+ assert_true(iframe.contentWindow.location.search.indexOf('n1=v1') > 0, "n1=v1");
+ assert_true(iframe.contentWindow.location.search.indexOf('n2=v2') == -1), "n2=v2";
+}, 'Test activation of submitter for requestSubmit 2');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/file-submission.py b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/file-submission.py
new file mode 100644
index 0000000000..89cd182add
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/file-submission.py
@@ -0,0 +1,10 @@
+import json
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ headers = [(b"Content-Type", b"text/html")]
+ testinput = request.POST.first(b"testinput")
+ value = isomorphic_decode(testinput.value)
+ body = u"<script>parent.postMessage(" + json.dumps(value) + u", '*');</script>"
+ return headers, body
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form-submission.py b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form-submission.py
new file mode 100644
index 0000000000..f0c2d4cf61
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form-submission.py
@@ -0,0 +1,12 @@
+def main(request, response):
+ if request.headers.get(b'Content-Type') == b'application/x-www-form-urlencoded':
+ result = request.body == b'foo=bara'
+ elif request.headers.get(b'Content-Type') == b'text/plain':
+ result = request.body == b'qux=baz\r\n'
+ else:
+ result = request.POST.first(b'foo') == b'bar'
+
+ result = result and request.url_parts.query == u'query=1'
+
+ return ([(b"Content-Type", b"text/plain")],
+ b"OK" if result else b"FAIL")
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form.html
new file mode 100644
index 0000000000..8b16672d6b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+ form.html
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/getactionurl-iframe.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/getactionurl-iframe.html
new file mode 100644
index 0000000000..116371a995
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/getactionurl-iframe.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+<script>
+ const form = document.createElement('form');
+ document.body.appendChild(form);
+ form.action = `data:text/html,
+ <!DOCTYPE html>
+ <body>
+ <script>
+ window.top.postMessage('PASS', '*');
+ <\/script>
+ `;
+ form.submit();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/jsurl-form-submit-iframe.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/jsurl-form-submit-iframe.html
new file mode 100644
index 0000000000..00a1eefd0f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/jsurl-form-submit-iframe.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<form id=formid action="form.html"></form>
+<a id=anchorid href="javascript:jsurl()">anchor</a>
+
+<script>
+function jsurl() {
+ formid.submit();
+ return 'jsurl return value';
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/location.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/location.html
new file mode 100644
index 0000000000..6724189eff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/location.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+ location.html
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/targetted-form.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/targetted-form.js
new file mode 100644
index 0000000000..52482c859f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/targetted-form.js
@@ -0,0 +1,38 @@
+let frameCounter = 0;
+
+function populateForm(optionalContentHtml) {
+ if (!optionalContentHtml)
+ optionalContentHtml = '';
+ const frameName = "form-test-target-" + frameCounter++;
+ document.body.insertAdjacentHTML(
+ 'afterbegin',
+ `<iframe name="${frameName}"></iframe>` +
+ `<form action="/common/blank.html" target="` +
+ `${frameName}">${optionalContentHtml}</form>`);
+ return document.getElementsByName(frameName)[0].nextSibling;
+}
+
+function submitPromise(form, iframe) {
+ return new Promise((resolve, reject) => {
+ iframe.onload = () => resolve(iframe.contentWindow.location.search);
+ iframe.onerror = () => reject(new Error('iframe onerror fired'));
+ form.submit();
+ });
+}
+
+function loadPromise(iframe) {
+ return new Promise((resolve, reject) => {
+ iframe.onload = function() {
+ // The initial about:blank load event can be fired before the form navigation occurs.
+ // See https://github.com/whatwg/html/issues/490 for more information.
+ if (iframe.contentWindow.location == "about:blank") { return; }
+ resolve();
+ };
+ iframe.onerror = () => reject(new Error('iframe onerror fired'));
+ });
+}
+
+function getParamValue(iframe, paramName) {
+ let params = (new URL(iframe.contentWindow.location)).searchParams;
+ return params.get(paramName);
+}
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/submission-checks.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/submission-checks.window.js
new file mode 100644
index 0000000000..e242ce830a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/submission-checks.window.js
@@ -0,0 +1,62 @@
+async_test(t => {
+ const frame = document.createElement("frame"),
+ form = document.createElement("form");
+ t.add_cleanup(() => frame.remove());
+ form.action = "/common/blank.html";
+ form.target = "doesnotmattertwobits";
+ frame.name = "doesnotmattertwobits";
+ document.body.appendChild(frame);
+ frame.onload = t.step_func(() => {
+ if(frame.contentWindow.location.href === "about:blank")
+ return;
+ assert_unreached();
+ });
+ form.submit();
+ t.step_timeout(() => {
+ assert_equals(frame.contentWindow.location.href, "about:blank");
+ t.done();
+ }, 500);
+}, "<form> not connected to a document cannot navigate");
+
+async_test(t => {
+ const frame = document.createElement("frame"),
+ form = document.createElement("form");
+ t.add_cleanup(() => frame.remove());
+ form.action = "/common/blank.html";
+ form.target = "doesnotmattertwoqbits";
+ form.onsubmit = t.step_func(() => form.remove());
+ frame.name = "doesnotmattertwoqbits";
+ document.body.appendChild(frame);
+ document.body.appendChild(form);
+ frame.onload = t.step_func(() => {
+ if(frame.contentWindow.location.href === "about:blank")
+ return;
+ assert_unreached();
+ });
+ const submit = form.appendChild(document.createElement("input"));
+ submit.type = "submit"
+ submit.click();
+ t.step_timeout(() => {
+ assert_equals(frame.contentWindow.location.href, "about:blank");
+ t.done();
+ }, 500);
+}, "<form> not connected to a document after submit event cannot navigate");
+
+async_test(t => {
+ const frame = document.createElement("frame"),
+ form = document.createElement("form");
+ t.add_cleanup(() => frame.remove());
+ form.action = "/";
+ document.body.appendChild(frame);
+ frame.contentDocument.body.appendChild(form);
+ frame.onload = t.step_func(() => {
+ if(frame.contentWindow.location.href === "about:blank")
+ return;
+ form.submit();
+ t.step_timeout(() => {
+ assert_equals(frame.contentWindow.location.pathname, "/common/blank.html");
+ t.done();
+ }, 500)
+ });
+ frame.src = "/common/blank.html";
+}, "<form> in a navigated document cannot navigate");
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-entity-body.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-entity-body.html
new file mode 100644
index 0000000000..be9c5f0696
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-entity-body.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var simple_tests = [
+ {
+ name: "form submission from form should navigate to url with x-www-form-urlencoded",
+ input: "<input name=foo value=bara>",
+ enctype: "application/x-www-form-urlencoded",
+ submitelement: "",
+ submitaction: function(doc) { doc.getElementById("testform").submit(); }
+ },
+ {
+ name: "form submission from form should navigate to url with multipart/form-data",
+ input: "<textarea name=foo>bar</textarea>",
+ enctype: "multipart/form-data",
+ submitelement: "",
+ submitaction: function(doc) { doc.getElementById("testform").submit(); }
+ },
+ {
+ name: "form submission from form should navigate to url with text/plain",
+ input: "<textarea name=qux>baz</textarea>",
+ enctype: "text/plain",
+ submitelement: "",
+ submitaction: function(doc) { doc.getElementById("testform").submit(); }
+ },
+ {
+ name: "form submission from button should navigate to url with x-www-form-urlencoded",
+ input: "<input name=foo value=bara>",
+ enctype: "application/x-www-form-urlencoded",
+ submitelement: "<button id=buttonsubmit type=\"submit\">Submit</button>",
+ submitaction: function(doc) { doc.getElementById("buttonsubmit").click(); }
+ },
+ {
+ name: "form submission from button should navigate to url with multipart/form-data",
+ input: "<textarea name=foo>bar</textarea>",
+ enctype: "multipart/form-data",
+ submitelement: "<button id=buttonsubmit type=\"submit\">Submit</button>",
+ submitaction: function(doc) { doc.getElementById("buttonsubmit").click(); }
+ },
+ {
+ name: "form submission from button should navigate to url with text/plain",
+ input: "<textarea name=qux>baz</textarea>",
+ enctype: "text/plain",
+ submitelement: "<button id=buttonsubmit type=\"submit\">Submit</button>",
+ submitaction: function(doc) { doc.getElementById("buttonsubmit").click(); }
+ },
+ {
+ name: "form submission from input should navigate to url with x-www-form-urlencoded",
+ input: "<input name=foo value=bara>",
+ enctype: "application/x-www-form-urlencoded",
+ submitelement: "<input id=inputsubmit type=\"submit\">Submit</input>",
+ submitaction: function(doc) { doc.getElementById("inputsubmit").click(); }
+ },
+ {
+ name: "form submission from input should navigate to url with multipart/form-data",
+ input: "<textarea name=foo>bar</textarea>",
+ enctype: "multipart/form-data",
+ submitelement: "<input id=inputsubmit type=\"submit\">Submit</input>",
+ submitaction: function(doc) { doc.getElementById("inputsubmit").click(); }
+ },
+ {
+ name: "form submission from input should navigate to url with text/plain",
+ input: "<input name=qux value=baz>",
+ enctype: "text/plain",
+ submitelement: "<input id=inputsubmit type=\"submit\">Submit</input>",
+ submitaction: function(doc) { doc.getElementById("inputsubmit").click(); }
+ },
+ {
+ name: "form submission from submit input should contain submit button value",
+ input: "<button type=submit name=notclicked value=nope>not clicked</button>",
+ enctype: "application/x-www-form-urlencoded",
+ submitelement: "<button id=inputsubmit type=\"submit\" name=foo value=bara>Submit</button>",
+ submitaction: function(doc) { doc.getElementById("inputsubmit").click(); }
+ }
+,
+ {
+ name: "form submission from submit button should contain submit button value",
+ input: "<input type=submit name=notclicked value=nope/>",
+ enctype: "application/x-www-form-urlencoded",
+ submitelement: "<input id=inputsubmit type=\"submit\" name=foo value=bara >",
+ submitaction: function(doc) { doc.getElementById("inputsubmit").click(); }
+ }
+];
+simple_tests.forEach(function(test_obj) {
+ test_obj.test = async_test(test_obj.name);
+});
+function run_simple_test() {
+ if (simple_tests.length == 0) {
+ return;
+ }
+ var test_obj = simple_tests.pop();
+ var t = test_obj.test;
+ var testframe = document.getElementById("testframe");
+ var testdocument = testframe.contentWindow.document;
+ testdocument.body.innerHTML =
+ "<form id=testform method=post action=\"/html/semantics/forms/form-submission-0/resources/form-submission.py?query=1\" enctype=\"" + test_obj.enctype + "\">" +
+ test_obj.input +
+ test_obj.submitelement +
+ "</form>";
+ testframe.onload = function() {
+ t.step(function (){
+ var response = testframe.contentDocument.documentElement.textContent;
+ assert_equals(response, "OK");
+ });
+ t.done();
+ run_simple_test();
+ };
+ test_obj.submitaction(testdocument);
+}
+</script>
+<iframe id=testframe src="/common/blank.html" onload="run_simple_test();"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-file.sub.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-file.sub.html
new file mode 100644
index 0000000000..aab60ba949
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-file.sub.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<iframe id=testframe name=testframe></iframe>
+<form id=testform method=post action="//{{domains[www1]}}:{{location[port]}}/html/semantics/forms/form-submission-0/resources/file-submission.py" target=testframe enctype="multipart/form-data">
+<input name=testinput id=testinput type=file>
+</form>
+<script>
+async_test(t => {
+ const dataTransfer = new DataTransfer();
+ dataTransfer.items.add(new File(["foobar"], "name"));
+ assert_equals(1, dataTransfer.files.length);
+
+ testinput.files = dataTransfer.files;
+ testform.submit();
+
+ onmessage = t.step_func(e => {
+ if (e.source !== testframe) return;
+ assert_equals(e.data, "foobar");
+ t.done();
+ });
+}, 'Posting a File');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/text-plain.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/text-plain.window.js
new file mode 100644
index 0000000000..508f9884bf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/text-plain.window.js
@@ -0,0 +1,215 @@
+// META: script=enctypes-helper.js
+
+const formTest = formSubmissionTemplate("text/plain");
+
+formTest({
+ name: "basic",
+ value: "test",
+ expected: "basic=test\r\n",
+ description: "Basic test",
+});
+
+formTest({
+ name: "basic",
+ value: new File([], "file-test.txt"),
+ expected: "basic=file-test.txt\r\n",
+ description: "Basic File test",
+});
+
+formTest({
+ name: "a\0b",
+ value: "c",
+ expected: "a\0b=c\r\n",
+ description: "0x00 in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\0c",
+ expected: "a=b\0c\r\n",
+ description: "0x00 in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\0c"),
+ expected: "a=b\0c\r\n",
+ description: "0x00 in filename",
+});
+
+formTest({
+ name: "a\nb",
+ value: "c",
+ expected: "a\r\nb=c\r\n",
+ description: "\\n in name",
+});
+
+formTest({
+ name: "a\rb",
+ value: "c",
+ expected: "a\r\nb=c\r\n",
+ description: "\\r in name",
+});
+
+formTest({
+ name: "a\r\nb",
+ value: "c",
+ expected: "a\r\nb=c\r\n",
+ description: "\\r\\n in name",
+});
+
+formTest({
+ name: "a\n\rb",
+ value: "c",
+ expected: "a\r\n\r\nb=c\r\n",
+ description: "\\n\\r in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\nc",
+ expected: "a=b\r\nc\r\n",
+ description: "\\n in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\rc",
+ expected: "a=b\r\nc\r\n",
+ description: "\\r in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\r\nc",
+ expected: "a=b\r\nc\r\n",
+ description: "\\r\\n in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\n\rc",
+ expected: "a=b\r\n\r\nc\r\n",
+ description: "\\n\\r in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\nc"),
+ expected: "a=b\r\nc\r\n",
+ description: "\\n in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\rc"),
+ expected: "a=b\r\nc\r\n",
+ description: "\\r in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\r\nc"),
+ expected: "a=b\r\nc\r\n",
+ description: "\\r\\n in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\n\rc"),
+ expected: "a=b\r\n\r\nc\r\n",
+ description: "\\n\\r in filename",
+});
+
+formTest({
+ name: 'a"b',
+ value: "c",
+ expected: 'a"b=c\r\n',
+ description: "double quote in name",
+});
+
+formTest({
+ name: "a",
+ value: 'b"c',
+ expected: 'a=b"c\r\n',
+ description: "double quote in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], 'b"c'),
+ expected: 'a=b"c\r\n',
+ description: "double quote in filename",
+});
+
+formTest({
+ name: "a'b",
+ value: "c",
+ expected: "a'b=c\r\n",
+ description: "single quote in name",
+});
+
+formTest({
+ name: "a",
+ value: "b'c",
+ expected: "a=b'c\r\n",
+ description: "single quote in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b'c"),
+ expected: "a=b'c\r\n",
+ description: "single quote in filename",
+});
+
+formTest({
+ name: "a\\b",
+ value: "c",
+ expected: "a\\b=c\r\n",
+ description: "backslash in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\\c",
+ expected: "a=b\\c\r\n",
+ description: "backslash in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\\c"),
+ expected: "a=b\\c\r\n",
+ description: "backslash in filename",
+});
+
+formTest({
+ name: "áb",
+ value: "ç",
+ expected: "\xC3\xA1b=\xC3\xA7\r\n",
+ description: "non-ASCII in name and value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "ə.txt"),
+ expected: "a=\xC9\x99.txt\r\n",
+ description: "non-ASCII in filename",
+});
+
+formTest({
+ name: "aəb",
+ value: "c\uFFFDd",
+ formEncoding: "windows-1252",
+ expected: "a&#601;b=c&#65533;d\r\n",
+ description: "characters not in encoding in name and value",
+});
+
+formTest({
+ name: "á",
+ value: new File([], "💩"),
+ formEncoding: "windows-1252",
+ expected: "\xE1=&#128169;\r\n",
+ description: "character not in encoding in filename",
+});
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/url-encoded.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/url-encoded.html
new file mode 100644
index 0000000000..d05364387e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/url-encoded.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id=testframe src="/common/blank.html"></iframe>
+<script>
+var simple_tests = [
+ {
+ name: "text.simple",
+ input: "<input name=foo value=bara>",
+ output: "foo=bara"
+ },
+ {
+ name: "textarea.simple",
+ input: "<textarea name=foo>bar</textarea>",
+ output: "foo=bar"
+ },
+ {
+ name: "nokeygen.simple",
+ input: "<input name=foo value=barb><keygen>",
+ output: "foo=barb"
+ }
+];
+simple_tests.forEach(function(test_obj) {
+ test_obj.test = async_test(test_obj.name);
+});
+function run_simple_test() {
+ if (simple_tests.length == 0) {
+ return;
+ }
+ test_obj = simple_tests.pop();
+ var t = test_obj.test;
+ var testframe = document.getElementById("testframe");
+ var testdocument = testframe.contentWindow.document;
+ testdocument.body.innerHTML =
+ "<form id=testform action=\"/common/blank.html\">" +
+ test_obj.input +
+ "</form>";
+ testframe.onload = function() {
+ t.step(function (){
+ var get_url = testframe.contentWindow.location.toString();
+ var encoded = get_url.substr(get_url.indexOf("?") + 1);
+ assert_equals(encoded, test_obj.output);
+ });
+ t.done();
+ run_simple_test();
+ };
+ testdocument.getElementById("testform").submit();
+}
+document.getElementById("testframe").onload = run_simple_test;
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/urlencoded2.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/urlencoded2.window.js
new file mode 100644
index 0000000000..503391dd6b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/urlencoded2.window.js
@@ -0,0 +1,215 @@
+// META: script=enctypes-helper.js
+
+const formTest = formSubmissionTemplate("application/x-www-form-urlencoded");
+
+formTest({
+ name: "basic",
+ value: "test",
+ expected: "basic=test",
+ description: "Basic test",
+});
+
+formTest({
+ name: "basic",
+ value: new File([], "file-test.txt"),
+ expected: "basic=file-test.txt",
+ description: "Basic File test",
+});
+
+formTest({
+ name: "a\0b",
+ value: "c",
+ expected: "a%00b=c",
+ description: "0x00 in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\0c",
+ expected: "a=b%00c",
+ description: "0x00 in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\0c"),
+ expected: "a=b%00c",
+ description: "0x00 in filename",
+});
+
+formTest({
+ name: "a\nb",
+ value: "c",
+ expected: "a%0D%0Ab=c",
+ description: "\\n in name",
+});
+
+formTest({
+ name: "a\rb",
+ value: "c",
+ expected: "a%0D%0Ab=c",
+ description: "\\r in name",
+});
+
+formTest({
+ name: "a\r\nb",
+ value: "c",
+ expected: "a%0D%0Ab=c",
+ description: "\\r\\n in name",
+});
+
+formTest({
+ name: "a\n\rb",
+ value: "c",
+ expected: "a%0D%0A%0D%0Ab=c",
+ description: "\\n\\r in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\nc",
+ expected: "a=b%0D%0Ac",
+ description: "\\n in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\rc",
+ expected: "a=b%0D%0Ac",
+ description: "\\r in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\r\nc",
+ expected: "a=b%0D%0Ac",
+ description: "\\r\\n in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\n\rc",
+ expected: "a=b%0D%0A%0D%0Ac",
+ description: "\\n\\r in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\nc"),
+ expected: "a=b%0D%0Ac",
+ description: "\\n in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\rc"),
+ expected: "a=b%0D%0Ac",
+ description: "\\r in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\r\nc"),
+ expected: "a=b%0D%0Ac",
+ description: "\\r\\n in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\n\rc"),
+ expected: "a=b%0D%0A%0D%0Ac",
+ description: "\\n\\r in filename",
+});
+
+formTest({
+ name: 'a"b',
+ value: "c",
+ expected: "a%22b=c",
+ description: "double quote in name",
+});
+
+formTest({
+ name: "a",
+ value: 'b"c',
+ expected: "a=b%22c",
+ description: "double quote in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], 'b"c'),
+ expected: "a=b%22c",
+ description: "double quote in filename",
+});
+
+formTest({
+ name: "a'b",
+ value: "c",
+ expected: "a%27b=c",
+ description: "single quote in name",
+});
+
+formTest({
+ name: "a",
+ value: "b'c",
+ expected: "a=b%27c",
+ description: "single quote in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b'c"),
+ expected: "a=b%27c",
+ description: "single quote in filename",
+});
+
+formTest({
+ name: "a\\b",
+ value: "c",
+ expected: "a%5Cb=c",
+ description: "backslash in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\\c",
+ expected: "a=b%5Cc",
+ description: "backslash in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\\c"),
+ expected: "a=b%5Cc",
+ description: "backslash in filename",
+});
+
+formTest({
+ name: "áb",
+ value: "ç",
+ expected: "%C3%A1b=%C3%A7",
+ description: "non-ASCII in name and value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "ə.txt"),
+ expected: "a=%C9%99.txt",
+ description: "non-ASCII in filename",
+});
+
+formTest({
+ name: "aəb",
+ value: "c\uFFFDd",
+ formEncoding: "windows-1252",
+ expected: "a%26%23601%3Bb=c%26%2365533%3Bd",
+ description: "characters not in encoding in name and value",
+});
+
+formTest({
+ name: "á",
+ value: new File([], "💩"),
+ formEncoding: "windows-1252",
+ expected: "%E1=%26%23128169%3B",
+ description: "character not in encoding in filename",
+});
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-target/form-target-iframe-helper.py b/testing/web-platform/tests/html/semantics/forms/form-submission-target/form-target-iframe-helper.py
new file mode 100644
index 0000000000..bbc3b71e94
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-target/form-target-iframe-helper.py
@@ -0,0 +1,3 @@
+def main(request, response):
+ return ([(b"Content-Type", b"text/plain")],
+ b"OK")
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-target/form-target-iframe.html b/testing/web-platform/tests/html/semantics/forms/form-submission-target/form-target-iframe.html
new file mode 100644
index 0000000000..f37bc33f6f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-target/form-target-iframe.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Form targetted at iframe</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+ window.addEventListener("load", t.step_func(function() {
+ var frame = document.createElement("iframe");
+ frame.name = "frame";
+ document.documentElement.appendChild(frame);
+ var form = document.createElement("form");
+ form.target = "frame";
+ form.action = "form-target-iframe-helper.py";
+ form.method = "POST";
+ var input = document.createElement("input");
+ input.name = "n";
+ form.appendChild(input);
+ document.documentElement.appendChild(form);
+ form.submit();
+ frame.addEventListener("load", t.step_func(function() {
+ if (frame.contentWindow.location.href.includes("form-target-iframe-helper.py")) {
+ assert_equals(frame.contentWindow.document.body.textContent, "OK");
+ t.done();
+ }
+ }));
+ }));
+}, "Form targetted at iframe");
+</script>
+<body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-target/form-target-request-header.html b/testing/web-platform/tests/html/semantics/forms/form-submission-target/form-target-request-header.html
new file mode 100644
index 0000000000..a5cd68078d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-target/form-target-request-header.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Form request header test</title>
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(t => {
+ window.addEventListener("load", function() {
+ let form = document.createElement("form");
+ form.action = "resources/form-target-request-header-helper.py";
+ form.method = "post";
+ form.target = "_blank";
+
+ const channelName = token();
+ const channel = new BroadcastChannel(channelName);
+ channel.onmessage = t.step_func_done(e => {
+ assert_equals(e.data, "OK");
+ });
+
+ let url_input = document.createElement("input");
+ url_input.type = "hidden";
+ url_input.name = "channelname";
+ url_input.value = channelName;
+
+ form.appendChild(url_input);
+ document.body.appendChild(form);
+ form.submit();
+ });
+}, 'Verify the content-type exist during a form submission toward "_blank"');
+</script>
+<body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-base-target.html b/testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-base-target.html
new file mode 100644
index 0000000000..222be95d2e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-base-target.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>&lt;form rel> with &lt;base target></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=resources/reltester.js></script>
+<base target=_blank>
+<div id=log></div>
+<form action=resources/endpoint.html><input type=hidden name=channelname></form>
+<script>
+const submitter = document.querySelector("form"),
+ channelInput = document.querySelector("input");
+relTester(submitter, channelInput, "<base target>");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-button-target.html b/testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-button-target.html
new file mode 100644
index 0000000000..76fa868590
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-button-target.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>&lt;form rel> with &lt;button formtarget></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=resources/reltester.js></script>
+<div id=log></div>
+<form action=resources/endpoint.html><input type=hidden name=channelname><button type=submit formtarget=_blank></form>
+<script>
+const submitter = document.querySelector("button"),
+ channelInput = document.querySelector("input");
+relTester(submitter, channelInput, "<button formtarget>");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-form-target.html b/testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-form-target.html
new file mode 100644
index 0000000000..58611f41a9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-form-target.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>&lt;form rel target></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=resources/reltester.js></script>
+<div id=log></div>
+<form action=resources/endpoint.html target=_blank><input type=hidden name=channelname></form>
+<script>
+const submitter = document.querySelector("form"),
+ channelInput = document.querySelector("input");
+relTester(submitter, channelInput, "<form target>");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-input-target.html b/testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-input-target.html
new file mode 100644
index 0000000000..b80e0240ba
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-target/rel-input-target.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>&lt;form rel> with &lt;input formtarget></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=resources/reltester.js></script>
+<base target=_blank>
+<div id=log></div>
+<form action=resources/endpoint.html><input type=hidden name=channelname><input type=submit formtarget=_blank></form>
+<script>
+const submitter = document.querySelector("input[type=submit]"),
+ channelInput = document.querySelector("input");
+relTester(submitter, channelInput, "<input formtarget>");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-target/resources/endpoint.html b/testing/web-platform/tests/html/semantics/forms/form-submission-target/resources/endpoint.html
new file mode 100644
index 0000000000..be9e794292
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-target/resources/endpoint.html
@@ -0,0 +1,11 @@
+<script>
+ const channelName = new URL(location).searchParams.get("channelname"),
+ channel = new BroadcastChannel(channelName);
+ channel.postMessage({ haveOpener: window.opener !== null,
+ referrer: document.referrer });
+ // Because messages are not delivered synchronously and because closing a
+ // browsing context prompts the eventual clearing of all task sources, this
+ // document should not be closed until the opener document has confirmed
+ // receipt.
+ channel.onmessage = () => window.close();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-target/resources/form-target-request-header-helper.py b/testing/web-platform/tests/html/semantics/forms/form-submission-target/resources/form-target-request-header-helper.py
new file mode 100644
index 0000000000..80770167a8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-target/resources/form-target-request-header-helper.py
@@ -0,0 +1,14 @@
+body_template="""
+<script>
+const channel = new BroadcastChannel('{}');
+channel.postMessage('{}', '*');
+window.close();
+</script>
+"""
+def main(request, response):
+ has_content_type = bool(request.headers.get(b'Content-Type'))
+ result = u"OK" if has_content_type else u"FAIL"
+ channel_name = request.body.decode('utf-8').split("=")[1];
+ body = body_template.format(channel_name, result);
+ headers = [(b"Content-Type", b"text/html")]
+ return headers, body
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-target/resources/reltester.js b/testing/web-platform/tests/html/semantics/forms/form-submission-target/resources/reltester.js
new file mode 100644
index 0000000000..8ca9ddbc27
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-target/resources/reltester.js
@@ -0,0 +1,82 @@
+function formUsesTargetBlank(submitter) {
+ if (submitter.formTarget && submitter.formTarget === "_blank") {
+ return true;
+ }
+ if (submitter.form && submitter.form.target === "_blank") {
+ return true;
+ }
+ if (submitter.target && submitter.target === "_blank") {
+ return true;
+ }
+ if (submitter.getRootNode().querySelector("base").target === "_blank") {
+ return true;
+ }
+ return false;
+}
+
+function relTester(submitter, channelInput, title) {
+ [
+ {
+ rel: "",
+ exposed: "all"
+ },
+ {
+ rel: "noopener",
+ exposed: "noopener"
+ },
+ {
+ rel: "noreferrer",
+ exposed: "noreferrer"
+ },
+ {
+ rel: "opener",
+ exposed: "all"
+ },
+ {
+ rel: "noopener noreferrer",
+ exposed: "noreferrer"
+ },
+ {
+ rel: "noreferrer opener",
+ exposed: "noreferrer"
+ },
+ {
+ rel: "opener noopener",
+ exposed: "noopener"
+ }
+ ].forEach(relTest => {
+ // Use promise_test to submit only after one test concluded
+ promise_test(t => {
+ return new Promise(resolve => {
+ const channelName = Date.now() + relTest.rel,
+ channel = new BroadcastChannel(channelName);
+ let form = submitter;
+ if (submitter.localName !== "form") {
+ form = submitter.form;
+ }
+ form.rel = relTest.rel;
+ channelInput.value = channelName;
+ if (submitter.localName !== "form") {
+ submitter.click();
+ } else {
+ submitter.submit();
+ }
+ channel.onmessage = t.step_func(e => {
+ if (relTest.exposed === "all" || relTest.exposed === "noopener") {
+ assert_equals(e.data.referrer, window.location.href, "referrer");
+ } else {
+ assert_equals(e.data.referrer, "", "referrer");
+ }
+ // When rel is not explicitly given, account for target=_blank defaulting to noopener
+ if (relTest.exposed === "all" && !(relTest.rel === "" && formUsesTargetBlank(submitter))) {
+ assert_true(e.data.haveOpener, "opener");
+ } else {
+ assert_false(e.data.haveOpener, "opener");
+ }
+ resolve();
+ });
+ t.add_cleanup(() => channel.postMessage(null));
+ });
+ }, `<form rel="${relTest.rel}"> with ${title}`);
+ });
+}
diff --git a/testing/web-platform/tests/html/semantics/forms/historical-search-event.html b/testing/web-platform/tests/html/semantics/forms/historical-search-event.html
new file mode 100644
index 0000000000..b7a089a68e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/historical-search-event.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>search event should not be supported</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<input id=input type=search incremental>
+<script>
+promise_test(async t => {
+ const input = document.getElementById('input');
+ const eventWatcher = new EventWatcher(t, input, ['search', 'keypress']);
+ await Promise.all([
+ test_driver.send_keys(input, 'x'),
+ eventWatcher.wait_for(['keypress'])
+ ]);
+ // During this timeout, the search event will fire, if it's supported,
+ // which fails the test since the event watcher isn't expecting it.
+ await new Promise(resolve => t.step_timeout(resolve, 1000));
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/historical.html b/testing/web-platform/tests/html/semantics/forms/historical.html
new file mode 100644
index 0000000000..277e124161
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/historical.html
@@ -0,0 +1,97 @@
+<!doctype html>
+<title>Historical forms features should not be supported</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<form id=form hidden>
+ <label id=label></label>
+ <input id=input>
+ <button id=button></button>
+ <select id=select>
+ <optgroup id=optgroup>
+ <option id=option>
+ </select>
+ <datalist id=datalist></datalist>
+ <textarea id=textarea></textarea>
+ <progress id=progress></progress>
+ <meter id=meter></meter>
+ <fieldset id=fieldset>
+ <legend id=legend></legend>
+ </fieldset>
+</form>
+
+<form hidden action="isindex-support.txt" target=isindex_iframe id=isindex_form>
+ <input name=isindex value=x>
+ <iframe name=isindex_iframe id=isindex_iframe></iframe>
+</form>
+<script>
+var tags = ['form', 'label', 'input', 'button', 'select', 'datalist',
+'optgroup', 'option', 'textarea', 'progress', 'meter', 'fieldset', 'legend'];
+
+function t(property, tagName) {
+ var tagNames = tagName ? [tagName] : tags;
+ tagNames.forEach(function(tagName) {
+ test(function() {
+ assert_false(property in document.getElementById(tagName));
+ }, tagName + '.' + property + ' should not be supported');
+ });
+}
+
+function inputType(type) {
+ test(function() {
+ var input = document.createElement('input');
+ input.type = type;
+ assert_equals(input.type, 'text');
+ }, '<input type=' + type + '> should not be supported');
+}
+
+// <input type=range multiple>
+// added in https://github.com/whatwg/html/commit/1efac390abb3f95df61f2d2ac6c0feb47349d97b
+// removed in https://github.com/whatwg/html/commit/b598d4f873fb8c27d4b23b033837108edfbc3d75
+t('valueLow', 'input');
+t('valueHigh', 'input');
+
+// requestAutoComplete()
+// added in https://github.com/whatwg/html/commit/321659e4db11228857632487ab72b6959db1ba86
+// removed in https://github.com/whatwg/html/commit/6a257aae619f85390eee20b47767f34887450fcd
+t('requestAutocomplete', 'form');
+t('onautocomplete', 'form');
+t('onautocompleteerror', 'form');
+
+// <input type=datetime>
+// added in WF2
+// removed in https://github.com/whatwg/html/commit/80ba4fa24e5d3d81a10aa1bbd8a2f72f4bcc3f7c
+inputType('datetime');
+
+// <progress form>, <meter form>
+// removed in https://github.com/whatwg/html/commit/3814376a311837ddfac229d9a631cd10adf53157
+t('form', 'progress');
+t('form', 'meter');
+
+// form.item(), form.namedItem()
+// removed in https://github.com/whatwg/html/commit/da87ab9009d5aeca95a602e718439e35b00d0731
+t('item', 'form');
+t('namedItem', 'form');
+
+// Never specified but implemented in WebKit/Chromium
+test(() => {
+ assert_false("onsearch" in window);
+}, "window.onsearch should not be supported");
+
+test(() => {
+ assert_false("onsearch" in document);
+}, "document.onsearch should not be supported");
+
+t('onsearch', 'input');
+t('incremental', 'input');
+
+// <input name=isindex>
+// removed in https://github.com/whatwg/html/commit/5c44abc734eb483f9a7ec79da5844d2fe63d9c3b
+async_test(function() {
+ var iframe = document.getElementById('isindex_iframe');
+ iframe.onload = this.step_func_done(function() {
+ assert_regexp_match(iframe.contentWindow.location.href, /\?isindex=x$/);
+ });
+ document.getElementById('isindex_form').submit();
+}, '<input name=isindex> should not be supported');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/input-change-event-properties.html b/testing/web-platform/tests/html/semantics/forms/input-change-event-properties.html
new file mode 100644
index 0000000000..f3c6f7d462
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/input-change-event-properties.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test the properties of input and change events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<fieldset id="clickable">
+ <input type="checkbox">
+ <input type="radio">
+</fieldset>
+
+<fieldset id="typeable">
+ <input type="text">
+ <input type="search">
+ <input type="tel">
+ <input type="url">
+ <input type="email">
+ <input type="password">
+ <input type="number">
+ <textarea></textarea>
+</fieldset>
+
+<select>
+ <option>1</option>
+ <option>2</option>
+</select>
+
+<!-- Not going to test the more complicated input types like date or color for now... -->
+
+<button id="click-me-to-unfocus-other-things">Clickable</button>
+
+<script>
+"use strict";
+const clickMeToUnfocusOtherThings = document.querySelector("#click-me-to-unfocus-other-things");
+
+for (const el of document.querySelector("#clickable").children) {
+ test(() => {
+ let inputEvent, changeEvent;
+ el.oninput = e => inputEvent = e;
+ el.onchange = e => changeEvent = e;
+
+ el.click();
+
+ assert_event(inputEvent, { bubbles: true, cancelable: false, composed: true });
+ assert_event(changeEvent, { bubbles: true, cancelable: false, composed: false });
+ }, `${el.outerHTML} click()`);
+}
+
+for (const el of document.querySelector("#typeable").children) {
+ promise_test(async () => {
+ let inputEvent, changeEvent;
+ el.oninput = e => inputEvent = e;
+ el.onchange = e => changeEvent = e;
+
+ await test_driver.send_keys(el, "1"); // has to be a number so <input type=number> works
+ await test_driver.click(clickMeToUnfocusOtherThings);
+
+ assert_event(inputEvent, { bubbles: true, cancelable: false, composed: true });
+ assert_event(changeEvent, { bubbles: true, cancelable: false, composed: false });
+ }, `${el.outerHTML} typing`);
+}
+
+promise_test(async () => {
+ const el = document.querySelector("select");
+
+ let inputEvent, changeEvent;
+ el.oninput = e => inputEvent = e;
+ el.onchange = e => changeEvent = e;
+
+ // TODO: this doesn't seem to work in Safari/on Macs. Or maybe Safari is legitimately failing.
+ // Someone with a Mac should investigate.
+ await test_driver.send_keys(el, "\uE015"); // down arrow key
+ await test_driver.click(clickMeToUnfocusOtherThings);
+
+ assert_event(inputEvent, { bubbles: true, cancelable: false, composed: true });
+ assert_event(changeEvent, { bubbles: true, cancelable: false, composed: false });
+}, `<select> pressing down arrow`);
+
+function assert_event(e, { bubbles, cancelable, composed }) {
+ assert_equals(e.bubbles, bubbles, `${e.type} bubbles`);
+ assert_equals(e.cancelable, cancelable, `${e.type} cancelable`);
+ assert_equals(e.composed, composed, `${e.type} composed`);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-event.html b/testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-event.html
new file mode 100644
index 0000000000..f7d98f7216
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-event.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>Test aspects of the reset event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ async_test((t) => {
+ var form = document.createElement("form")
+ form.onreset = t.step_func_done((e) => {
+ assert_true(e.bubbles)
+ assert_true(e.cancelable)
+ assert_true(e.isTrusted)
+ assert_equals(e.target, form)
+ })
+ form.reset()
+ assert_unreached()
+ })
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-form-2.html b/testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-form-2.html
new file mode 100644
index 0000000000..6ce0040c4a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-form-2.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Resetting a form integration test</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#concept-form-reset">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+
+ const form = document.createElement("form");
+ const text = document.createElement("input");
+ text.type = "text";
+ const checkbox = document.createElement("input");
+ checkbox.type = "checkbox";
+ const select = document.createElement("select");
+ select.multiple = true;
+ const option = document.createElement("option");
+ option.value = "option";
+ const textarea = document.createElement("textarea");
+
+ form.appendChild(text);
+ form.appendChild(checkbox);
+ form.appendChild(textarea);
+ form.appendChild(select);
+ select.appendChild(option);
+
+ text.defaultValue = "text default";
+ checkbox.defaultChecked = true;
+ option.defaultSelected = true;
+ textarea.defaultValue = "textarea default";
+
+ text.value = "text new value";
+ checkbox.checked = false;
+ option.selected = false;
+ textarea.value = "textarea new value";
+
+ form.reset();
+
+ assert_equals(text.value, "text default", "input should reset value to default");
+ assert_equals(checkbox.checked, true, "input should reset checkedness to default");
+ assert_equals(option.selected, true, "second option should reset selectedness to default");
+ assert_equals(select.selectedIndex, 0, "second option should reset selectedness to default");
+ assert_equals(textarea.value, "textarea default", "textarea should reset api value to default");
+
+ text.defaultValue = "text new default";
+ checkbox.defaultChecked = false;
+ option.defaultSelected = false;
+ textarea.defaultValue = "textarea new default";
+
+ assert_equals(text.value, "text new default", "input should reset dirty value to false");
+ assert_equals(checkbox.checked, false, "input should reset dirty checkedness to false");
+ assert_equals(option.selected, false, "option should reset dirtyness to false");
+ assert_equals(select.selectedIndex, -1, "option should reset dirtyness to false");
+ assert_equals(textarea.value, "textarea new default", "textarea should reset dirty value to false");
+
+}, "integration test on reset for a created-from-script form");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-form-event-realm.html b/testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-form-event-realm.html
new file mode 100644
index 0000000000..6c125c46d0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-form-event-realm.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>reset() event firing realm</title>
+<link rel="help" href="https://html.spec.whatwg.org/#resetting-a-form">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-fire">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe src="support/reset-form-event-realm.html"></iframe>
+<iframe></iframe>
+
+<script>
+"use strict";
+
+async_test(t => {
+ window.onload = t.step_func_done(() => {
+ const frame0Form = frames[0].document.forms[0];
+ const frame1Body = frames[1].document.body;
+
+ frame1Body.appendChild(frame0Form);
+
+ let resetCalled = false;
+ frame0Form.onreset = t.step_func(ev => {
+ resetCalled = true;
+
+ const functionConstructorInEvRealm = ev.constructor.constructor;
+ const functionConstructorInFormRealm = frame0Form.constructor.constructor;
+
+ assert_equals(functionConstructorInEvRealm, functionConstructorInFormRealm,
+ "the event must be created in the realm of the target");
+ });
+
+ frame0Form.reset();
+ assert_true(resetCalled, "The reset event handler must have been called");
+ });
+}, "reset()'s event must be fired in the Realm of the target")
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-form.html b/testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-form.html
new file mode 100644
index 0000000000..c7ac3e085b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/resetting-a-form/reset-form.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: Resetting a form</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#concept-form-reset">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#category-reset">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form name="fm1" style="display:none">
+ <input value="abc" id="ipt1" />
+ <input id="ipt2" />
+ <input type="radio" id="rd1" checked="checked" />
+ <input type="radio" id="rd2"/>
+ <input type="checkbox" id="cb1" checked="checked" />
+ <input type="checkbox" id="cb2" />
+ <textarea id="ta">abc</textarea>
+ <output id="opt">5</output>
+ <select id="slt1">
+ <option value="1">ITEM1</option>
+ <option value="2">ITEM2</option>
+ </select>
+ <select id="slt2">
+ <option value="1">ITEM1</option>
+ <option value="2" selected>ITEM2</option>
+ </select>
+ <select id="slt3" multiple>
+ <option value="1">ITEM1</option>
+ <option value="2" selected>ITEM2</option>
+ <option value="3" selected>ITEM3</option>
+ </select>
+ <button id="rst1" type="reset">Reset1</button>
+ <input id="rst2" type="reset" value="Reset2" />
+</form>
+<script>
+
+runTest(function() {
+ document.forms.fm1.reset();
+}, "by calling the reset() method");
+
+runTest(function() {
+ document.getElementById("rst1").click();
+}, "by clicking the button in reset status");
+
+runTest(function() {
+ document.getElementById("rst2").click();
+}, "by clicking the input in reset status");
+
+function setPreconditions (arg) {
+ document.getElementById("ipt1").value = "123";
+ document.getElementById("ipt2").value = "123";
+ document.getElementById("rd1").checked = false;
+ document.getElementById("rd2").checked = true;
+ document.getElementById("cb1").checked = false;
+ document.getElementById("cb2").checked = true;
+ document.getElementById("ta").value = "123";
+ document.getElementById("opt").textContent = "abc";
+ document.getElementById("slt1").value = "2";
+ document.getElementById("slt2").value = "1";
+ document.getElementById("slt3").options[0].selected = true;
+ document.getElementById("slt3").options[1].selected = false;
+ document.getElementById("slt3").options[2].selected = false;
+
+ assert_equals(document.getElementById("ipt1").value, "123", "Precondition 1");
+ assert_equals(document.getElementById("ipt2").value, "123", "Precondition 2");
+ assert_false(document.getElementById("rd1").checked, "Precondition 3");
+ assert_true(document.getElementById("rd2").checked, "Precondition 4");
+ assert_false(document.getElementById("cb1").checked, "Precondition 5");
+ assert_true(document.getElementById("cb2").checked, "Precondition 6");
+ assert_equals(document.getElementById("ta").value, "123", "Precondition 17");
+ assert_equals(document.getElementById("opt").textContent, "abc", "Precondition 8");
+ assert_true(document.getElementById("slt1").options[1].selected, "Precondition 9");
+ assert_true(document.getElementById("slt2").options[0].selected, "Precondition 10");
+ assert_true(document.getElementById("slt3").options[0].selected, "Precondition 11");
+ assert_false(document.getElementById("slt3").options[1].selected, "Precondition 12");
+ assert_false(document.getElementById("slt3").options[2].selected, "Precondition 13");
+}
+
+function runTest(reset, description) {
+ test(function() {
+ setPreconditions("Setting preconditions for resetting " + description);
+ reset();
+ assert_equals(document.getElementById("ipt1").value, "abc", "The value of the input element in text status should be 'abc'.");
+ assert_equals(document.getElementById("ipt2").value, "", "The value of the input element in text status should be empty string.");
+ assert_true(document.getElementById("rd1").checked, "The checked attribute of the input element in radio should be true.");
+ assert_false(document.getElementById("rd2").checked, "The checked attribute of the input element in radio should be false.");
+ assert_true(document.getElementById("cb1").checked, "The checked attribute of the input element in checkbox should be true.");
+ assert_false(document.getElementById("cb2").checked, "The checked attribute of the input element in checkbox should be false.");
+ }, "Resetting <input> " + description);
+
+ test(function() {
+ setPreconditions("Setting preconditions for resetting " + description);
+ reset();
+ assert_equals(document.getElementById("ta").value, document.getElementById("ta").textContent, "The value attribute of the textarea element should be reset.");
+ assert_equals(document.getElementById("ta").value, "abc", "The value attribute of the textarea element should be 'abc'.");
+ }, "Resetting <textarea> " + description);
+
+ test(function() {
+ setPreconditions("Setting preconditions for resetting " + description);
+ reset();
+ assert_equals(document.getElementById("opt").textContent, document.getElementById("opt").defaultValue, "The textContent of the output element should be reset.");
+ assert_equals(document.getElementById("opt").textContent, "abc", "The textContent of the output element should be 'abc'.");
+ }, "Resetting <output> " + description);
+
+ test(function() {
+ setPreconditions("Setting preconditions for resetting " + description);
+ reset();
+ assert_true(document.getElementById("slt1").options[0].selected, "The first option in the select element should be selected.");
+ assert_false(document.getElementById("slt1").options[1].selected, "The second option in the select element should not be selected.");
+ assert_false(document.getElementById("slt2").options[0].selected, "The first option in the select element should not be selected.");
+ assert_true(document.getElementById("slt2").options[1].selected, "The second option in the select element should be selected.");
+ assert_false(document.getElementById("slt3").options[0].selected, "The first option in the select element with multiple attribute should not be selected.");
+ assert_true(document.getElementById("slt3").options[1].selected, "The second option in the select element with multiple attribute should be selected.");
+ assert_true(document.getElementById("slt3").options[2].selected, "The third option in the select element with multiple attribute should be selected.");
+ }, "Resetting <select> " + description);
+}
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/resetting-a-form/support/reset-form-event-realm.html b/testing/web-platform/tests/html/semantics/forms/resetting-a-form/support/reset-form-event-realm.html
new file mode 100644
index 0000000000..f55c651b54
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/resetting-a-form/support/reset-form-event-realm.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+
+<form></form>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/defaultSelection.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/defaultSelection.html
new file mode 100644
index 0000000000..be965bf5cf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/defaultSelection.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset="utf-8">
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<textarea>foo</textarea>
+<input type="text" value="foo"></input>
+<script>
+
+for (let el of [document.querySelector("textarea"), document.querySelector("input")]) {
+ test(function() {
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, `Default selectionStart and selectionEnd for ${el}`);
+
+ test(function() {
+ el.value="foo";
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, `selectionStart and selectionEnd do not change when same value set again for ${el}`);
+
+ test(function() {
+ el.value="Foo";
+ assert_equals(el.selectionStart, 3);
+ assert_equals(el.selectionEnd, 3);
+ }, `selectionStart and selectionEnd change when value changed to upper case for ${el}`);
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/original-id.json b/testing/web-platform/tests/html/semantics/forms/textfieldselection/original-id.json
new file mode 100644
index 0000000000..d9fe435856
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/original-id.json
@@ -0,0 +1 @@
+{"original_id":"textFieldSelection"} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/select-event.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/select-event.html
new file mode 100644
index 0000000000..c3a65f7302
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/select-event.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>text field selection: select()</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#textFieldSelection">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+
+<textarea>foobar</textarea>
+<input type="text" value="foobar">
+<input type="search" value="foobar">
+<input type="tel" value="1234">
+<input type="url" value="https://example.com/">
+<input type="password" value="hunter2">
+
+<script>
+"use strict";
+
+const els = [document.querySelector("textarea"), ...document.querySelectorAll("input")];
+
+const actions = [
+ {
+ label: "select()",
+ action: el => el.select()
+ },
+ {
+ label: "selectionStart",
+ action: el => el.selectionStart = 1
+ },
+ {
+ label: "selectionEnd",
+ action: el => el.selectionEnd = el.value.length - 1
+ },
+ {
+ label: "selectionDirection",
+ action: el => el.selectionDirection = "backward"
+ },
+ {
+ label: "setSelectionRange()",
+ action: el => el.setSelectionRange(1, el.value.length - 1) // changes direction implicitly to none/forward
+ },
+ {
+ label: "setRangeText()",
+ action: el => el.setRangeText("newmiddle", el.selectionStart, el.selectionEnd, "select")
+ },
+ {
+ label: "selectionStart out of range",
+ action: el => el.selectionStart = 1000
+ },
+ {
+ label: "selectionEnd out of range",
+ action: el => el.selectionEnd = 1000
+ },
+ {
+ label: "setSelectionRange out of range",
+ action: el => el.setSelectionRange(1000, 2000)
+ }
+];
+
+function waitForRender() {
+ return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+}
+
+function initialize(el) {
+ el.setRangeText("foobar", 0, el.value.length, "start");
+ // Make sure to flush async dispatches
+ return waitForRender();
+}
+
+els.forEach((el) => {
+ const elLabel = el.localName === "textarea" ? "textarea" : "input type " + el.type;
+
+ actions.forEach((action) => {
+ // promise_test instead of async_test is important because these need to happen in sequence (to test that events
+ // fire if and only if the selection changes).
+ promise_test(async t => {
+ await initialize(el);
+
+ const watcher = new EventWatcher(t, el, "select");
+
+ const promise = watcher.wait_for("select").then(e => {
+ assert_true(e.isTrusted, "isTrusted must be true");
+ assert_true(e.bubbles, "bubbles must be true");
+ assert_false(e.cancelable, "cancelable must be false");
+ });
+
+ action.action(el);
+
+ return promise;
+ }, `${elLabel}: ${action.label}`);
+
+ promise_test(async t => {
+ el.onselect = t.unreached_func("the select event must not fire the second time");
+
+ action.action(el);
+
+ await waitForRender();
+ el.onselect = null;
+ }, `${elLabel}: ${action.label} a second time (must not fire select)`);
+
+ promise_test(async t => {
+ const element = el.cloneNode(true);
+ let fired = false;
+ element.addEventListener('select', () => fired = true, { once: true });
+
+ action.action(element);
+
+ await waitForRender();
+ assert_true(fired, "event didn't fire");
+
+ }, `${elLabel}: ${action.label} disconnected node`);
+
+ // Intentionally still using promise_test, as assert_unreachable does not
+ // make the test fail inside a listener while t.unreached_func() does.
+ promise_test(async t => {
+ const element = el.cloneNode(true);
+ let fired = false;
+ element.addEventListener('select', () => fired = true, { once: true });
+
+ action.action(element);
+
+ assert_false(fired, "the select event must not fire synchronously");
+ await waitForRender();
+ assert_true(fired, "event didn't fire");
+ }, `${elLabel}: ${action.label} event queue`);
+
+ promise_test(async t => {
+ const element = el.cloneNode(true);
+ let selectCount = 0;
+ element.addEventListener('select', () => ++selectCount);
+ assert_equals(element.selectionEnd, 0);
+
+ action.action(element);
+ action.action(element);
+
+ await waitForRender();
+ assert_equals(selectCount, 1, "the select event must not fire twice");
+ }, `${elLabel}: ${action.label} twice in disconnected node (must fire select only once)`);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-after-content-change.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-after-content-change.html
new file mode 100644
index 0000000000..60390085c6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-after-content-change.html
@@ -0,0 +1,144 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Selection indices after content change</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<input id="i1" type="text" value="hello">
+<textarea id="t1">hello</textarea>
+
+<script>
+"use strict";
+
+// This helper ensures that when the selection direction is reset, it always is reset to the same value consistently
+// (which must be one of either "none" or "forward"). This helps catch bugs like one observed in Chrome, where textareas
+// reset to "none" but inputs reset to "forward".
+let observedResetSelectionDirection;
+function assertSelectionDirectionIsReset(element) {
+ if (!observedResetSelectionDirection) {
+ assert_in_array(element.selectionDirection, ["none", "forward"],
+ "selectionDirection must be set to either none or forward");
+ observedResetSelectionDirection = element.selectionDirection;
+ } else {
+ assert_equals(element.selectionDirection, observedResetSelectionDirection,
+ `selectionDirection must be reset to ${observedResetSelectionDirection} (which was previously observed to be ` +
+ `the value after resetting the selection direction)`);
+ }
+}
+
+runInputTest("input out of document", () => {
+ const input = document.createElement("input");
+ input.value = "hello";
+ return input;
+});
+
+runInputTest("input in document", () => {
+ const input = document.querySelector("#i1");
+ input.value = "hello";
+ return input;
+});
+
+runInputTest("input in document, with focus", () => {
+ const input = document.querySelector("#i1");
+ input.value = "hello";
+ input.focus();
+ return input;
+});
+
+runTextareaTest("textarea out of document", () => {
+ const textarea = document.createElement("textarea");
+ textarea.value = "hello";
+ return textarea;
+});
+
+runTextareaTest("textarea in document", () => {
+ const textarea = document.querySelector("#t1");
+ textarea.value = "hello";
+ return textarea;
+});
+
+runTextareaTest("textarea in document, with focus", () => {
+ const textarea = document.querySelector("#t1");
+ textarea.value = "hello";
+ textarea.focus();
+ return textarea;
+});
+
+function runTest(descriptor, elementFactory) {
+ test(() => {
+ const element = elementFactory();
+ element.setSelectionRange(1, 3, "backward");
+
+ assert_equals(element.selectionStart, 1, "Sanity check: selectionStart was set correctly");
+ assert_equals(element.selectionEnd, 3, "Sanity check: selectionEnd was set correctly");
+ assert_equals(element.selectionDirection, "backward", "Sanity check: selectionDirection was set correctly");
+
+ element.value = "hello";
+
+ assert_equals(element.selectionStart, 1, "selectionStart must not change");
+ assert_equals(element.selectionEnd, 3, "selectionEnd must not change");
+ assert_equals(element.selectionDirection, "backward", "selectionDirection must not change");
+ }, `${descriptor}: selection must not change when setting the same value`);
+
+ test(() => {
+ const element = elementFactory();
+ element.setSelectionRange(1, 3, "backward");
+
+ assert_equals(element.selectionStart, 1, "Sanity check: selectionStart was set correctly");
+ assert_equals(element.selectionEnd, 3, "Sanity check: selectionEnd was set correctly");
+ assert_equals(element.selectionDirection, "backward", "Sanity check: selectionDirection was set correctly");
+
+ element.value = "hello2";
+
+ assert_equals(element.selectionStart, element.value.length, "selectionStart must be reset to the end");
+ assert_equals(element.selectionEnd, element.value.length, "selectionEnd must be reset to the end");
+ assertSelectionDirectionIsReset(element);
+ }, `${descriptor}: selection must change when setting a different value`);
+}
+
+function runInputTest(descriptor, elementFactory) {
+ runTest(descriptor, elementFactory);
+
+ test(() => {
+ const input = elementFactory();
+ input.setSelectionRange(1, 3, "backward");
+
+ assert_equals(input.selectionStart, 1, "Sanity check: selectionStart was set correctly");
+ assert_equals(input.selectionEnd, 3, "Sanity check: selectionEnd was set correctly");
+ assert_equals(input.selectionDirection, "backward", "Sanity check: selectionDirection was set correctly");
+
+ input.value = "he\nllo";
+
+ assert_equals(input.selectionStart, 1, "selectionStart must not change");
+ assert_equals(input.selectionEnd, 3, "selectionEnd must not change");
+ assert_equals(input.selectionDirection, "backward", "selectionDirection must not change");
+ }, `${descriptor}: selection must not change when setting a value that becomes the same after the value ` +
+ `sanitization algorithm`);
+}
+
+function runTextareaTest(descriptor, elementFactory) {
+ runTest(descriptor, elementFactory);
+
+ test(() => {
+ const textarea = elementFactory();
+ textarea.value = "hell\no";
+ textarea.setSelectionRange(1, 3, "backward");
+
+ assert_equals(textarea.selectionStart, 1, "Sanity check: selectionStart was set correctly");
+ assert_equals(textarea.selectionEnd, 3, "Sanity check: selectionEnd was set correctly");
+ assert_equals(textarea.selectionDirection, "backward", "Sanity check: selectionDirection was set correctly");
+
+ textarea.value = "hell\r\no";
+
+ assert_equals(textarea.selectionStart, 1, "selectionStart must not change when setting to CRLF");
+ assert_equals(textarea.selectionEnd, 3, "selectionEnd must not change when setting to CRLF");
+ assert_equals(textarea.selectionDirection, "backward", "selectionDirection must not change when setting to CRLF");
+
+ textarea.value = "hell\ro";
+
+ assert_equals(textarea.selectionStart, 1, "selectionStart must not change when setting to CR");
+ assert_equals(textarea.selectionEnd, 3, "selectionEnd must not change when setting to CR");
+ assert_equals(textarea.selectionDirection, "backward", "selectionDirection must not change when setting to CR");
+ }, `${descriptor}: selection must not change when setting the same normalized value`);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application-textarea.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application-textarea.html
new file mode 100644
index 0000000000..48c6313f32
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application-textarea.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>text field selection (textarea)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#textFieldSelection">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ test(function() {
+ var el = document.createElement("textarea");
+ assert_equals(el.selectionStart, 0, "initial selectionStart");
+ assert_equals(el.selectionEnd, 0, "initial selectionEnd");
+ // The initial selection direction must be "none" if the platform supports that
+ // direction, or "forward" otherwise.
+ assert_in_array(el.selectionDirection, ["none", "forward"]);
+
+ const initialDirection = el.selectionDirection;
+ el.selectionDirection = "none";
+ assert_equals(el.selectionDirection, initialDirection);
+
+ el.value = "foo";
+ el.selectionStart = 1;
+ el.selectionEnd = 1;
+ el.selectionDirection = "forward";
+ assert_equals(el.selectionStart, 1, "updated selectionStart");
+ assert_equals(el.selectionEnd, 1, "updated selectionEnd");
+ assert_equals(el.selectionDirection, "forward", "updated selectionDirection");
+
+ el.setRangeText("foobar");
+ el.setSelectionRange(0, 1);
+ assert_equals(el.selectionStart, 0, "final selectionStart");
+ assert_equals(el.selectionEnd, 1, "final selectionEnd");
+ assert_in_array(el.selectionDirection, ["none", "forward"]);
+
+ const finalDirection = el.selectionDirection;
+ el.finalDirection = "forward";
+ assert_equals(el.selectionDirection, finalDirection);
+ }, "text field selection for the input textarea");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application.html
new file mode 100644
index 0000000000..7771c3cf31
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name=variant content="">
+<meta name=variant content="?week,month">
+<title>text field selection</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#textFieldSelection">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ var nonApplicableTypes = ["hidden", "email", "datetime-local", "date", "time", "number", "range", "color", "checkbox", "radio", "file", "submit", "image", "reset", "button"];
+ var applicableTypes = ["text", "search", "tel", "url", "password", "aninvalidtype", null];
+
+ if (location.search) {
+ // If the <meta name=variant> tag used is non-empty, then use it instead of
+ // nonApplicableTypes.
+ nonApplicableTypes = location.search.substr(1).split(',');
+ }
+
+ nonApplicableTypes.forEach(function(type){
+ var el = document.createElement("input");
+ el.type = type;
+
+ test(() => {
+ assert_equals(el.selectionStart, null);
+ }, `selectionStart on an input[type=${type}] returns null`);
+
+ test(() => {
+ assert_equals(el.selectionEnd, null);
+ }, `selectionEnd on an input[type=${type}] returns null`);
+
+ test(() => {
+ assert_equals(el.selectionDirection, null);
+ }, `selectionDirection on an input[type=${type}] returns null`);
+
+ test(() => {
+ assert_throws_dom("InvalidStateError", function(){
+ el.selectionStart = 0;
+ });
+ }, `assigning selectionStart on an input[type=${type}] throws InvalidStateError`);
+
+ test(() => {
+ assert_throws_dom("InvalidStateError", function(){
+ el.selectionEnd = 0;
+ });
+ }, `assigning selectionEnd on an input[type=${type}] throws InvalidStateError`);
+
+ test(() => {
+ assert_throws_dom("InvalidStateError", function(){
+ el.selectionDirection = 'none';
+ });
+ }, `assigning selectionDirection on an input[type=${type}] throws InvalidStateError`);
+
+ test(() => {
+ assert_throws_dom("InvalidStateError", function(){
+ el.setRangeText("foobar");
+ });
+ }, `setRangeText on an input[type=${type}] throws InvalidStateError`);
+
+ test(() => {
+ assert_throws_dom("InvalidStateError", function(){
+ el.setSelectionRange(0, 1);
+ });
+ }, `setSelectionRange on an input[type=${type}] throws InvalidStateError`);
+ });
+
+ applicableTypes.forEach(function(type) {
+ const el = document.createElement("input");
+ if (type) {
+ el.type = type;
+ }
+ const initialDirection = el.selectionDirection;
+
+ test(() => {
+ assert_equals(el.selectionStart, 0);
+ }, `selectionStart on an input[type=${type}] returns a value`);
+
+ test(() => {
+ assert_equals(el.selectionEnd, 0);
+ }, `selectionEnd on an input[type=${type}] returns a value`);
+
+ test(() => {
+ assert_in_array(el.selectionDirection, ["forward", "none"]);
+ }, `selectionDirection on an input[type=${type}] returns a value`);
+
+ test(() => {
+ el.selectionDirection = "none";
+ assert_equals(el.selectionDirection, initialDirection);
+ }, `assigning selectionDirection "none" on an input[type=${type}] should give the initial direction`);
+
+ test(() => {
+ el.selectionStart = 1;
+ }, `assigning selectionStart on an input[type=${type}] doesn't throw an exception`);
+
+ test(() => {
+ el.selectionEnd = 1;
+ }, `assigning selectionEnd on an input[type=${type}] doesn't throw an exception`);
+
+ test(() => {
+ el.selectionDirection = "forward";
+ }, `assigning selectionDirection on an input[type=${type}] doesn't throw an exception`);
+
+ test(() => {
+ el.setRangeText("foobar");
+ }, `setRangeText on an input[type=${type}] doesn't throw an exception`);
+
+ test(() => {
+ el.setSelectionRange(0, 1);
+ }, `setSelectionRange on an input[type=${type}] doesn't throw an exception`);
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end-extra.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end-extra.html
new file mode 100644
index 0000000000..c8ba83bb98
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end-extra.html
@@ -0,0 +1,153 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<form id="form"><input id="form-input" type="text" value="abc" /></form>
+<script>
+ // * Should we test setting the dirty flag in any way that isn't
+ // setting the value?
+ // * How to simulate users typing?
+
+ test(function() {
+ var el = document.createElement("textarea");
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ el.defaultValue = "123";
+ assert_equals(el.value.length, 3);
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, "Setting defaultValue in a textarea should NOT move the cursor to the end");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.value = "abcdef";
+ assert_equals(el.selectionStart, 6);
+ assert_equals(el.selectionEnd, 6);
+ el.defaultValue = "123";
+ assert_equals(el.value.length, 6);
+ assert_equals(el.selectionStart, 6);
+ assert_equals(el.selectionEnd, 6);
+ }, "Setting defaultValue in a textarea with a value should NOT make any difference");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.appendChild(document.createTextNode("abcdef"));
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ el.textContent = "abcdef123456";
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, "Setting textContent in a textarea should NOT move selection{Start,End} to the end");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.appendChild(document.createTextNode("abcdef"));
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ el.appendChild(document.createTextNode("123456"));
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, "Adding children to a textarea should NOT move selection{Start,End} to the end");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.appendChild(document.createTextNode("abcdef"));
+ el.appendChild(document.createTextNode("123"));
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+
+ el.removeChild(el.firstChild);
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, "Removing children from a textarea should NOT update selection{Start,End}");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.textContent = "abcdef\nwhatevs";
+ el.selectionStart = 3;
+ el.selectionEnd = 5;
+
+ el.firstChild.data = "abcdef\r\nwhatevs";
+ assert_equals(el.selectionStart, 3);
+ assert_equals(el.selectionEnd, 5);
+ }, "Setting the same value (with different newlines) in a textarea should NOT update selection{Start,End}");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.textContent = "foobar";
+ el.selectionStart = 3;
+ el.selectionEnd = 5;
+ el.firstChild.remove();
+ assert_equals(el.selectionStart, 0, 'selectionStart after node removal');
+ assert_equals(el.selectionEnd, 0, 'selectionEnd after node removal');
+ el.appendChild(document.createTextNode("foobar"));
+ assert_equals(el.selectionStart, 0, 'selectionStart after appendChild');
+ assert_equals(el.selectionEnd, 0, 'selectionEnd after appendChild');
+
+ el.selectionStart = 3;
+ el.selectionEnd = 5;
+ el.textContent = "foobar2"; // This removes the child node first.
+ assert_equals(el.selectionStart, 0, 'selectionStart after textContent setter');
+ assert_equals(el.selectionEnd, 0, 'selectionEnd after textContent setter');
+
+ el.selectionStart = 3;
+ el.selectionEnd = 5;
+ el.defaultValue = "foobar"; // Same as textContent setter.
+ assert_equals(el.selectionStart, 0, 'selectionStart after defaultValue setter');
+ assert_equals(el.selectionEnd, 0, 'selectionEnd after defaultValue setter');
+
+ }, "Removing child nodes in non-dirty textarea should make selection{Start,End} 0");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.defaultValue = "123";
+ assert_equals(el.value.length, 3);
+ el.selectionStart = 3;
+ el.selectionEnd = 3;
+ el.value = "12";
+ assert_equals(el.value.length, 2);
+ assert_equals(el.selectionStart, 2);
+ assert_equals(el.selectionEnd, 2);
+ }, "Setting value to a shorter string than defaultValue should correct the cursor position");
+
+ test(function() {
+ var el = document.createElement("input");
+ el.type = "text";
+ el.value = "http://example.com ";
+ assert_equals(el.selectionStart, 21);
+ assert_equals(el.selectionEnd, 21);
+ el.type = "url";
+ assert_equals(el.selectionStart, 18);
+ assert_equals(el.selectionEnd, 18);
+ }, "Shortening value by turning the input type into 'url' should correct selection{Start,End}");
+
+ test(function() {
+ var el = document.createElement("input");
+ el.type = "text";
+ el.value = "#123456xx";
+ assert_equals(el.selectionStart, 9);
+ assert_equals(el.selectionEnd, 9);
+ el.type = "color";
+ el.type = "text";
+ // https://html.spec.whatwg.org/C/input.html#the-input-element:attr-input-type-15
+ // 9. If previouslySelectable is false and nowSelectable is true, set the
+ // element's text entry cursor position to the beginning of the text
+ // control, ...
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, "Shortening value by turning the input type into 'color' and back to 'text' should correct selection{Start,End}");
+
+ test(function() {
+ var form = document.getElementById("form");
+ var el = document.getElementById("form-input");
+
+ el.value = "abcde";
+ assert_equals(el.value.length, 5);
+ form.reset();
+ assert_equals(el.value.length, 3);
+ assert_equals(el.selectionStart, 3);
+ assert_equals(el.selectionEnd, 3);
+ }, "Resetting a value to a shorter string than defaultValue should correct the cursor position");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end.html
new file mode 100644
index 0000000000..768bce4129
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end.html
@@ -0,0 +1,206 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+ function createInputElement(value, append, suffix) {
+ var el = document.createElement("input");
+ el.type = "text";
+ el.value = value;
+ el.id = "input" + (append ? "-appended" : "-not-appended") + (suffix ? suffix : "");
+ if (append) {
+ document.body.appendChild(el);
+ }
+ return el;
+ };
+
+ function createTextareaElement(value, append, suffix) {
+ var el = document.createElement("textarea");
+ el.value = value;
+
+ el.id = "textarea" + (append ? "-appended" : "-not-appended") + (suffix ? suffix : "");
+ if (append) {
+ document.body.appendChild(el);
+ }
+ return el;
+ };
+
+ function createPrefocusedInputElement(value, append) {
+ var el = createInputElement(value, append, "-prefocused");
+ el.focus();
+ el.blur();
+ return el;
+ }
+
+ function createPrefocusedTextareaElement(value, append) {
+ var el = createTextareaElement(value, append, "-prefocused");
+ el.focus();
+ el.blur();
+ return el;
+ }
+
+ function createClonedTextareaWithNoDirtyValueFlag(text) {
+ const textarea = document.createElement("textarea");
+ textarea.textContent = text;
+ return textarea.cloneNode(true);
+ }
+
+ function createTestElements(value) {
+ return [ createInputElement(value, true),
+ createInputElement(value, false),
+ createPrefocusedInputElement(value, true),
+ createPrefocusedInputElement(value, false),
+ createTextareaElement(value, true),
+ createTextareaElement(value, false),
+ createPrefocusedTextareaElement(value, true),
+ createPrefocusedTextareaElement(value, false),
+ createClonedTextareaWithNoDirtyValueFlag(value)
+ ];
+ }
+
+ var testValue = "abcdefghij";
+
+ test(function() {
+ assert_equals(testValue.length, 10);
+ }, "Sanity check for testValue length; if this fails, variou absolute offsets in the test below need to be adjusted to be less than testValue.length");
+
+ for (let prop of ["selectionStart", "selectionEnd"]) {
+ for (let el of createTestElements(testValue)) {
+ if (el.defaultValue !== el.value) {
+ test(function() {
+ assert_equals(el[prop], testValue.length);
+ }, `Initial .value set on ${el.id} should set ${prop} to end of value`);
+ }
+ }
+ }
+
+ test(function() {
+ for (let el of createTestElements(testValue)) {
+ var t = async_test(`onselect should fire when selectionStart is changed on ${el.id}`);
+ el.onselect = t.step_func_done(function(e) {
+ assert_equals(e.type, "select");
+ el.remove();
+ });
+ el.selectionStart = 2;
+ }
+ }, "onselect should fire when selectionStart is changed");
+
+ test(function() {
+ for (let el of createTestElements(testValue)) {
+ var t = async_test(`onselect should fire when selectionEnd is changed on ${el.id}`);
+ el.onselect = t.step_func_done(function(e) {
+ assert_equals(e.type, "select");
+ el.remove();
+ });
+ el.selectionEnd = 2;
+ }
+ }, "onselect should fire when selectionEnd is changed");
+
+ test(function() {
+ for (let el of createTestElements(testValue)) {
+ el.selectionStart = 0;
+ el.selectionEnd = 5;
+ el.selectionStart = 8;
+ assert_equals(el.selectionStart, 8, `selectionStart on ${el.id}`);
+ assert_equals(el.selectionEnd, 8, `selectionEnd on ${el.id}`);
+ el.remove();
+ }
+ }, "Setting selectionStart to a value larger than selectionEnd should increase selectionEnd");
+
+ test(function() {
+ for (let el of createTestElements(testValue)) {
+ el.selectionStart = 8;
+ el.selectionEnd = 5;
+ assert_equals(el.selectionStart, 5, `selectionStart on ${el.id}`);
+ assert_equals(el.selectionEnd, 5, `selectionEnd on ${el.id}`);
+ el.remove();
+ }
+ }, "Setting selectionEnd to a value smaller than selectionStart should decrease selectionStart");
+
+ test(function() {
+ for (let el of createTestElements(testValue)) {
+ el.selectionStart = 0;
+ assert_equals(el.selectionStart, 0, `We just set it on ${el.id}`);
+ el.selectionStart = -1;
+ assert_equals(el.selectionStart, testValue.length,
+ `selectionStart setter on ${el.id} should convert -1 to 2^32-1`);
+ el.selectionStart = Math.pow(2, 32);
+ assert_equals(el.selectionStart, 0,
+ `selectionStart setter on ${el.id} should convert 2^32 to 0`);
+ el.selectionStart = Math.pow(2, 32) - 1;
+ assert_equals(el.selectionStart, testValue.length,
+ `selectionStart setter on ${el.id} should leave 2^32-1 as-is`);
+ el.remove();
+ }
+ }, "selectionStart edge-case values");
+
+ test(function() {
+ for (let el of createTestElements(testValue)) {
+ el.selectionEnd = 0;
+ assert_equals(el.selectionEnd, 0, `We just set it on ${el.id}`);
+ el.selectionEnd = -1;
+ assert_equals(el.selectionEnd, testValue.length,
+ `selectionEnd setter on ${el.id} should convert -1 to 2^32-1`);
+ el.selectionEnd = Math.pow(2, 32);
+ assert_equals(el.selectionEnd, 0,
+ `selectionEnd setter on ${el.id} should convert 2^32 to 0`);
+ el.selectionEnd = Math.pow(2, 32) - 1;
+ assert_equals(el.selectionEnd, testValue.length,
+ `selectionEnd setter on ${el.id} should leave 2^32-1 as-is`);
+ el.remove();
+ }
+ }, "selectionEnd edge-case values");
+
+ test(() => {
+ for (const el of createTestElements(testValue)) {
+ el.selectionStart = 200;
+ assert_equals(el.selectionStart, testValue.length);
+ el.remove();
+ }
+ }, "selectionStart should be clamped by the current value length");
+
+ test(() => {
+ for (const el of createTestElements(testValue)) {
+ el.selectionStart = 300;
+ assert_equals(el.selectionEnd, testValue.length);
+ el.remove();
+ }
+ }, "selectionEnd should be clamped by the current value length");
+
+ test(() => {
+ for (const el of createTestElements(testValue)) {
+ el.setSelectionRange(200, 300);
+ assert_equals(el.selectionStart, testValue.length);
+ assert_equals(el.selectionEnd, testValue.length);
+ el.remove();
+ }
+ }, "setSelectionRange should be clamped by the current value length");
+
+ test(() => {
+ for (let el of createTestElements(testValue)) {
+ const start = 1;
+ const end = testValue.length - 1;
+
+ el.setSelectionRange(start, end);
+
+ assert_equals(el.selectionStart, start, `selectionStart on ${el.id}`);
+ assert_equals(el.selectionEnd, end, `selectionEnd on ${el.id}`);
+
+ el.selectionDirection = "backward";
+
+ assert_equals(el.selectionStart, start,
+ `selectionStart on ${el.id} after setting selectionDirection to "backward"`);
+ assert_equals(el.selectionEnd, end,
+ `selectionEnd on ${el.id} after setting selectionDirection to "backward"`);
+
+ el.selectionDirection = "forward";
+
+ assert_equals(el.selectionStart, start,
+ `selectionStart on ${el.id} after setting selectionDirection to "forward"`);
+ assert_equals(el.selectionEnd, end,
+ `selectionEnd on ${el.id} after setting selectionDirection to "forward"`);
+ }
+ }, "selectionStart and selectionEnd should remain the same when selectionDirection is changed");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html
new file mode 100644
index 0000000000..0fd8a5b182
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html
@@ -0,0 +1,127 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<div id=target></div>
+<script>
+ var target = document.getElementById("target");
+ var sometext = "something";
+ var shorttext = "abc";
+ var elemData = [
+ {
+ desc: "textarea not in body",
+ factory: () => document.createElement("textarea"),
+ },
+ {
+ desc: "input not in body",
+ factory: () => document.createElement("input"),
+ },
+ {
+ desc: "textarea in body",
+ factory: () => document.body.appendChild(document.createElement("textarea")),
+ },
+ {
+ desc: "input in body",
+ factory: () => document.body.appendChild(document.createElement("input")),
+ },
+ {
+ desc: "textarea in body with parsed default value",
+ factory: () => {
+ target.innerHTML = "<textarea>abcdefghij</textarea>"
+ return target.querySelector("textarea");
+ },
+ },
+ {
+ desc: "input in body with parsed default value",
+ factory: () => {
+ target.innerHTML = "<input value='abcdefghij'>"
+ return target.querySelector("input");
+ },
+ },
+ {
+ desc: "focused textarea",
+ factory: () => {
+ var t = document.body.appendChild(document.createElement("textarea"));
+ t.focus();
+ return t;
+ },
+ },
+ {
+ desc: "focused input",
+ factory: () => {
+ var i = document.body.appendChild(document.createElement("input"));
+ i.focus();
+ return i;
+ },
+ },
+ {
+ desc: "focused then blurred textarea",
+ factory: () => {
+ var t = document.body.appendChild(document.createElement("textarea"));
+ t.focus();
+ t.blur();
+ return t;
+ },
+ },
+ {
+ desc: "focused then blurred input",
+ factory: () => {
+ var i = document.body.appendChild(document.createElement("input"));
+ i.focus();
+ i.blur()
+ return i;
+ },
+ },
+ ];
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ el.defaultValue = sometext;
+ assert_true(sometext.length > 8,
+ "sometext too short, test won't work right");
+ el.selectionStart = 4;
+ el.selectionEnd = 6;
+ el.setRangeText("xyz");
+ el.defaultValue = "set range text";
+ assert_equals(el.value, sometext.slice(0, 4) + "xyz" + sometext.slice(6),
+ "Calling setRangeText should set the value dirty flag");
+ }, `value dirty flag behavior after setRangeText on ${data.desc}`);
+}
+
+for (var tag of ['input', 'textarea']) {
+ test(function() {
+ var el = document.createElement(tag);
+ document.body.appendChild(el);
+ this.add_cleanup(() => el.remove());
+
+ for (let val of ["", "foo", "foobar"]) {
+ el.value = val;
+ assert_equals(el.selectionStart, val.length,
+ "element.selectionStart should be value.length");
+ assert_equals(el.selectionEnd, val.length,
+ "element.selectionEnd should be value.length");
+ }
+ }, `selection is collapsed to the end after changing values on ${tag}`);
+
+ test(function() {
+ var el = document.createElement(tag);
+ document.body.appendChild(el);
+ this.add_cleanup(() => el.remove());
+
+ el.value = "foobar"
+ el.selectionStart = 2
+ el.selectionEnd = 4
+ el.value = "foobar"
+
+ assert_equals(el.selectionStart, 2,
+ "element.selectionStart should be unchanged");
+ assert_equals(el.selectionEnd, 4,
+ "element.selectionEnd should be unchanged");
+ }, `selection is not collapsed to the end when value is set to its existing value on ${tag}`);
+}
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection.html
new file mode 100644
index 0000000000..04ab7298e3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection.html
@@ -0,0 +1,206 @@
+<!DOCTYPE HTML>
+<title>test if select() API returns correct attributes</title>
+<meta charset="UTF-8">
+<meta name="timeout" content="long">
+<link rel="author" title="Koji Tashiro" href="mailto:koji.tashiro@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/association-of-controls-and-forms.html#textFieldSelection">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script>
+ var body = document.getElementsByTagName("body").item(0);
+ var dirs = ['forward', 'backward', 'none'];
+ var sampleText = "0123456789";
+
+ var createInputElement = function(value, append = true) {
+ var el = document.createElement("input");
+ el.type = "text";
+ el.value = value;
+ el.id = "input" + (append ? "-appended" : "-not-appended");
+ if (append) {
+ body.appendChild(el);
+ }
+ return el;
+ };
+
+ var createTextareaElement = function(value, append = true) {
+ var el = document.createElement("textarea");
+ el.value = value;
+ el.id = "textarea" + (append ? "-appended" : "-not-appended");
+ if (append) {
+ body.appendChild(el);
+ }
+ return el;
+ };
+
+
+ test(function() {
+ var text = 'a';
+ for (var i=0; i<255; i++) {
+ var el = createInputElement(text);
+ el.select();
+ var selectionText = el.value.substring(el.selectionStart, el.selectionEnd);
+ assert_equals(selectionText, text, "Selection text mismatched");
+ el.parentNode.removeChild(el);
+ text += 'a';
+ }
+ }, "test if selection text is correct for input");
+
+
+ test(function() {
+ var text = 'a';
+ for (var i=0; i<255; i++) {
+ var el = createTextareaElement(text);
+ el.select();
+ var selectionText = el.value.substring(el.selectionStart, el.selectionEnd);
+ assert_equals(selectionText, text, "Selection text mismatched");
+ el.parentNode.removeChild(el);
+ text += 'a';
+ }
+ }, "test if selection text is correct for textarea");
+
+
+ test(function() {
+ var text = 'あ';
+ for (var i=0; i<255; i++) {
+ var el = createInputElement(text);
+ el.select();
+ var selectionText = el.value.substring(el.selectionStart, el.selectionEnd);
+ assert_equals(selectionText, text, "Selection text mismatched");
+ el.parentNode.removeChild(el);
+ text += 'あ';
+ }
+ }, "test if non-ascii selection text is correct for input");
+
+
+ test(function() {
+ var text = 'あ';
+ for (var i=0; i<255; i++) {
+ var el = createTextareaElement(text);
+ el.select();
+ var selectionText = el.value.substring(el.selectionStart, el.selectionEnd);
+ assert_equals(selectionText, text, "Selection text mismatched");
+ el.parentNode.removeChild(el);
+ text += 'あ';
+ }
+ }, "test if non-ascii selection text is correct for textarea");
+
+
+ for (var append of [true, false]) {
+ test(function() {
+ var el = createInputElement(sampleText, append);
+ // If there is no selection, then it must return the offset(in logical order)
+ // to the character that immediately follows the text entry cursor.
+ assert_equals(el.selectionStart, el.value.length,
+ "SelectionStart offset without selection in " + el.id);
+ el.select();
+ assert_equals(el.selectionStart, 0, "SelectionStart offset");
+ el.remove();
+ }, "test SelectionStart offset for input that is " +
+ (append ? "appended" : " not appended"));
+ }
+
+ for (var append of [true, false]) {
+ test(function() {
+ var el = createTextareaElement(sampleText, append);
+ // If there is no selection, then it must return the offset(in logical order)
+ // to the character that immediately follows the text entry cursor.
+ assert_equals(el.selectionStart, el.value.length,
+ "SelectionStart offset without selection in " + el.id);
+ el.select();
+ assert_equals(el.selectionStart, 0, "SelectionStart offset");
+ el.remove();
+ }, "test SelectionStart offset for textarea that is " +
+ (append ? "appended" : " not appended"));
+ }
+
+ for (var append of [true, false]) {
+ test(function() {
+ var el = createInputElement(sampleText, append);
+ // If there is no selection, then it must return the offset(in logical order)
+ // to the character that immediately follows the text entry cursor.
+ assert_equals(el.selectionEnd, el.value.length,
+ "SelectionEnd offset without selection in " + el.id);
+ el.select();
+ assert_equals(el.selectionEnd, el.value.length, "SelectionEnd offset");
+ el.remove();
+ }, "test SelectionEnd offset for input that is " +
+ (append ? "appended" : " not appended"));
+ }
+
+
+ for (var append of [true, false]) {
+ test(function() {
+ var el = createTextareaElement(sampleText, append);
+ // If there is no selection, then it must return the offset(in logical order)
+ // to the character that immediately follows the text entry cursor.
+ assert_equals(el.selectionEnd, el.value.length,
+ "SelectionEnd offset without selection in " + el.id);
+ el.select();
+ assert_equals(el.selectionEnd, el.value.length, "SelectionEnd offset");
+ el.remove();
+ }, "test SelectionEnd offset for textarea that is " +
+ (append ? "appended" : " not appended"));
+ }
+
+ test(function() {
+ var el = createInputElement(sampleText);
+ assert_in_array(el.selectionDirection, dirs, "SelectionDirection");
+ el.select();
+ assert_in_array(el.selectionDirection, dirs, "SelectionDirection");
+ el.parentNode.removeChild(el);
+ }, "test SelectionDirection for input");
+
+
+ test(function() {
+ var el = createTextareaElement(sampleText);
+ assert_in_array(el.selectionDirection, dirs, "SelectionDirection");
+ el.select();
+ assert_in_array(el.selectionDirection, dirs, "SelectionDirection");
+ el.parentNode.removeChild(el);
+ }, "test SelectionDirection for textarea");
+
+ promise_test(async () => {
+ // cause a layout overflow
+ const el = createInputElement(sampleText.repeat(100));
+ el.selectionEnd = 0;
+ await new Promise(requestAnimationFrame);
+ assert_equals(el.scrollLeft, 0);
+
+ el.select();
+ await new Promise(requestAnimationFrame);
+ assert_equals(el.scrollLeft, 0);
+ el.remove();
+ }, `test scrollLeft for input`);
+
+ promise_test(async () => {
+ // cause a layout overflow
+ const el = createInputElement(sampleText.repeat(100));
+ el.scrollLeft = 33;
+
+ el.select();
+ await new Promise(requestAnimationFrame);
+ assert_equals(el.scrollLeft, 33);
+ el.remove();
+ }, `test scrollLeft preservation for input`);
+
+ for (const localName of ["input", "textarea"]) {
+ promise_test(async () => {
+ const container = document.createElement("div");
+ container.style.height = "100px";
+ container.style.overflow = "scroll";
+ const element = document.createElement(localName);
+ element.style.marginTop = "120px";
+ container.append(element);
+ document.body.append(container);
+
+ element.select();
+ await new Promise(requestAnimationFrame);
+ assert_equals(container.scrollTop, 0);
+
+ container.remove();
+ }, `test container.scrollTop for ${localName}`);
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/setSelectionRange.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/setSelectionRange.html
new file mode 100644
index 0000000000..bdf52a77f8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/setSelectionRange.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<textarea>
+
+</textarea>
+<script>
+test(function() {
+ let textarea = document.querySelector('textarea');
+ assert_equals(textarea.selectionStart, 0);
+ assert_equals(textarea.selectionEnd, 0);
+ textarea.setSelectionRange(0, 1);
+ assert_equals(textarea.selectionStart, 0);
+ assert_equals(textarea.selectionEnd, 1);
+}, "setSelectionRange on line boundaries");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/textarea-selection-while-parsing.xhtml b/testing/web-platform/tests/html/semantics/forms/textfieldselection/textarea-selection-while-parsing.xhtml
new file mode 100644
index 0000000000..57326bb4a8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/textarea-selection-while-parsing.xhtml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<textarea>
+a
+<script>document.querySelector('textarea').value = 'ggg';</script>
+b
+</textarea>
+<script>
+test(() => {
+ let ta = document.querySelector('textarea');
+ assert_equals(ta.selectionStart, 3);
+ assert_equals(ta.selectionEnd, 3);
+}, 'Value setter while parsing textarea children should move ' +
+ 'selection{Start,End} to the end');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setRangeText.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setRangeText.html
new file mode 100644
index 0000000000..b435fe7881
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setRangeText.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>text field selection: setRangeText</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#textFieldSelection">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #display_none {display:none;}
+</style>
+<div id="log"></div>
+<input type=text id=text value="foobar">
+<input type=search id=search value="foobar">
+<input type=tel id=tel value="foobar">
+<input type=url id=url value="foobar">
+<input type=password id=password value="foobar">
+<input id=display_none value="foobar">
+<textarea id=textarea>foobar</textarea>
+<script>
+ var input = document.createElement("input");
+ input.id = "input_not_in_doc";
+ input.value = "foobar";
+
+ var elements = [
+ document.getElementById("text"),
+ document.getElementById("search"),
+ document.getElementById("tel"),
+ document.getElementById("url"),
+ document.getElementById("password"),
+ document.getElementById("display_none"),
+ document.getElementById("textarea"),
+ input,
+ ]
+
+ function untilEvent(element, eventName) {
+ return new Promise((resolve) => {
+ element.addEventListener(eventName, resolve, { once: true });
+ });
+ }
+
+ elements.forEach(function(element) {
+ test(function() {
+ element.value = "foobar";
+ element.selectionStart = 0;
+ element.selectionEnd = 3;
+ assert_equals(element.selectionStart, 0);
+ assert_equals(element.selectionEnd, 3);
+ element.setRangeText("foobar2");
+ assert_equals(element.value, "foobar2bar");
+ assert_equals(element.selectionStart, 0);
+ assert_equals(element.selectionEnd, 7);
+ element.setRangeText("foobar3", 7, 10);
+ assert_equals(element.value, "foobar2foobar3");
+ }, element.id + " setRangeText with only one argument replaces the value between selectionStart and selectionEnd, otherwise replaces the value between 2nd and 3rd arguments");
+
+ test(function(){
+ element.value = "foobar";
+ element.selectionStart = 0;
+ element.selectionEnd = 0;
+
+ element.setRangeText("foobar2", 0, 3); // no 4th arg, default "preserve"
+ assert_equals(element.value, "foobar2bar");
+ assert_equals(element.selectionStart, 0);
+ assert_equals(element.selectionEnd, 0);
+ }, element.id + " selectionMode missing");
+
+ test(function(){
+ element.value = "foobar"
+ element.setRangeText("foo", 3, 6, "select");
+ assert_equals(element.value, "foofoo");
+ assert_equals(element.selectionStart, 3);
+ assert_equals(element.selectionEnd, 6);
+ }, element.id + " selectionMode 'select'");
+
+ test(function(){
+ element.value = "foobar"
+ element.setRangeText("foo", 3, 6, "start");
+ assert_equals(element.value, "foofoo");
+ assert_equals(element.selectionStart, 3);
+ assert_equals(element.selectionEnd, 3);
+ }, element.id + " selectionMode 'start'");
+
+ test(function(){
+ element.value = "foobar"
+ element.setRangeText("foobar", 3, 6, "end");
+ assert_equals(element.value, "foofoobar");
+ assert_equals(element.selectionStart, 9);
+ assert_equals(element.selectionEnd, 9);
+ }, element.id + " selectionMode 'end'");
+
+ test(function(){
+ element.value = "foobar"
+ element.selectionStart = 0;
+ element.selectionEnd = 5;
+ assert_equals(element.selectionStart, 0);
+ element.setRangeText("", 3, 6, "preserve");
+ assert_equals(element.value, "foo");
+ assert_equals(element.selectionStart, 0);
+ assert_equals(element.selectionEnd, 3);
+ }, element.id + " selectionMode 'preserve'");
+
+ test(function(){
+ assert_throws_dom("INDEX_SIZE_ERR", function() {
+ element.setRangeText("barfoo", 2, 1);
+ });
+ }, element.id + " setRangeText with 3rd argument greater than 2nd argument throws an IndexSizeError exception");
+
+ test(function(){
+ assert_throws_js(TypeError, function() {
+ element.setRangeText();
+ });
+ }, element.id + " setRangeText without argument throws a type error");
+
+ promise_test(async (t) => {
+ // At this point there are already "select" events queued up on
+ // "element". Give them time to fire; otherwise we can get spurious
+ // passes.
+ //
+ // This is unfortunately racy in that we might _still_ get spurious
+ // passes. I'm not sure how best to handle that.
+ t.step_timeout(function() {
+ var q = false;
+ element.onselect = t.step_func_done(function(e) {
+ assert_true(q, "event should be queued");
+ assert_true(e.isTrusted, "event is trusted");
+ assert_true(e.bubbles, "event bubbles");
+ assert_false(e.cancelable, "event is not cancelable");
+ });
+ element.setRangeText("foobar2", 0, 6);
+ q = true;
+ }, 10);
+ }, element.id + " setRangeText fires a select event");
+
+ promise_test(async () => {
+ element.value = "XXXXXXXXXXXXXXXXXXX";
+ const { length } = element.value;
+ element.setSelectionRange(0, length);
+ await untilEvent(element, "select");
+ element.setRangeText("foo", 2, 2);
+ await untilEvent(element, "select");
+ assert_equals(element.selectionStart, 0, ".selectionStart");
+ assert_equals(element.selectionEnd, length + 3, ".selectionEnd");
+ }, element.id + " setRangeText fires a select event when fully selected");
+
+ promise_test(async () => {
+ element.value = "XXXXXXXXXXXXXXXXXXX";
+ element.select();
+ await untilEvent(element, "select");
+ element.setRangeText("foo", 2, 2);
+ await untilEvent(element, "select");
+ assert_equals(element.selectionStart, 0, ".selectionStart");
+ assert_equals(element.selectionEnd, element.value.length, ".selectionEnd");
+ }, element.id + " setRangeText fires a select event after select()");
+ })
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setSelectionRange.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setSelectionRange.html
new file mode 100644
index 0000000000..3aba6b7adb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setSelectionRange.html
@@ -0,0 +1,304 @@
+<!DOCTYPE HTML>
+<title>Test of text field setSelectionRange</title>
+<link rel="author" title="Takeharu.Oshida" href="mailto:georgeosddev@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="hide" style="display: block">
+ <input id="a" type="text" value="abcde">
+ <textarea id="b">abcde</textarea>
+</div>
+<script>
+var expected_direction_none;
+setup(function() {
+ var input = document.createElement("input");
+ input.setSelectionRange(0, 1, "none");
+ var direction = input.selectionDirection;
+ if (direction !== "none" && direction !== "forward") {
+ throw new Error("Unexpected direction");
+ }
+ expected_direction_none = direction;
+});
+
+test(function() {
+ var input = document.getElementById("a");
+ test(function() {
+ assert_equals(typeof(input.setSelectionRange), "function", "element must have 'setSelectionRange' function");
+ },"input typeof(input.setSelectionRange)'");
+
+ test(function() {
+ assert_equals(input.setSelectionRange(0,1,"forward"),undefined,"setSelectionRange is void functuon");
+ },"input setSelectionRange return void");
+
+ test(function() {
+ input.setSelectionRange(0,1)
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(0,1)');
+
+ test(function() {
+ input.setSelectionRange(0,input.value.length+1)
+ assert_equals(input.selectionEnd, input.value.length, "Arguments greater than the length of the value of the text field must be treated as pointing at the end of the text field");
+ },'input setSelectionRange(0,input.value.length+1)');
+
+ test(function() {
+ input.setSelectionRange(input.value.length+1,input.value.length+1)
+ assert_equals(input.selectionStart, input.value.length, "Arguments (start) greater than the length of the value of the text field must be treated as pointing at the end of the text field");
+ assert_equals(input.selectionEnd, input.value.length, "Arguments (end) greater than the length of the value of the text field must be treated as pointing at the end of the text field");
+ },'input setSelectionRange(input.value.length+1,input.value.length+1)');
+
+ test(function() {
+ input.setSelectionRange(input.value.length+1,1)
+ assert_equals(input.selectionStart, 1, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(input.value.length+1,1)');
+
+ test(function() {
+ input.setSelectionRange(2,2)
+ assert_equals(input.selectionStart, 2, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ assert_equals(input.selectionEnd, 2, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ },'input setSelectionRange(2,2)');
+
+ test(function() {
+ input.setSelectionRange(2,1)
+ assert_equals(input.selectionStart, 1, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ assert_equals(input.selectionEnd, 1, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ },'input setSelectionRange(2,1)');
+
+ test(function() {
+ input.setSelectionRange(0,1,"backward")
+ assert_equals(input.selectionDirection, "backward", 'The direction of the selection must be set to backward if direction is a case-sensitive match for the string "backward"');
+ },'input direction of setSelectionRange(0,1,"backward")');
+
+ test(function() {
+ input.setSelectionRange(0,1,"forward")
+ assert_equals(input.selectionDirection, "forward", 'The direction of the selection must be set to forward if direction is a case-sensitive match for the string "forward"');
+ },'input direction of setSelectionRange(0,1,"forward")');
+
+ test(function() {
+ input.setSelectionRange(0,1,"none")
+ assert_equals(input.selectionDirection, expected_direction_none);
+ },'input direction of setSelectionRange(0,1,"none")');
+
+ test(function() {
+ input.setSelectionRange(0,1,"hoge")
+ assert_equals(input.selectionDirection, expected_direction_none);
+ },'input direction of setSelectionRange(0,1,"hoge")');
+
+ test(function() {
+ input.setSelectionRange(0,1,"BACKWARD")
+ assert_equals(input.selectionDirection, expected_direction_none);
+ },'input direction of setSelectionRange(0,1,"BACKWARD")');
+
+ test(function() {
+ input.setSelectionRange(0,1)
+ assert_equals(input.selectionDirection, expected_direction_none);
+ },'input direction of setSelectionRange(0,1)');
+
+ test(function() {
+ input.setSelectionRange(1,-1);
+ assert_equals(input.selectionStart, 1, "element.selectionStart should be 1");
+ assert_equals(input.selectionEnd, input.value.length, "ECMAScript conversion to unsigned long");
+ },'input setSelectionRange(1,-1)');
+
+ test(function() {
+ input.setSelectionRange(-1,1);
+ assert_equals(input.selectionStart, 1, "ECMAScript conversion to unsigned long + if end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(-1,1)');
+
+ test(function() {
+ input.setSelectionRange("string",1);
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange("string",1)');
+
+ test(function() {
+ input.setSelectionRange(true,1);
+ assert_equals(input.selectionStart, 1, "element.selectionStart should be 1");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(true,1)');
+
+ test(function() {
+ input.setSelectionRange([],1);
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange([],1)');
+
+ test(function() {
+ input.setSelectionRange({},1);
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange({},1)');
+
+ test(function() {
+ input.setSelectionRange(NaN,1);
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(NaN,1)');
+
+ test(function() {
+ input.setSelectionRange(null,1);
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(null,1)');
+
+ test(function() {
+ input.setSelectionRange(undefined,1);
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(undefined,1)');
+
+ test(function() {
+ input.setSelectionRange(Math.pow(2,32) - 2, Math.pow(2,32) - 1);
+ assert_equals(input.selectionStart, input.value.length,
+ "element.selectionStart should be value.length");
+ assert_equals(input.selectionEnd, input.value.length,
+ "element.selectionEnd should be value.length");
+ }, 'input setSelectionRange(Math.pow(2,32) - 2, Math.pow(2,32) - 1)');
+
+ test(function() {
+ input.setSelectionRange(Math.pow(2,31), Math.pow(2,32) - 1);
+ assert_equals(input.selectionStart, input.value.length,
+ "element.selectionStart should be value.length");
+ assert_equals(input.selectionEnd, input.value.length,
+ "element.selectionEnd should be value.length");
+ }, 'input setSelectionRange(Math.pow(2,31), Math.pow(2,32) - 1)');
+},"test of input.setSelectionRange");
+
+async_test(function() {
+ var q = false;
+ var input = document.getElementById("a");
+ input.addEventListener("select", this.step_func_done(function(e) {
+ assert_true(q, "event should be queued");
+ assert_true(e.isTrusted, "event is trusted");
+ assert_true(e.bubbles, "event bubbles");
+ assert_false(e.cancelable, "event is not cancelable");
+ }));
+ input.setSelectionRange(0, 1);
+ q = true;
+}, "input setSelectionRange fires a select event");
+
+test(function() {
+ var textarea = document.getElementById("b");
+ test(function() {
+ assert_equals(typeof(textarea.setSelectionRange), "function", "element must have 'setSelectionRange' function");
+ },"textarea typeof(input.setSelectionRange)'");
+
+ test(function() {
+ assert_equals(textarea.setSelectionRange(0,1,"forward"),undefined,"setSelectionRange is void functuon");
+ },"textarea setSelectionRange return void");
+
+ test(function() {
+ textarea.setSelectionRange(0,1)
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'textarea setSelectionRange(0,1)');
+
+ test(function() {
+ textarea.setSelectionRange(0,textarea.value.length+1)
+ assert_equals(textarea.selectionEnd, textarea.value.length, "Arguments greater than the length of the value of the text field must be treated as pointing at the end of the text field");
+ },'textarea setSelectionRange(0,textarea.value.length+1)');
+
+ test(function() {
+ textarea.setSelectionRange(2,2)
+ assert_equals(textarea.selectionStart, 2, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ assert_equals(textarea.selectionEnd, 2, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ },'textarea setSelectionRange(2,2)');
+
+ test(function() {
+ textarea.setSelectionRange(2,1)
+ assert_equals(textarea.selectionStart, 1, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ assert_equals(textarea.selectionEnd, 1, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ },'textarea setSelectionRange(2,1)');
+
+ test(function() {
+ textarea.setSelectionRange(0,1,"backward")
+ assert_equals(textarea.selectionDirection, "backward", 'The direction of the selection must be set to backward if direction is a case-sensitive match for the string "backward"');
+ },'textarea direction of setSelectionRange(0,1,"backward")');
+
+ test(function() {
+ textarea.setSelectionRange(0,1,"forward")
+ assert_equals(textarea.selectionDirection, "forward", 'The direction of the selection must be set to forward if direction is a case-sensitive match for the string "forward"');
+ },'textarea direction of setSelectionRange(0,1,"forward")');
+
+ test(function() {
+ textarea.setSelectionRange(0,1,"none")
+ assert_equals(textarea.selectionDirection, expected_direction_none);
+ },'textarea direction of setSelectionRange(0,1,"none")');
+
+ test(function() {
+ textarea.setSelectionRange(0,1,"hoge")
+ assert_equals(textarea.selectionDirection, expected_direction_none);
+ },'textarea direction of setSelectionRange(0,1,"hoge")');
+
+ test(function() {
+ textarea.setSelectionRange(0,1,"BACKWARD")
+ assert_equals(textarea.selectionDirection, expected_direction_none);
+ },'textarea direction of setSelectionRange(0,1,"BACKWARD")');
+
+ test(function() {
+ textarea.setSelectionRange(0,1)
+ assert_equals(textarea.selectionDirection, expected_direction_none);
+ },'textarea direction of setSelectionRange(0,1)');
+
+ test(function() {
+ textarea.setSelectionRange("string",1);
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange("string",1)');
+
+ test(function() {
+ textarea.setSelectionRange(true,1);
+ assert_equals(textarea.selectionStart, 1, "element.selectionStart should be 1");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange(true,1)');
+
+ test(function() {
+ textarea.setSelectionRange([],1);
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange([],1)');
+
+ test(function() {
+ textarea.setSelectionRange({},1);
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange({},1)');
+
+ test(function() {
+ textarea.setSelectionRange(NaN,1);
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange(NaN,1)');
+
+ test(function() {
+ textarea.setSelectionRange(null,1);
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange(null,1)');
+
+ test(function() {
+ textarea.setSelectionRange(undefined,1);
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange(undefined,1)');
+
+ test(function() {
+ textarea.setSelectionRange(Math.pow(2,32) - 2, Math.pow(2,32) - 1);
+ assert_equals(textarea.selectionStart, textarea.value.length,
+ "element.selectionStart should be value.length");
+ assert_equals(textarea.selectionEnd, textarea.value.length,
+ "element.selectionEnd should be value.length");
+ }, 'textarea setSelectionRange(Math.pow(2,32) - 2, Math.pow(2,32) - 1)');
+
+ test(function() {
+ textarea.setSelectionRange(Math.pow(2,31), Math.pow(2,32) - 1);
+ assert_equals(textarea.selectionStart, textarea.value.length,
+ "element.selectionStart should be value.length");
+ assert_equals(textarea.selectionEnd, textarea.value.length,
+ "element.selectionEnd should be value.length");
+ }, 'textarea setSelectionRange(Math.pow(2,31), Math.pow(2,32) - 1)');
+},"test of textarea.setSelectionRange");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/active-onblur.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/active-onblur.html
new file mode 100644
index 0000000000..b9e1b9705e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/active-onblur.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<link rel="help" href="http://crbug.com/945854">
+<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>
+
+<!-- This behavior is not explicitly specified. -->
+
+<button id=b1>button one</button>
+<button id=b2>button two</button>
+
+<script>
+promise_test(async () => {
+ b1.focus();
+
+ // Hold spacebar down
+ await (new test_driver.Actions()).keyDown('\uE00D').send();
+ assert_equals(document.querySelector(':active'), b1,
+ 'Buttons should be :active while the spacebar is pressed down.');
+
+ // Press tab
+ await (new test_driver.Actions()).keyDown('\uE004').keyUp('\uE004').send();
+ assert_equals(document.querySelector(':active'), null,
+ 'Buttons should not be :active after tab is used to change focus.');
+
+ // Release spacebar
+ await (new test_driver.Actions()).keyUp('\uE00D').send();
+}, 'Buttons should clear :active when the user tabs away from them while holding spacebar.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-activate-frame.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-activate-frame.html
new file mode 100644
index 0000000000..37619d7912
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-activate-frame.html
@@ -0,0 +1,3 @@
+<form action="about:blank">
+ <button id="submit">Submit</button>
+</form>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-activate-keyup-prevented.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-activate-keyup-prevented.html
new file mode 100644
index 0000000000..29ffbbdd99
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-activate-keyup-prevented.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Button activation submits on keyup, but not if keydown is defaultPrevented</title>
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1481400">
+<link rel=author href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel=author href="https://mozilla.org" title="Mozilla">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<button>The button</button>
+<script>
+let button = document.querySelector("button");
+promise_test(async t => {
+ button.focus();
+ assert_equals(document.activeElement, button, "Button should be focused");
+ let clickPromise = new Promise(resolve => {
+ button.addEventListener("click", resolve, { once: true });
+ });
+
+ await test_driver.send_keys(button, " ");
+
+ await clickPromise;
+
+ assert_true(true, "Button should have activated");
+
+ document.addEventListener("keydown", t.step_func(function(e) {
+ e.preventDefault();
+ }));
+
+ button.addEventListener("click", t.unreached_func("button got incorrectly activated"));
+
+ await test_driver.send_keys(button, " ");
+
+ await new Promise(resolve => t.step_timeout(resolve, 0));
+ assert_true(true, "Button should not have activated");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-activate.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-activate.html
new file mode 100644
index 0000000000..43fe96d398
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-activate.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta charset="utf-8">
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe src="button-activate-frame.html" onload="runTest()"></iframe>
+<script>
+var t = async_test("button activation behaviour submits form");
+function runTest() {
+ var iframe = document.querySelector('iframe');
+ iframe.onload = t.step_func(function() {
+ t.done();
+ });
+ var doc = iframe.contentDocument;
+ doc.querySelector('button').click();
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-checkvalidity.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-checkvalidity.html
new file mode 100644
index 0000000000..55d3091a0a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-checkvalidity.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>button_checkValidity</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <p><button id='button_id'>button</button></p>
+ </form>
+ <script>
+
+ var button = document.getElementById("button_id");
+
+ try
+ {
+ var ret = button.checkValidity();
+
+ test(function() {
+ assert_equals(ret, true, "calling of checkValidity method is failed.");
+ });
+ }
+ catch (e) {
+ test(function() {
+ assert_unreached("autofocus attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-click-submits.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-click-submits.html
new file mode 100644
index 0000000000..f09d06080f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-click-submits.html
@@ -0,0 +1,210 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Clicking a button should submit the form</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-button-type-submit-state">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+async_test(t => {
+
+ const form = document.createElement("form");
+ const button = document.createElement("button");
+ form.appendChild(button);
+ document.body.appendChild(form);
+
+ form.addEventListener("submit", t.step_func_done(ev => {
+ ev.preventDefault();
+ assert_equals(ev.target, form);
+ }));
+
+ button.click();
+
+}, "clicking a button with .click() should trigger a submit (form connected)");
+
+async_test(t => {
+
+ const form = document.createElement("form");
+ const button = document.createElement("button");
+ form.appendChild(button);
+
+ form.addEventListener("submit", t.step_func_done(ev => {
+ ev.preventDefault();
+ assert_unreached("Form should not be submitted");
+ }));
+
+ button.click();
+ t.step_timeout(() => t.done(), 500);
+
+}, "clicking a button with .click() should not trigger a submit (form disconnected)");
+
+async_test(t => {
+
+ const form = document.createElement("form");
+ const button = document.createElement("button");
+ form.appendChild(button);
+ document.body.appendChild(form);
+
+ form.addEventListener("submit", t.step_func_done(ev => {
+ ev.preventDefault();
+ assert_equals(ev.target, form);
+ }));
+
+ const e = new MouseEvent("click");
+ button.dispatchEvent(e);
+
+}, "clicking a button by dispatching an event should trigger a submit (form connected)");
+
+async_test(t => {
+
+ const form = document.createElement("form");
+ const button = document.createElement("button");
+ form.appendChild(button);
+
+ form.addEventListener("submit", t.step_func_done(ev => {
+ ev.preventDefault();
+ assert_unreached("Form should not be submitted");
+ }));
+
+ const e = new MouseEvent("click");
+ button.dispatchEvent(e);
+ t.step_timeout(() => t.done(), 500);
+
+}, "clicking a button by dispatching an event should not trigger a submit (form disconnected)");
+
+async_test(t => {
+
+ const form = document.createElement("form");
+ const button = document.createElement("button");
+ form.appendChild(button);
+
+ form.addEventListener("submit", t.step_func_done(ev => {
+ ev.preventDefault();
+ assert_unreached("Form should not be submitted");
+ }));
+
+ button.addEventListener("click", t.step_func(ev => {
+ ev.preventDefault();
+ t.step_timeout(() => t.done(), 500);
+ }));
+ button.click();
+
+}, "clicking a button that cancels the event should not trigger a submit");
+
+async_test(t => {
+
+ const form = document.createElement("form");
+ const button = document.createElement("button");
+ button.setAttribute("disabled", "");
+ form.appendChild(button);
+ document.body.appendChild(form);
+
+ form.addEventListener("submit", t.step_func_done(ev => {
+ ev.preventDefault();
+ assert_unreached("Form should not be submitted");
+ }));
+
+ button.click();
+ t.step_timeout(() => t.done(), 500);
+
+}, "clicking a disabled button (via disabled attribute) should not trigger submit");
+
+async_test(t => {
+
+ const form = document.createElement("form");
+ form.innerHTML = `<fieldset disabled><button>hello</button></fieldset>`;
+ const button = form.querySelector("button");
+ document.body.appendChild(form);
+
+ form.addEventListener("submit", t.step_func_done(ev => {
+ ev.preventDefault();
+ assert_unreached("Form should not be submitted");
+ }));
+
+ button.click();
+ t.step_timeout(() => t.done(), 500);
+
+}, "clicking a disabled button (via ancestor fieldset) should not trigger submit");
+
+test(t => {
+
+ const form = document.createElement("form");
+ form.innerHTML = `<fieldset disabled><legend><button>hello</button></legend></fieldset>`;
+ const button = form.querySelector("button");
+ document.body.appendChild(form);
+
+ form.addEventListener("submit", t.step_func_done(ev => {
+ ev.preventDefault();
+ assert_equals(ev.target, form);
+ }));
+
+ button.click();
+
+}, "clicking a button inside a disabled fieldset's legend *should* trigger submit");
+
+async_test(t => {
+
+ const form = document.createElement("form");
+ const button = document.createElement("button");
+ const span = document.createElement("span");
+ button.appendChild(span);
+ form.appendChild(button);
+ document.body.appendChild(form);
+
+ form.addEventListener("submit", t.step_func_done(ev => {
+ ev.preventDefault();
+ assert_equals(ev.target, form);
+ }));
+
+ span.click();
+
+}, "clicking the child of a button with .click() should trigger a submit");
+
+async_test(t => {
+
+ const form = document.createElement("form");
+ const button = document.createElement("button");
+ const span = document.createElement("span");
+ button.appendChild(span);
+ form.appendChild(button);
+ document.body.appendChild(form);
+
+ form.addEventListener("submit", t.step_func_done(ev => {
+ ev.preventDefault();
+ assert_equals(ev.target, form);
+ }));
+
+ const e = new MouseEvent("click", { bubbles: true });
+ span.dispatchEvent(e);
+
+}, "clicking the child of a button by dispatching a bubbling event should trigger a submit");
+
+async_test(t => {
+
+ const form = document.createElement("form");
+ const button = document.createElement("button");
+ const span = document.createElement("span");
+ button.appendChild(span);
+ form.appendChild(button);
+ document.body.appendChild(form);
+
+ form.addEventListener("submit", t.step_func_done(ev => {
+ ev.preventDefault();
+ assert_unreached("Form should not be submitted");
+ }));
+
+ span.addEventListener("click", t.step_func(ev => {
+ ev.preventDefault();
+ t.step_timeout(() => t.done(), 500);
+ }));
+
+ const e = new MouseEvent("click", { bubbles: false });
+ span.dispatchEvent(e);
+
+}, "clicking the child of a button by dispatching a non-bubbling event should not trigger submit");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-events.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-events.html
new file mode 100644
index 0000000000..be7806e1ee
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-events.html
@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: Button - events</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-button-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form name="fm1" style="display:none">
+ <button id="btn">BUTTON</button>
+ <button id="menu_btn" type="menu" menu="menu">MENU BUTTON</button>
+</form>
+<script>
+
+var btn = document.getElementById("btn"),
+ menu_btn = document.getElementById("menu_btn"),
+ t1 = async_test("The submit event must be fired when click a button in submit status"),
+ t2 = async_test("The reset event must be fired when click a button in reset status");
+ t3 = async_test("type=button shouldn't trigger submit or reset events");
+ t4 = async_test("Switching from type=button to type=submit should submit the form");
+ t5 = async_test("Switching from type=button to type=reset should reset the form");
+ t6 = async_test("Innermost button should submit its form");
+ t7 = async_test("Innermost button should reset its form");
+ t8 = async_test("Anchor inside a button should be prevent button activation");
+ t9 = async_test("input type=submit inside a button should be prevent button activation");
+
+document.forms.fm1.onsubmit = t1.step_func(function (evt) {
+ evt.preventDefault();
+ assert_true(evt.isTrusted, "The isTrusted attribute of the submit event should be true.");
+ assert_true(evt.bubbles, "The bubbles attribute of the submit event should be true.");
+ assert_true(evt.cancelable, "The cancelable attribute of the submit event should be true.");
+ assert_true(evt instanceof Event, "The submit event is an instance of Event interface.");
+ t1.done();
+});
+
+document.forms.fm1.onreset = t2.step_func(function (evt) {
+ assert_true(evt.isTrusted, "The isTrusted attribute of the reset event should be true.");
+ assert_true(evt.bubbles, "The bubbles attribute of the reset event should be true.");
+ assert_true(evt.cancelable, "The cancelable attribute of the reset event should be true.");
+ assert_true(evt instanceof Event, "The reset event is an instance of Event interface.");
+ t2.done();
+});
+
+t1.step(function () {
+ btn.type = "submit";
+ assert_equals(btn.type, "submit", "The button type should be 'submit'.");
+ btn.click();
+});
+
+t2.step(function () {
+ btn.type = "reset";
+ assert_equals(btn.type, "reset", "The button type should be 'reset'.");
+ btn.click();
+});
+
+t3.step(function () {
+ btn.type = "button";
+ assert_equals(btn.type, "button", "The button type should be 'button'.");
+ document.forms.fm1.onsubmit = t3.step_func(function (evt) {
+ assert_unreached("type=button shouldn't trigger submission.");
+ });
+ document.forms.fm1.onreset = t3.step_func(function (evt) {
+ assert_unreached("type=button shouldn't reset the form.");
+ });
+ btn.click();
+ t3.done();
+});
+
+t4.step(function () {
+ btn.type = "button";
+ btn.onclick = function() { btn.type = "submit"; }
+ document.forms.fm1.onsubmit = t4.step_func(function (evt) {
+ evt.preventDefault();
+ assert_equals(btn.type, "submit", "The button type should be 'submit'.");
+ t4.done();
+ });
+ btn.click();
+});
+
+t5.step(function () {
+ btn.type = "button";
+ btn.onclick = function() { btn.type = "reset"; }
+ document.forms.fm1.onreset = t5.step_func(function (evt) {
+ evt.preventDefault();
+ assert_equals(btn.type, "reset", "The button type should be 'reset'.");
+ t5.done();
+ });
+ btn.click();
+});
+
+t6.step(function () {
+ btn.type = "submit";
+ btn.innerHTML = "";
+ var fm2 = document.createElement("form");
+ var btn2 = document.createElement("button");
+ btn2.type = "submit";
+ fm2.appendChild(btn2);
+ btn.appendChild(fm2);
+ assert_true(document.forms.fm1.contains(fm2), "Should have nested forms");
+
+ function submitListener(evt) {
+ evt.preventDefault();
+ assert_equals(evt.target, fm2, "Innermost form should have got the submit event");
+ };
+ window.addEventListener("submit", submitListener, true);
+ btn2.click();
+ window.removeEventListener("submit", submitListener, true);
+ t6.done();
+});
+
+t7.step(function () {
+ btn.type = "reset";
+ btn.innerHTML = "";
+ var fm2 = document.createElement("form");
+ var btn2 = document.createElement("button");
+ btn2.type = "reset";
+ fm2.appendChild(btn2);
+ btn.appendChild(fm2);
+ assert_true(document.forms.fm1.contains(fm2), "Should have nested forms");
+
+ function resetListener(evt) {
+ evt.currentTarget.removeEventListener(evt.type, resetListener, true);
+ evt.preventDefault();
+ assert_equals(evt.target, fm2, "Innermost form should have got the reset event");
+ t7.done();
+ };
+ window.addEventListener("reset", resetListener, true);
+ btn2.click();
+});
+
+t8.step(function () {
+ btn.type = "submit";
+ btn.innerHTML = "";
+ var a = document.createElement("a");
+ a.href = "#";
+ btn.appendChild(a);
+ document.forms.fm1.onsubmit = t8.step_func(function (evt) {
+ assert_unreached("type=button shouldn't trigger submission.");
+ });
+
+ a.click();
+ t8.done();
+});
+
+t9.step(function () {
+ btn.type = "submit";
+ btn.innerHTML = "";
+ var fm2 = document.createElement("form");
+ var btn2 = document.createElement("input");
+ btn2.type = "submit";
+ fm2.appendChild(btn2);
+ btn.appendChild(fm2);
+ assert_true(document.forms.fm1.contains(fm2), "Should have nested forms");
+
+ function submitListener(evt) {
+ evt.preventDefault();
+ assert_equals(evt.target, fm2, "Innermost form should have got the submit event");
+ };
+
+ window.addEventListener("submit", submitListener, true);
+ btn2.click();
+ window.removeEventListener("submit", submitListener, true);
+ t9.done();
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-labels.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-labels.html
new file mode 100644
index 0000000000..b06c71f95d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-labels.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>button_labels</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <p><label>Full name:<label>(name)<button id='button_id1'>button1</button><small>Format: First Last</small></label></label></p>
+ <p><label>Age: <button id='button_id2'>button2</button></label></p>
+ </form>
+ <script>
+ test(function() {
+ var button1 = document.getElementById("button_id1");
+ var button2 = document.getElementById("button_id2");
+
+ assert_true(button1.labels instanceof NodeList, "button1.labels is NodeList");
+ assert_equals(button1.labels.length, 2, "button1.labels.length");
+
+ assert_true(button2.labels instanceof NodeList, "button2.labels is NodeList");
+ assert_equals(button2.labels.length, 1, "button2.labels.length");
+ });
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-menu-historical.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-menu-historical.html
new file mode 100644
index 0000000000..fe68be0fd0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-menu-historical.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<title>Test that nobody implemented the now-removed menu type and attribute on button</title>
+<meta charset="utf-8">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://github.com/whatwg/html/pull/2342">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<button id="b" type="menu" menu="m">button</button>
+<menu id="m"></menu>
+
+<script>
+"use strict";
+
+const button = document.querySelector("button");
+
+test(() => {
+ assert_false('menu' in button, 'The menu property must not exist on the button');
+ assert_equals(button.menu, undefined, 'The value of the menu property on the button must be undefined');
+}, 'button.menu, the potentially-reflecting IDL attribute, does not exist');
+
+test(() => {
+ assert_equals(button.type, 'submit', 'The type property must reflect as its invalid value default of submit');
+}, 'button.type reflects properly');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-setcustomvalidity.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-setcustomvalidity.html
new file mode 100644
index 0000000000..1747bd727a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-setcustomvalidity.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<title>button setCustomValidity</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<button id='button_test'></button>
+
+<script>
+
+test(() => {
+ let elem = document.getElementById("button_test");
+ assert_false(elem.validity.customError);
+ elem.setCustomValidity("custom error");
+ assert_true(elem.validity.customError);
+}, "button setCustomValidity is correct")
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-children.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-children.html
new file mode 100644
index 0000000000..06218f4fc9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-children.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe name=frame1 id=frame1></iframe>
+<form id=form1 target=frame1 action="does_not_exist.html">
+ <button id=submitbutton type=submit>
+ <div id=buttonchilddiv>
+ button child div text
+ </div>
+ </button>
+</form>
+
+<script>
+async_test(t => {
+ window.addEventListener('load', () => {
+ const frame1 = document.getElementById('frame1');
+ frame1.addEventListener('load', t.step_func_done(() => {}));
+
+ const submitButton = document.getElementById('submitbutton');
+ submitButton.addEventListener('click', event => {
+ event.preventDefault();
+ const form = document.getElementById('form1');
+ form.submit();
+ });
+
+ const buttonChildDiv = document.getElementById('buttonchilddiv');
+ buttonChildDiv.click();
+ });
+}, 'This test will pass if a form navigation successfully occurs when clicking a child element of a <button type=submit> element with a onclick event handler which prevents the default form submission and manually calls form.submit() instead.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-remove-children-jssubmit.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-remove-children-jssubmit.html
new file mode 100644
index 0000000000..26ce16dd2d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-remove-children-jssubmit.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-2">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe name=frame1 id=frame1></iframe>
+<form id=form1 target=frame1 action="does_not_exist.html">
+ <button id=submitbutton type=submit>
+ <span id=outerchild>
+ <span id=innerchild>submit</span>
+ </span>
+ </button>
+</form>
+
+<script>
+async_test(t => {
+ window.addEventListener('load', () => {
+ const frame1 = document.getElementById('frame1');
+ frame1.addEventListener('load', t.step_func_done(() => {}));
+
+ const submitButton = document.getElementById('submitbutton');
+ submitButton.addEventListener('click', event => {
+ document.getElementById('outerchild').remove();
+ document.getElementById('form1').submit();
+ });
+
+ document.getElementById('innerchild').click();
+ });
+}, 'This test will pass if a form navigation successfully occurs when clicking a child element of a <button type=submit> element with a onclick event handler which removes the button\'s child and then calls form.submit().');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-remove-children.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-remove-children.html
new file mode 100644
index 0000000000..1dc259564c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-remove-children.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-2">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe name=frame1 id=frame1></iframe>
+<form id=form1 target=frame1 action="does_not_exist.html">
+ <button id=submitbutton type=submit>
+ <span id=outerchild>
+ <span id=innerchild>submit</span>
+ </span>
+ </button>
+</form>
+
+<script>
+async_test(t => {
+ window.addEventListener('load', () => {
+ const frame1 = document.getElementById('frame1');
+ frame1.addEventListener('load', t.step_func_done(() => {}));
+
+ const submitButton = document.getElementById('submitbutton');
+ submitButton.addEventListener('click', event => {
+ document.getElementById('outerchild').remove();
+ });
+
+ document.getElementById('innerchild').click();
+ });
+}, 'This test will pass if a form navigation successfully occurs when clicking a child element of a <button type=submit> element with a onclick event handler which removes the button\'s child.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-remove-jssubmit.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-remove-jssubmit.html
new file mode 100644
index 0000000000..6e71d958d6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-remove-jssubmit.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-2">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe name="frame" id="frame"></iframe>
+<form id="form" target="frame" action="does_not_exist.html">
+ <input id="input" name="name" value="foo">
+ <button id="submitbutton" type="submit">Submit</button>
+</form>
+
+<script>
+async_test(t => {
+ window.addEventListener('load', () => {
+ const frame = document.getElementById('frame');
+ frame.addEventListener('load', t.step_func_done(() => {
+ const expected = (new URL("does_not_exist.html?name=bar", location.href)).href;
+ assert_equals(frame.contentWindow.location.href, expected);
+ }));
+
+ const form = document.getElementById('form');
+ const input = document.getElementById('input');
+ const submitButton = document.getElementById('submitbutton');
+ submitButton.addEventListener('click', event => {
+ submitButton.remove();
+ form.submit();
+ input.value = "bar";
+ form.submit();
+ input.value = "baz";
+ });
+
+ submitButton.click();
+ });
+}, 'This test will pass if a form navigation successfully occurs when clicking a <button type=submit> element with a onclick event handler which removes the button and then calls form.submit().');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-type-enumerated-ascii-case-insensitive.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-type-enumerated-ascii-case-insensitive.html
new file mode 100644
index 0000000000..30a2c24a80
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-type-enumerated-ascii-case-insensitive.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/#attr-button-type">
+<link rel="help" href="https://html.spec.whatwg.org/#enumerated-attribute">
+<meta name="assert" content="button@type values are ASCII case-insensitive">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<button type="reset">
+<button type="ReSeT">
+<button type="reſet">
+<button type="submit">
+<button type="SuBmIt">
+<button type="ſubmit">
+<script>
+const button = document.querySelectorAll("button");
+
+test(() => {
+ assert_equals(button[0].type, "reset", "lowercase valid");
+ assert_equals(button[1].type, "reset", "mixed case valid");
+ assert_equals(button[2].type, "submit", "non-ASCII invalid");
+}, "keyword reset");
+
+test(() => {
+ assert_equals(button[3].type, "submit", "lowercase valid");
+
+ // vacuous: the invalid value default is currently submit, so even if the UA
+ // treats this as invalid, the observable behaviour would still be correct
+ assert_equals(button[4].type, "submit", "mixed case valid");
+
+ // vacuous: the invalid value default is currently submit, so even if the UA
+ // treats this as valid, the observable behaviour would still be correct
+ assert_equals(button[5].type, "submit", "non-ASCII invalid");
+}, "keyword submit");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-type.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-type.html
new file mode 100644
index 0000000000..6cfd6687c7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-type.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLButtonElement.prototype.type</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-button-type">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+
+ const button = document.createElement("button");
+ assert_equals(button.type, "submit");
+
+}, "a button's type should be submit by default");
+
+test(() => {
+
+ const button = document.createElement("button");
+
+ for (const type of ["reset", "button", "submit"]) {
+ button.type = type;
+ assert_equals(button.type, type);
+
+ button.type = type.toUpperCase();
+ assert_equals(button.type, type);
+ }
+
+ button.type = "reset";
+ button.type = "asdfgdsafd";
+ assert_equals(button.type, "submit");
+
+ button.type = "reset";
+ button.type = "";
+ assert_equals(button.type, "submit");
+
+}, "a button's type should stay within the range of valid values");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-untrusted-key-event.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-untrusted-key-event.html
new file mode 100644
index 0000000000..dd34d31233
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-untrusted-key-event.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Forms</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<form id="input_form">
+ <button name="submitButton" type="submit">Submit</button>
+ <button name="resetButton" type="reset">Reset</button>
+ <button name="buttonButton" type="button">Button</button>
+</form>
+<script type="module">
+const form = document.querySelector("form");
+form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ assert_true(false, 'form should not be submitted');
+});
+
+for (const button of document.querySelectorAll("button")) {
+ button.addEventListener("click", function(e) {
+ assert_true(false, `${button.type} button should not be clicked`);
+ });
+}
+
+// Create and append button elements
+for (const button of document.querySelectorAll("button")) {
+ test(() => {
+ // keyCode: Enter
+ button.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ keyCode: 13,
+ })
+ );
+
+ // key: Enter
+ button.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ key: "Enter",
+ })
+ );
+
+ // keyCode: Space
+ button.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ keyCode: 32,
+ })
+ );
+
+ // key: Space
+ button.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ key: " ",
+ })
+ );
+ }, `Dispatching untrusted keypress events to ${button.type} button should not cause click event`);
+
+ test(() => {
+ // keyCode: Enter
+ button.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ keyCode: 13,
+ })
+ );
+ button.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ keyCode: 13,
+ })
+ );
+
+ // key: Enter
+ button.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ key: "Enter",
+ })
+ );
+ button.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ key: "Enter",
+ })
+ );
+
+ // keyCode: Space
+ button.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ keyCode: 32,
+ })
+ );
+ button.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ keyCode: 32,
+ })
+ );
+
+ // key: Space
+ button.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ key: " ",
+ })
+ );
+ button.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ key: " ",
+ })
+ );
+ }, `Dispatching untrusted keyup/keydown events to ${button.type} button should not cause click event`);
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-validation.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-validation.html
new file mode 100644
index 0000000000..f3b57fd296
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-validation.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>button element validation</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-button-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<button id=btn1>button</button>
+<button id=btn2 type=submit>button</button>
+<button id=btn3 type=reset>button</button>
+<button id=btn4 type=button>button</button>
+<button id=btn5 type=menu>button</button>
+<button id=btn6 type=foobar>button</button>
+<script>
+ function willValid(element, expectedType, willValidate, desc) {
+ test(function(){
+ assert_equals(element.type, expectedType);
+ assert_equals(element.willValidate, willValidate);
+ }, desc);
+ }
+
+ willValid(document.getElementById('btn1'), "submit", true, "missing type attribute");
+ willValid(document.getElementById('btn2'), "submit", true, "submit type attribute");
+ willValid(document.getElementById('btn3'), "reset", false, "reset type attribute");
+ willValid(document.getElementById('btn4'), "button", false, "button type attribute");
+ willValid(document.getElementById('btn5'), "submit", true, "historical menu type attribute");
+ willValid(document.getElementById('btn6'), "submit", true, "invalid type attribute");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-validationmessage.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-validationmessage.html
new file mode 100644
index 0000000000..a2572ed7be
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-validationmessage.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>button_validationMessage</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <p><button id='button_id'>button</button></p>
+ </form>
+ <script>
+
+ var button = document.getElementById("button_id");
+
+ if (typeof(button.validationMessage) == "string") {
+ test(function() {
+ assert_equals(button.validationMessage, "", "validationMessage attribute is not correct.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("validationMessage attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-validity.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-validity.html
new file mode 100644
index 0000000000..acc02d92bf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-validity.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>button_validity</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <p><button id='button_id'>button</button></p>
+ </form>
+ <script>
+
+ var button = document.getElementById("button_id");
+
+ if (typeof(button.validity) == "object") {
+ test(function() {
+ assert_equals(button.validity.valueMissing, false, "validity attribute is not correct.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("validity attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-willvalidate-readonly-attribute.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-willvalidate-readonly-attribute.html
new file mode 100644
index 0000000000..c4a05a4401
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-willvalidate-readonly-attribute.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Button element with "readonly" attribute shouldn't be barred from constraint validation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<button id="implicitSubmitButton" readonly>1</button>
+<button id="explicitSubmitButton" readonly type="submit">2</button>
+<script>
+ test(() => {
+ assert_true(implicitSubmitButton.willValidate);
+ assert_true(explicitSubmitButton.willValidate);
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-button-element/button-willvalidate.html b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-willvalidate.html
new file mode 100644
index 0000000000..e91c5e3843
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-willvalidate.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>button_willValidate</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <p><button id='button_id'>button</button></p>
+ </form>
+ <script>
+
+ var button = document.getElementById("button_id");
+
+ if (typeof(button.willValidate) == "boolean") {
+ test(function() {
+ assert_equals(button.willValidate, true, "willValidate attribute is not correct.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("willValidate attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-datalist-element/datalistoptions.html b/testing/web-platform/tests/html/semantics/forms/the-datalist-element/datalistoptions.html
new file mode 100644
index 0000000000..245d43cec4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-datalist-element/datalistoptions.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Datalist element options</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#the-datalist-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<label>
+ Number:
+ <input list=numbers>
+</label>
+<datalist id=numbers>
+ <label> Select number:
+ <select id=num>
+ <option label="zero" value="0">
+ <option label="one" value="1">
+ <option label="two">2
+ <option label="three" disabled>3
+ <option>
+ </select>
+ </label>
+</datalist>
+<script>
+ test(function(){
+ var datalist = document.getElementById('numbers'),
+ labels = [],
+ values = [];
+ assert_equals(datalist.options.length, 5);
+
+ for (var i = 0, len = datalist.options.length; i < len; i++) {
+ assert_equals(datalist.options[i], datalist.options.item(i));
+ labels.push(datalist.options[i].label);
+ values.push(datalist.options[i].value);
+ }
+ assert_array_equals(labels, ["zero", "one", "two", "three", ""]);
+ assert_array_equals(values, ["0", "1", "2", "3", ""]);
+ }, "options label/value");
+
+ test(function(){
+ assert_false(document.getElementById('num').willValidate);
+ }, "If an element has a datalist element ancestor, it is barred from constraint validation");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-datalist-element/remove-datalist-crash.html b/testing/web-platform/tests/html/semantics/forms/the-datalist-element/remove-datalist-crash.html
new file mode 100644
index 0000000000..b40d5feebe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-datalist-element/remove-datalist-crash.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>Remove datalist element crash</title>
+<link rel="help" href="https://crbug.com/1199861">
+<datalist id="datalist"><option>foo</option></datalist>
+<input id="input">
+<input list="datalist">
+<script>
+ document.body.offsetTop;
+ input.appendChild(datalist);
+ datalist.remove();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/HTMLFieldSetElement.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/HTMLFieldSetElement.html
new file mode 100644
index 0000000000..9e6e52d7b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/HTMLFieldSetElement.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: HTMLFieldSetElement interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-fieldset-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form name="fm1" style="display:none">
+ <fieldset id="fs_outer">
+ <legend><input type="checkbox" name="cb"></legend>
+ <input type=text name="txt" id="ctl1">
+ <button id="ctl2" name="btn">BUTTON</button>
+ <fieldset id="fs_inner">
+ <input type="text" name="txt_inner">
+ <progress name="pg" value="0.5"></progress>
+ </fieldset>
+ </fieldset>
+</form>
+<script>
+
+var fm1,
+ fs_outer,
+ children_outer;
+
+setup(function () {
+ fm1 = document.forms.fm1;
+ fs_outer = document.getElementById("fs_outer");
+ children_outer = fs_outer.elements;
+});
+
+test(function () {
+ assert_equals(fs_outer.type, "fieldset", "The value of type attribute is incorrect.");
+}, "The type attribute must return 'fieldset'");
+
+test(function () {
+ assert_equals(fs_outer.form, fm1, "The fieldset should have a form owner.");
+}, "The form attribute must return the fieldset's form owner");
+
+test(function () {
+ assert_equals(children_outer.constructor, HTMLCollection,
+ "The elements attribute should be an HTMLCollection object");
+}, "The elements must return an HTMLCollection object");
+
+test(function () {
+ var fs_inner = document.getElementById("fs_inner");
+ var children_inner = fs_inner.elements;
+ assert_array_equals(children_inner, [fm1.txt_inner],
+ "The items in the collection must be children of the inner fieldset element.");
+ assert_array_equals(children_outer, [fm1.cb, fm1.txt, fm1.btn, fm1.fs_inner, fm1.txt_inner],
+ "The items in the collection must be children of the outer fieldset element.");
+}, "The controls must root at the fieldset element");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/README.md b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/README.md
new file mode 100644
index 0000000000..b238a023dc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/README.md
@@ -0,0 +1,12 @@
+Fieldset accessibility tests
+============================
+
+These tests are intended to test the accessibility of the fieldset and legend elements.
+
+To run these tests, open the browser's developer tools and navigate to the Accessibility pane (may
+need to activate it in Settings), or use an OS-level accessibility inspector, and verify that the
+accessible name/role matches the expected accessible name/role.
+
+The following issue discusses ways to automate these tests:
+
+https://github.com/web-platform-tests/wpt/issues/12791
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/aria-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/aria-manual.html
new file mode 100644
index 0000000000..c61d62769a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/aria-manual.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>fieldset accessibility test: ARIA</title>
+<div id=fieldset role=group aria-labelledby=legend>
+ <div id=legend>Foo</div>
+ <input>
+</div>
+<p>Expected accessible name for id=fieldset: "Foo"
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/baseline-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/baseline-manual.html
new file mode 100644
index 0000000000..2ee1ab20e9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/baseline-manual.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>fieldset accessibility test: baseline</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: "Foo"
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-appearance-none-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-appearance-none-manual.html
new file mode 100644
index 0000000000..dbc4edc2a2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-appearance-none-manual.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>fieldset accessibility test: fieldset -webkit-appearance: none</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ fieldset { -webkit-appearance: none; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: "Foo"
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-display-contents-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-display-contents-manual.html
new file mode 100644
index 0000000000..943a030337
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-display-contents-manual.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>fieldset accessibility test: fieldset display: contents</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ fieldset { display: contents; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: "Foo"
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-display-none-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-display-none-manual.html
new file mode 100644
index 0000000000..b45576036a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-display-none-manual.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>fieldset accessibility test: fieldset display: none</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ fieldset { display: none; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected no accessible node for id=fieldset.
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-div-display-contents-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-div-display-contents-manual.html
new file mode 100644
index 0000000000..2eb01f2a71
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-div-display-contents-manual.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>fieldset accessibility test: fieldset div display: contents</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ div { display: contents; }
+</style>
+<fieldset id=fieldset>
+ <div>
+ <legend>Foo</legend>
+ <input>
+ </div>
+</fieldset>
+<p>Expected accessible name for id=fieldset: ""
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-role-none-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-role-none-manual.html
new file mode 100644
index 0000000000..4638a2b8d1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-role-none-manual.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>fieldset accessibility test: fieldset role=none</title>
+<link rel=help href=http://w3c.github.io/aria/#none>
+<fieldset id=fieldset role=none>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected no accessible node for id=fieldset.
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-role-presentation-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-role-presentation-manual.html
new file mode 100644
index 0000000000..e1360d29c9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-role-presentation-manual.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>fieldset accessibility test: fieldset role=presentation</title>
+<link rel=help href=http://w3c.github.io/aria/#presentation>
+<fieldset id=fieldset role=presentation>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected no accessible node for id=fieldset.
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-visibility-collapse-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-visibility-collapse-manual.html
new file mode 100644
index 0000000000..a3dd273bbe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-visibility-collapse-manual.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>fieldset accessibility test: fieldset visibility: collapse</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ fieldset { visibility: collapse; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected no accessible node for id=fieldset.
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-visibility-hidden-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-visibility-hidden-manual.html
new file mode 100644
index 0000000000..894f00af52
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/fieldset-visibility-hidden-manual.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<title>fieldset accessibility test: fieldset visibility: hidden</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ fieldset { visibility: hidden; }
+ legend, input { visibility: visible; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected no accessible node for id=fieldset.
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/flexbox-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/flexbox-manual.html
new file mode 100644
index 0000000000..2d3d2a929c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/flexbox-manual.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>fieldset accessibility test: flexbox</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ fieldset { display: flex; }
+ legend { float: left; flex: auto; }
+ input { display: block; flex: auto; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: "Foo"
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/grid-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/grid-manual.html
new file mode 100644
index 0000000000..9d966d0113
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/grid-manual.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<title>fieldset accessibility test: grid</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ fieldset { display: grid; grid-template-columns: auto auto; }
+ legend { float: left; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: "Foo"
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-abspos-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-abspos-manual.html
new file mode 100644
index 0000000000..019e63fcd3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-abspos-manual.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>fieldset accessibility test: position: absolute legend</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ legend { position: absolute; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: "Foo"
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-child-display-none-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-child-display-none-manual.html
new file mode 100644
index 0000000000..bc5d9fb7f6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-child-display-none-manual.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>fieldset accessibility test: legend child display: none</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ legend > span { display: none; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo<span>Bar</span></legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: "Foo"
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-child-visibility-hidden-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-child-visibility-hidden-manual.html
new file mode 100644
index 0000000000..01ceb9ec65
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-child-visibility-hidden-manual.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>fieldset accessibility test: legend visibility: hidden</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ legend > span { visibility: hidden; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo<span>Bar</span></legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: "Foo"
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-display-contents-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-display-contents-manual.html
new file mode 100644
index 0000000000..f9fd1a31b8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-display-contents-manual.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>fieldset accessibility test: legend display: contents</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ legend { display: contents; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: "Foo"
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-display-none-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-display-none-manual.html
new file mode 100644
index 0000000000..14060b99f8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-display-none-manual.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>fieldset accessibility test: legend display: none</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ legend { display: none; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: ""
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-float-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-float-manual.html
new file mode 100644
index 0000000000..40f2c4ac23
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-float-manual.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>fieldset accessibility test: floating legend</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ legend { float: left; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: "Foo"
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-role-group-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-role-group-manual.html
new file mode 100644
index 0000000000..e15ff4d810
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-role-group-manual.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>fieldset accessibility test: legend role=group aria-labelledby=fieldset</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<fieldset id=fieldset>
+ <legend role=group aria-labelledby=fieldset>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: ""
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-visibility-collapse-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-visibility-collapse-manual.html
new file mode 100644
index 0000000000..c44bb1e888
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-visibility-collapse-manual.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>fieldset accessibility test: legend visibility: collapse</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ legend { visibility: collapse; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: ""
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-visibility-hidden-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-visibility-hidden-manual.html
new file mode 100644
index 0000000000..f989712565
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/legend-visibility-hidden-manual.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>fieldset accessibility test: legend visibility: hidden</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<style>
+ legend { visibility: hidden; }
+</style>
+<fieldset id=fieldset>
+ <legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: ""
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/multiple-legends-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/multiple-legends-manual.html
new file mode 100644
index 0000000000..5d25317ad0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/multiple-legends-manual.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>fieldset accessibility test: multiple legends</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<fieldset id=fieldset>
+ <div></div>
+ <legend>Foo</legend>
+ <legend>Bar</legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: "Foo"
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/role-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/role-manual.html
new file mode 100644
index 0000000000..d09c203b6b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/role-manual.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>fieldset accessibility test: role</title>
+<fieldset id=fieldset>
+ <legend id=legend>Foo</legend>
+ <input>
+</fieldset>
+<p>Expected accessible role for id=fieldset: "group"
+<p>Expected accessible role for id=legend: No corresponding role
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/shadow-dom-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/shadow-dom-manual.html
new file mode 100644
index 0000000000..bb93d07644
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/shadow-dom-manual.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>fieldset accessibility test: shadow DOM</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<template id="my-fieldset">
+ <fieldset id=fieldset>
+ <slot name="my-text"></slot>
+ <input>
+ </fieldset>
+</template>
+
+<my-fieldset>
+ <legend slot="my-text">Foo</legend>
+</my-fieldset>
+
+<p>Expected accessible name for id=fieldset: ""
+
+<script>
+customElements.define('my-fieldset',
+ class extends HTMLElement {
+ constructor() {
+ super();
+
+ const template = document.getElementById('my-fieldset');
+ const templateContent = template.content;
+
+ this.attachShadow({mode: 'open'}).appendChild(
+ templateContent.cloneNode(true)
+ );
+ }
+ }
+);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/title-attribute-and-empty-legend-manual.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/title-attribute-and-empty-legend-manual.html
new file mode 100644
index 0000000000..0169a513a9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/accessibility/title-attribute-and-empty-legend-manual.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>fieldset accessibility test: title attribute and empty legend</title>
+<link rel=help href=https://w3c.github.io/html-aam/#fieldset-element-accessible-name-computation>
+<fieldset id=fieldset title="Foo">
+ <legend></legend>
+ <input>
+</fieldset>
+<p>Expected accessible name for id=fieldset: "Foo"
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/disabled-001.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/disabled-001.html
new file mode 100644
index 0000000000..02137ab97e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/disabled-001.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Fieldset disabled</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-fieldset-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form>
+ <fieldset id=fs disabled>
+ <legend>
+ <input type=checkbox id=clubc_l1>
+ <input type=radio id=clubr_l1>
+ <input type=text id=clubt_l1>
+ </legend>
+ <legend><input type=checkbox id=club_l2></legend>
+ <p><label>Name on card: <input id=clubname required></label></p>
+ <p><label>Card number: <input id=clubnum required pattern="[-0-9]+"></label></p>
+ </fieldset>
+ <fieldset id=fs2 disabled>
+ <p><legend><input type=checkbox id=club2></legend></p>
+ <p><label>Name on card: <input id=clubname2 required></label></p>
+ <p><label>Card number: <input id=clubnum2 required pattern="[-0-9]+"></label></p>
+ </fieldset>
+ <fieldset id=fs3 disabled>
+ <fieldset>
+ <legend><input type=checkbox id=club3></legend>
+ </fieldset>
+ <p><label>Name on card: <input id=clubname3 required></label></p>
+ <p><label>Card number: <input id=clubnum3 required pattern="[-0-9]+"></label></p>
+ </fieldset>
+ <fieldset id=fs4 disabled>
+ <legend>
+ <fieldset id=fs4-1><input type=checkbox id=club4></fieldset>
+ </legend>
+ <p><label>Name on card: <input id=clubname4 required></label></p>
+ <p><label>Card number: <input id=clubnum4 required pattern="[-0-9]+"></label></p>
+ </fieldset>
+</form>
+<script>
+ test(function () {
+ assert_true(document.getElementById('fs').disabled, "The fieldset is disabled");
+ assert_false(document.getElementById('clubname').willValidate, "fieldset is disabled so is input 'clubname'");
+ assert_false(document.getElementById('clubnum').willValidate, "fieldset is disabled so is input 'clubnum'");
+ assert_true(document.getElementById('clubc_l1').willValidate, "input 'clubc_l1' is descendant of the first legend child of the fieldset. It should not be disabled");
+ assert_true(document.getElementById('clubr_l1').willValidate, "input 'clubr_l1' is descendant of the first legend child of the fieldset. It should not be disabled");
+ assert_true(document.getElementById('clubt_l1').willValidate, "input 'clubt_l1' is descendant of the first legend child of the fieldset. It should not be disabled");
+ assert_false(document.getElementById('club_l2').willValidate, "input 'club_l2' is a descendant of the second legend child of the fieldset. It should be disabled");
+ }, "The disabled attribute, when specified, causes all the form control descendants of the fieldset element, excluding those that are descendants of the fieldset element's first legend element child, if any, to be disabled.");
+
+ test(function () {
+ assert_true(document.getElementById('fs2').disabled, "The fieldset is disabled");
+ assert_false(document.getElementById('clubname2').willValidate, "fieldset is disabled so is input 'clubname2'");
+ assert_false(document.getElementById('clubnum2').willValidate, "fieldset is disabled so is input 'clubnum2'");
+ assert_false(document.getElementById('club2').willValidate, "the first legend is not a child of the disabled fieldset: input 'club2' is disabled");
+ }, "The first 'legend' element is not a child of the disabled fieldset: Its descendants should be disabled.");
+
+ test(function () {
+ assert_true(document.getElementById('fs3').disabled, "The fieldset is disabled");
+ assert_false(document.getElementById('clubname3').willValidate, "fieldset is disabled so is input 'clubname3'");
+ assert_false(document.getElementById('clubnum3').willValidate, "fieldset is disabled so is input 'clubnum3'");
+ assert_false(document.getElementById('club3').willValidate, "the first legend is not a child of the disabled fieldset: input 'club3' is disabled");
+ }, "The <legend> element is not a child of the disabled fieldset: Its descendants should be disabled.");
+
+ test(function () {
+ assert_true(document.getElementById('fs4').disabled, "The fieldset is disabled");
+ assert_false(document.getElementById('clubname4').willValidate, "fieldset is disabled so is input 'clubname4'");
+ assert_false(document.getElementById('clubnum4').willValidate, "fieldset is disabled so is input 'clubnum4'");
+ assert_true(document.getElementById('club4').willValidate, "the first legend a child of the disabled fieldset: input 'club4' is disabled");
+ }, "The <legend> element is child of the disabled fieldset: Its descendants should be disabled.");
+
+ test(function () {
+ let fs41 = document.querySelector('#fs4-1');
+ fs41.disabled = true;
+ assert_true(fs41.disabled, "The fieldset in a legend is disabled");
+ assert_false(document.getElementById('club4').willValidate, "In a disabled fieldset in the first legend child of another disabled fieldset: input 'club4' is disabled");
+ }, "A <fieldset> element is in the <legend> element of another disabled <fieldset>: Its descendants should be disabled.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/disabled-002.xhtml b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/disabled-002.xhtml
new file mode 100644
index 0000000000..896d737df4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/disabled-002.xhtml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>File input descendants of disabled fieldsets</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-fieldset-disabled" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id="log"></div>
+ <form>
+ <fieldset id="fs" disabled="disabled">
+ <input id="myfile" type="file" />
+ </fieldset>
+ </form>
+ <script>
+ test(function () {
+ assert_true(document.getElementById('fs').disabled, "disabled fieldset should be disabled");
+ assert_false(document.getElementById('myfile').willValidate, "form control descendant of disabled fieldset that is not also a descendant of a legend should be disabled");
+ }, "A file input without a disabled attribute that is a descendant of a disabled fieldset should be disabled (modulo legend-related complications that don't apply here)");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-checkvalidity.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-checkvalidity.html
new file mode 100644
index 0000000000..eeeca1853b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-checkvalidity.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>FieldSet_checkValidity</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <fieldset id="input_field">
+ </fieldset>
+ </form>
+ <script>
+
+ var field = document.getElementById("input_field");
+
+ try
+ {
+ var ret = field.checkValidity();
+
+ test(function() {
+ assert_equals(ret, true, "calling of checkValidity method is failed.");
+ });
+ }
+ catch (e) {
+ test(function() {
+ assert_unreached("Error is raised.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-intrinsic-size.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-intrinsic-size.html
new file mode 100644
index 0000000000..5ff0d7db41
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-intrinsic-size.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Fieldset with intrinsic size</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-fieldset-element">
+<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#intrinsic">
+<meta name="assert" content="A fieldset with an intrinsic size should be as big as required by the contents.">
+<style>
+fieldset {
+ height: min-content;
+ padding: 7px;
+ border: 3px solid cyan;
+}
+fieldset > div {
+ border: 3px solid orange;
+}
+.auto {
+ height: auto;
+}
+.min-content {
+ height: min-content;
+}
+.max-content {
+ height: max-content;
+}
+.content-box {
+ box-sizing: content-box;
+}
+.border-box {
+ box-sizing: border-box;
+}
+</style>
+
+<div id="log"></div>
+
+<fieldset class="auto content-box">
+ <legend>Legend</legend>
+ <div>Contents</div>
+</fieldset>
+<fieldset class="auto border-box">
+ <legend>Legend</legend>
+ <div>Contents</div>
+</fieldset>
+<fieldset class="min-content content-box">
+ <legend>Legend</legend>
+ <div>Contents</div>
+</fieldset>
+<fieldset class="min-content border-box">
+ <legend>Legend</legend>
+ <div>Contents</div>
+</fieldset>
+<fieldset class="max-content content-box">
+ <legend>Legend</legend>
+ <div>Contents</div>
+</fieldset>
+<fieldset class="max-content border-box">
+ <legend>Legend</legend>
+ <div>Contents</div>
+</fieldset>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+for (let fieldset of document.querySelectorAll("fieldset")) {
+ test(function() {
+ const fieldsetRect = fieldset.getBoundingClientRect();
+ const contentsRect = fieldset.querySelector("div").getBoundingClientRect();
+ const fieldsetOuterEnd = fieldsetRect.y + fieldsetRect.height;
+ const fieldsetInnerEnd = fieldsetOuterEnd - 10;
+ const contentsOuterEnd = contentsRect.y + contentsRect.height;
+ assert_equals(fieldsetInnerEnd, contentsOuterEnd);
+ }, fieldset.className);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-setcustomvalidity.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-setcustomvalidity.html
new file mode 100644
index 0000000000..64aa374f19
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-setcustomvalidity.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<title>fieldset setCustomValidity</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<fieldset id='fieldset_test'></fieldset>
+
+<script>
+
+test(() => {
+ let elem = document.getElementById("fieldset_test");
+ assert_false(elem.validity.customError);
+ elem.setCustomValidity("custom error");
+ assert_true(elem.validity.customError);
+}, "fieldset setCustomValidity is correct")
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-validationmessage.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-validationmessage.html
new file mode 100644
index 0000000000..14dda76a13
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-validationmessage.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>FieldSet_validationMessage</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <fieldset id="input_field">
+ </fieldset>
+ </form>
+ <script>
+
+ var field = document.getElementById("input_field");
+
+ if (typeof(field.validationMessage) == "string") {
+ test(function() {
+ assert_equals(field.validationMessage, "", "validationMessage attribute is not correct.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("validationMessage attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-validity.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-validity.html
new file mode 100644
index 0000000000..7fd2d85656
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-validity.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>FieldSet_validity</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <fieldset id="input_field">
+ </fieldset>
+ </form>
+ <script>
+
+ var field = document.getElementById("input_field");
+
+ if (typeof(field.validity) == "object") {
+ test(function() {
+ assert_equals(field.validity.valueMissing, false, "validity attribute is not correct.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("validity attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-willvalidate.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-willvalidate.html
new file mode 100644
index 0000000000..357c9c16fb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/fieldset-willvalidate.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>FieldSet_willValidate</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <fieldset id="input_field">
+ </fieldset>
+ </form>
+ <script>
+
+ var field = document.getElementById("input_field");
+
+ if (typeof(field.willValidate) == "boolean") {
+ test(function() {
+ assert_equals(field.willValidate, false, "willValidate attribute is not correct.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("willValidate attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-in-inactive-document-crash.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-in-inactive-document-crash.html
new file mode 100644
index 0000000000..8a3543fcbc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-in-inactive-document-crash.html
@@ -0,0 +1,6 @@
+<iframe id="i"></iframe>
+<script>
+var form = i.contentDocument.createElement("form");
+i.remove();
+form.action = "GET";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-reflection-with-base-url.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-reflection-with-base-url.html
new file mode 100644
index 0000000000..67828a3077
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-reflection-with-base-url.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>form.action with a base URL</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#dom-fs-action">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<base href="/common/blank.html">
+
+<form id="form1" action="a.html"></form>
+<form id="form2" action=""></form>
+<form id="form3"></form>
+
+<script>
+"use strict";
+
+test(() => {
+
+ assert_equals(document.querySelector("#form1").action, (new URL("a.html", document.baseURI)).href,
+ "action should equal the correct absolute URL");
+
+}, "An action URL should be resolved relative to the document's base URL (not the document's URL)");
+
+test(() => {
+
+ assert_equals(document.querySelector("#form2").action, document.URL);
+
+}, "An empty-string action content attribute should cause the IDL attribute to return the document's URL (not the document's base URL)");
+
+test(() => {
+
+ assert_equals(document.querySelector("#form3").action, document.URL);
+
+}, "A missing action content attribute should cause the IDL attribute to return the document's URL (not the document's base URL)");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-reflection.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-reflection.html
new file mode 100644
index 0000000000..c92fd0f0cf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-reflection.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>form.action</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#dom-fs-action">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<form id="form1" action="a.html"></form>
+<form id="form2" action=""></form>
+<form id="form3"></form>
+
+<script>
+"use strict";
+
+test(() => {
+
+ assert_equals(document.querySelector("#form1").action, (new URL("a.html", document.baseURI)).href,
+ "action should equal the correct absolute URL");
+
+}, "An action URL should be resolved relative to the document's base URL (= the document's URL in this case)");
+
+test(() => {
+
+ assert_equals(document.querySelector("#form2").action, document.URL);
+
+}, "An empty-string action content attribute should cause the IDL attribute to return the document's URL (= the document's base URL in this case)");
+
+test(() => {
+
+ assert_equals(document.querySelector("#form3").action, document.URL);
+
+}, "A missing action content attribute should cause the IDL attribute to return the document's URL (= the document's base URL in this case)");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-submission-with-base-url.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-submission-with-base-url.html
new file mode 100644
index 0000000000..baee5500de
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-submission-with-base-url.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>form action="" attribute effect on submission</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#dom-fs-action">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+// promise_test instead of async_test because all tests use window.success, and so can't run at the same time.
+
+promise_test(t => {
+ return new Promise(resolve => {
+ window.success = t.step_func(locationLoaded => {
+ const expected = (new URL("resources/target/form-action-url-target.html?name=value", location.href)).href;
+ assert_equals(locationLoaded, expected);
+ resolve();
+ });
+
+ const iframe = document.createElement("iframe");
+ iframe.src = "resources/form-with-action-and-base.sub.html?action=form-action-url-target.html";
+ document.body.appendChild(iframe);
+ });
+}, "An action URL should be resolved relative to the document's base URL (not document URL)");
+
+promise_test(t => {
+ return new Promise(resolve => {
+ window.success = t.step_func(locationLoaded => {
+ const expected = (new URL("resources/form-with-action-and-base.sub.html?name=value", location.href)).href;
+ assert_equals(locationLoaded, expected);
+ resolve();
+ });
+
+ const iframe = document.createElement("iframe");
+ iframe.src = "resources/form-with-action-and-base.sub.html?action=";
+ document.body.appendChild(iframe);
+ });
+}, "An empty-string action should submit the form to its containing document's URL (not its base URL)");
+
+promise_test(t => {
+ return new Promise(resolve => {
+ window.success = t.step_func(locationLoaded => {
+ const expected = (new URL("resources/form-no-action-with-base.html?name=value", location.href)).href;
+ assert_equals(locationLoaded, expected);
+ resolve();
+ });
+
+ const iframe = document.createElement("iframe");
+ iframe.src = "resources/form-no-action-with-base.html";
+ document.body.appendChild(iframe);
+ });
+}, "A missing action should submit the form to its containing document's URL (not its base URL)");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-submission.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-submission.html
new file mode 100644
index 0000000000..54ca7b5ff5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action-submission.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>form action="" attribute effect on submission</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#dom-fs-action">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+// promise_test instead of async_test because all tests use window.success, and so can't run at the same time.
+
+promise_test(t => {
+ return new Promise(resolve => {
+ window.success = t.step_func(locationLoaded => {
+ const expected = (new URL("resources/target/form-action-url-target.html?name=value", location.href)).href;
+ assert_equals(locationLoaded, expected);
+ resolve();
+ });
+
+ const iframe = document.createElement("iframe");
+ iframe.src = "resources/form-with-action.sub.html?action=target/form-action-url-target.html";
+ document.body.appendChild(iframe);
+ });
+}, "An action URL should be resolved relative to the document's base URL (= document's URL in this case)");
+
+promise_test(t => {
+ return new Promise(resolve => {
+ window.success = t.step_func(locationLoaded => {
+ const expected = (new URL("resources/form-with-action.sub.html?name=value", location.href)).href;
+ assert_equals(locationLoaded, expected);
+ resolve();
+ });
+
+ const iframe = document.createElement("iframe");
+ iframe.src = "resources/form-with-action.sub.html?action=";
+ document.body.appendChild(iframe);
+ });
+}, "An empty-string action should submit the form to the document's URL (= document's base URL in this case)");
+
+promise_test(t => {
+ return new Promise(resolve => {
+ window.success = t.step_func(locationLoaded => {
+ const expected = (new URL("resources/form-no-action.html?name=value", location.href)).href;
+ assert_equals(locationLoaded, expected);
+ resolve();
+ });
+
+ const iframe = document.createElement("iframe");
+ iframe.src = "resources/form-no-action.html";
+ document.body.appendChild(iframe);
+ });
+}, "A missing action should submit the form to the document's URL (= document's base URL in this case)");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action.html
new file mode 100644
index 0000000000..14717c5e6a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-action.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>Form_action</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action="http://www.google.com/"
+ id="input_form">
+ <p><input type=hidden name="custname"></p>
+ <p><input type=hidden name="custtel"></p>
+ <p><input type=hidden name="custemail"></p>
+
+ </form>
+ <script>
+
+ var form = document.getElementById("input_form");
+
+ if (typeof(form.action) == "string") {
+ test(function() {
+ assert_equals(form.action, "http://www.google.com/", "action attribute is not correct.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("action attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-autocomplete.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-autocomplete.html
new file mode 100644
index 0000000000..fcd93ce2ef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-autocomplete.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>form autocomplete attribute</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#the-form-element">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#attr-fe-autocomplete">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form name="missing_attribute">
+ <input>
+ <input autocomplete="on">
+ <input autocomplete="off">
+ <input autocomplete="foobar">
+</form>
+<form name="autocomplete_on" autocomplete="on">
+ <input>
+ <input autocomplete="on">
+ <input autocomplete="off">
+ <input autocomplete="foobar">
+</form>
+<form name="autocomplete_off" autocomplete="off">
+ <input>
+ <input autocomplete="on">
+ <input autocomplete="off">
+ <input autocomplete="foobar">
+</form>
+<form name="autocomplete_invalid" autocomplete="foobar">
+ <input>
+ <input autocomplete="on">
+ <input autocomplete="off">
+ <input autocomplete="foobar">
+</form>
+<script>
+ function autocompletetest(form, expectedValues, desc) {
+ test(function(){
+ assert_equals(form.autocomplete, expectedValues[0]);
+ assert_equals(form.elements[0].autocomplete, expectedValues[1]);
+ assert_equals(form.elements[1].autocomplete, expectedValues[2]);
+ assert_equals(form.elements[2].autocomplete, expectedValues[3]);
+ assert_equals(form.elements[3].autocomplete, expectedValues[4]);
+ }, desc);
+ }
+
+ autocompletetest(document.forms.missing_attribute, ["on", "", "on", "off", ""], "form autocomplete attribute missing");
+ autocompletetest(document.forms.autocomplete_on, ["on", "", "on", "off", ""], "form autocomplete attribute on");
+ autocompletetest(document.forms.autocomplete_off, ["off", "", "on", "off", ""], "form autocomplete attribute off");
+ autocompletetest(document.forms.autocomplete_invalid, ["on", "", "on", "off", ""], "form autocomplete attribute invalid");
+
+ var keywords = [ "on", "off", "name", "honorific-prefix", "given-name", "additional-name", "family-name", "honorific-suffix", "nickname", "username", "new-password", "current-password", "one-time-code", "organization-title", "organization", "street-address", "address-line1", "address-line2", "address-line3", "address-level4", "address-level3", "address-level2", "address-level1", "country", "country-name", "postal-code", "cc-name", "cc-given-name", "cc-additional-name", "cc-family-name", "cc-number", "cc-exp", "cc-exp-month", "cc-exp-year", "cc-csc", "cc-type", "transaction-currency", "transaction-amount", "language", "bday", "bday-day", "bday-month", "bday-year", "sex", "url", "photo", "tel", "tel-country-code", "tel-national", "tel-area-code", "tel-local", "tel-local-prefix", "tel-local-suffix", "tel-extension", "email", "impp", "webauthn" ];
+
+ keywords.forEach(function(keyword) {
+ test(function(){
+ var input = document.createElement("input");
+ // Include whitespace to test splitting tokens on whitespace.
+ // Convert to uppercase to ensure that the tokens are normalized to lowercase.
+ input.setAttribute("autocomplete", " " + keyword.toUpperCase() + "\t");
+ assert_equals(input.autocomplete, keyword);
+ }, keyword + " is an allowed autocomplete field name");
+ });
+
+
+test(() => {
+ const select = document.createElement("select");
+ select.setAttribute("autocomplete", " \n");
+ assert_equals(select.autocomplete, "");
+}, "Test whitespace-only attribute value");
+
+test(() => {
+ const select = document.createElement("select");
+
+ select.setAttribute("autocomplete", "foo off");
+ assert_equals(select.autocomplete, "");
+
+ // Normal category; max=3
+ select.setAttribute("autocomplete", "foo section-foo billing name");
+ assert_equals(select.autocomplete, "");
+
+ // Contact category; max=4
+ select.setAttribute("autocomplete", "foo section-bar billing work tel");
+ assert_equals(select.autocomplete, "");
+
+ // Credential category; max=5
+ select.setAttribute("autocomplete", "foo section-bar billing work tel webauthn");
+ assert_equals(select.autocomplete, "");
+}, "Test maximum number of tokens");
+
+test(() => {
+ const textarea = document.createElement("textarea");
+
+ textarea.setAttribute("autocomplete", "call-sign");
+ assert_equals(textarea.autocomplete, "");
+}, "Unknown field");
+
+test(() => {
+ const hidden = document.createElement("input");
+ hidden.type = "hidden";
+ hidden.setAttribute("autocomplete", "on");
+ assert_equals(hidden.autocomplete, "");
+ hidden.setAttribute("autocomplete", "off");
+ assert_equals(hidden.autocomplete, "");
+}, "Test 'wearing the autofill anchor mantle' with off/on");
+
+test(() => {
+ const textarea = document.createElement("textarea");
+
+ textarea.setAttribute("autocomplete", " HOME\ntel");
+ assert_equals(textarea.autocomplete, "home tel");
+
+ textarea.setAttribute("autocomplete", "shipping country");
+ assert_equals(textarea.autocomplete, "shipping country");
+
+ textarea.setAttribute("autocomplete", "billing work email");
+ assert_equals(textarea.autocomplete, "billing work email");
+
+ textarea.setAttribute("autocomplete", " section-FOO bday");
+ assert_equals(textarea.autocomplete, "section-foo bday");
+}, "Serialize combinations of section, mode, contact, and field");
+
+test(() => {
+ const textarea = document.createElement("textarea");
+
+ textarea.setAttribute("autocomplete", "\tusername webauthn");
+ assert_equals(textarea.autocomplete, "username webauthn");
+
+ textarea.setAttribute("autocomplete", " section-LOGIN shipping work tel webauthn ");
+ assert_equals(textarea.autocomplete, "section-login shipping work tel webauthn");
+}, "Serialize combinations of section, mode, contact, field, and credential");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-checkvalidity.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-checkvalidity.html
new file mode 100644
index 0000000000..941ab94d45
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-checkvalidity.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>Form_checkValidity</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <p><input type=hidden name="custname"></p>
+ <p><input type=hidden name="custtel"></p>
+ <p><input type=hidden name="custemail"></p>
+
+ </form>
+ <script>
+
+ var form = document.getElementById("input_form");
+
+ try
+ {
+ var ret = form.checkValidity();
+
+ test(function() {
+ assert_equals(ret, true, "calling of checkValidity method is failed.");
+ });
+ }
+ catch (e) {
+ test(function() {
+ assert_unreached("Error is raised.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-filter.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-filter.html
new file mode 100644
index 0000000000..693560188a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-filter.html
@@ -0,0 +1,192 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>form.elements must contain all listed elements with the form owner</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-form-elements">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+
+<!--
+ Elements with data-in are expected to be in the form.elements collection.
+ The choice of other elements besides "listed elements" (i.e. img, label, meter, progress) was
+ because those are ones that appear in form-associated or labelable element categories.
+-->
+
+<button data-in form="form" id="before-button1"></button>
+<fieldset data-in form="form" id="before-fieldset1"></fieldset>
+<object data-in form="form" id="before-object1"></object>
+<output data-in form="form" id="before-output1"></output>
+<select data-in form="form" id="before-select1">
+ <option form="form" id="before-option1">x</option>
+</select>
+<textarea data-in form="form" id="before-textarea1"></textarea>
+
+<input data-in form="form" id="before-input1">
+<input data-in type="hidden" form="form" id="before-input2">
+<input data-in type="search" form="form" id="before-input3">
+<input data-in type="tel" form="form" id="before-input4">
+<input data-in type="url" form="form" id="before-input5">
+<input data-in type="email" form="form" id="before-input6">
+<input data-in type="password" form="form" id="before-input7">
+<input data-in type="date" form="form" id="before-input8">
+<input data-in type="month" form="form" id="before-input9">
+<input data-in type="week" form="form" id="before-input10">
+<input data-in type="time" form="form" id="before-input11">
+<input data-in type="datetime-local" form="form" id="before-input12">
+<input data-in type="number" form="form" id="before-input13">
+<input data-in type="range" form="form" id="before-input14">
+<input data-in type="color" form="form" id="before-input15">
+<input data-in type="checkbox" form="form" id="before-input16">
+<input data-in type="radio" form="form" id="before-input17">
+<input data-in type="file" form="form" id="before-input18">
+<input data-in type="submit" form="form" id="before-input19">
+<input data-in type="reset" form="form" id="before-input20">
+<input data-in type="button" form="form" id="before-input21">
+
+<img form="form" id="before-img1">
+<label form="form" id="before-label1"></label>
+<meter form="form" id="before-meter1"></meter>
+<progress form="form" id="before-progress1"></progress>
+
+<form id="form">
+ <button data-in id="button1"></button>
+ <fieldset data-in id="fieldset1"></fieldset>
+ <object data-in id="object1"></object>
+ <output data-in id="output1"></output>
+ <select data-in id="select1">
+ <option id="option1">x</option>
+ </select>
+ <textarea data-in id="textarea1"></textarea>
+
+ <input data-in id="input1">
+ <input data-in type="hidden" id="input2">
+ <input data-in type="search" id="input3">
+ <input data-in type="tel" id="input4">
+ <input data-in type="url" id="input5">
+ <input data-in type="email" id="input6">
+ <input data-in type="password" id="input7">
+ <input data-in type="date" id="input8">
+ <input data-in type="month" id="input9">
+ <input data-in type="week" id="input10">
+ <input data-in type="time" id="input11">
+ <input data-in type="datetime-local" id="input12">
+ <input data-in type="number" id="input13">
+ <input data-in type="range" id="input14">
+ <input data-in type="color" id="input15">
+ <input data-in type="checkbox" id="input16">
+ <input data-in type="radio" id="input17">
+ <input data-in type="file" id="input18">
+ <input data-in type="submit" id="input19">
+ <input data-in type="reset" id="input20">
+ <input data-in type="button" id="input21">
+
+ <img id="img1">
+ <label id="label1"></label>
+ <meter id="meter1"></meter>
+ <progress id="progress1"></progress>
+</form>
+
+<button data-in form="form" id="after-button1"></button>
+<fieldset data-in form="form" id="after-fieldset1"></fieldset>
+<object data-in form="form" id="after-object1"></object>
+<output data-in form="form" id="after-output1"></output>
+<select data-in form="form" id="after-select1">
+ <option form="form" id="after-option1">x</option>
+</select>
+<textarea data-in form="form" id="after-textarea1"></textarea>
+
+<input data-in form="form" id="after-input1">
+<input data-in type="hidden" form="form" id="after-input2">
+<input data-in type="search" form="form" id="after-input3">
+<input data-in type="tel" form="form" id="after-input4">
+<input data-in type="url" form="form" id="after-input5">
+<input data-in type="email" form="form" id="after-input6">
+<input data-in type="password" form="form" id="after-input7">
+<input data-in type="date" form="form" id="after-input8">
+<input data-in type="month" form="form" id="after-input9">
+<input data-in type="week" form="form" id="after-input10">
+<input data-in type="time" form="form" id="after-input11">
+<input data-in type="datetime-local" form="form" id="after-input12">
+<input data-in type="number" form="form" id="after-input13">
+<input data-in type="range" form="form" id="after-input14">
+<input data-in type="color" form="form" id="after-input15">
+<input data-in type="checkbox" form="form" id="after-input16">
+<input data-in type="radio" form="form" id="after-input17">
+<input data-in type="file" form="form" id="after-input18">
+<input data-in type="submit" form="form" id="after-input19">
+<input data-in type="reset" form="form" id="after-input20">
+<input data-in type="button" form="form" id="after-input21">
+
+<img form="form" id="after-img1">
+<label form="form" id="after-label1"></label>
+<meter form="form" id="after-meter1"></meter>
+<progress form="form" id="after-progress1"></progress>
+
+<button id="after-unassociated-button1"></button>
+<fieldset id="after-unassociated-fieldset1"></fieldset>
+<object id="after-unassociated-object1"></object>
+<output id="after-unassociated-output1"></output>
+<select id="after-unassociated-select1">
+ <option id="after-unassociated-option1">x</option>
+</select>
+<textarea id="after-unassociated-textarea1"></textarea>
+
+<input id="after-unassociated-input1">
+<input type="hidden" id="after-unassociated-input2">
+<input type="search" id="after-unassociated-input3">
+<input type="tel" id="after-unassociated-input4">
+<input type="url" id="after-unassociated-input5">
+<input type="email" id="after-unassociated-input6">
+<input type="password" id="after-unassociated-input7">
+<input type="date" id="after-unassociated-input8">
+<input type="month" id="after-unassociated-input9">
+<input type="week" id="after-unassociated-input10">
+<input type="time" id="after-unassociated-input11">
+<input type="datetime-local" id="after-unassociated-input12">
+<input type="number" id="after-unassociated-input13">
+<input type="range" id="after-unassociated-input14">
+<input type="color" id="after-unassociated-input15">
+<input type="checkbox" id="after-unassociated-input16">
+<input type="radio" id="after-unassociated-input17">
+<input type="file" id="after-unassociated-input18">
+<input type="submit" id="after-unassociated-input19">
+<input type="reset" id="after-unassociated-input20">
+<input type="button" id="after-unassociated-input21">
+
+<img id="after-unassociated-img1">
+<label id="after-unassociated-label1"></label>
+<meter id="after-unassociated-meter1"></meter>
+<progress id="after-unassociated-progress1"></progress>
+
+<form id="form2">
+ <span id="shadow-1"></span>
+</form>
+<span id="shadow-2"></span>
+
+<script>
+"use strict";
+test(() => {
+ const elements = document.querySelector("#form").elements;
+ const ids = Array.from(elements).map(el => el.id);
+
+ const allCorrectIDs = Array.from(document.querySelectorAll("[data-in]")).map(el => el.id);
+
+ assert_array_equals(ids, allCorrectIDs);
+});
+
+test(() => {
+ const shadowRoot1 = document.querySelector("#shadow-1").attachShadow({mode: "open"});
+ const input1 = document.createElement("input");
+ shadowRoot1.appendChild(input1);
+
+ const shadowRoot2 = document.querySelector("#shadow-2").attachShadow({mode: "open"});
+ const input2 = document.createElement("input");
+ input2.setAttribute("form", "form2");
+ shadowRoot2.appendChild(input2);
+
+ assert_equals(document.querySelector("#form2").elements.length, 0);
+ assert_equals(input1.form, null);
+ assert_equals(input2.form, null);
+}, "form.elements only includes elements from the same shadow tree");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-interfaces-01.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-interfaces-01.html
new file mode 100644
index 0000000000..c8b4a6c71e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-interfaces-01.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>form.elements: interfaces</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-form-elements">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#htmlformcontrolscollection">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var form = document.createElement("form");
+ ["HTMLFormControlsCollection", "HTMLCollection"].forEach(function(i) {
+ test(function() {
+ assert_true(i in window, "Interface should exist")
+ assert_true(form.elements instanceof window[i],
+ "elements should implement the interface")
+ }, "Testing interface " + i)
+ })
+})
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-matches.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-matches.html
new file mode 100644
index 0000000000..7921627265
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-matches.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<title>form.elements: matches</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-form-elements">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="test">
+<form id="form">
+<input type="image">
+</form>
+</div>
+<script>
+test(function() {
+ assert_equals(document.getElementById("form").elements.length, 0);
+}, "input type=image should not be present in the form.elements collection")
+test(function() {
+ var form = document.getElementById("form");
+ var i = document.createElement("input");
+ i.name = "2";
+ form.appendChild(i);
+ var j = document.createElement("input");
+ j.name = "03";
+ form.appendChild(j);
+ assert_equals(form.elements[-1], undefined, '[-1]');
+ assert_equals(form.elements["-1"], undefined, '["-1"]');
+ assert_equals(form.elements[0], i, '[0]');
+ assert_equals(form.elements["0"], i, '["0"]');
+ assert_equals(form.elements[1], j, '[1]');
+ assert_equals(form.elements["1"], j, '["1"]');
+ assert_equals(form.elements[2], undefined, '[2]');
+ assert_equals(form.elements["2"], undefined, '["2"]');
+ assert_equals(form.elements[03], undefined, '[03]');
+ assert_equals(form.elements["03"], j, '["03"]');
+ assert_equals(form.elements.item(-1), null, 'item(-1)');
+ assert_equals(form.elements.item(0), i, 'item(0)');
+ assert_equals(form.elements.item(1), j, 'item(1)');
+ assert_equals(form.elements.item(2), null, 'item(2)');
+ assert_equals(form.elements.namedItem("2"), i, 'namedItem("2")');
+ assert_equals(form.elements.namedItem("03"), j, 'namedItem("03")');
+ assert_equals(form.elements.namedItem("3"), null, 'namedItem("3")');
+ assert_array_equals(form.elements, [i, j]);
+ form.removeChild(i);
+ form.removeChild(j);
+}, "form.elements should include elements whose name starts with a number");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-nameditem-01.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-nameditem-01.html
new file mode 100644
index 0000000000..0b5aeb8ef5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-nameditem-01.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>form.elements: namedItem</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-form-elements">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="test">
+<form id=form>
+<input name=b id=i1>
+<input name=b id=i2>
+</form>
+</div>
+<script>
+test(function() {
+ assert_true("RadioNodeList" in window, "RadioNodeList should exist");
+}, "RadioNodeList should exist")
+test(function() {
+ var nl = document.forms.form.elements["b"];
+ assert_true(nl instanceof NodeList, "Should get a NodeList");
+ if ("RadioNodeList" in window) {
+ assert_true(nl instanceof RadioNodeList, "Should get a RadioNodeList");
+ }
+ assert_array_equals(nl,
+ [document.getElementById("i1"),
+ document.getElementById("i2")]);
+
+ var el = nl[0];
+ el.parentNode.removeChild(el);
+ assert_true(nl instanceof NodeList, "Should get a NodeList");
+ if ("RadioNodeList" in window) {
+ assert_true(nl instanceof RadioNodeList, "Should get a RadioNodeList");
+ }
+ assert_array_equals(nl, [document.getElementById("i2")]);
+ assert_equals(document.forms.form.elements["b"], document.getElementById("i2"));
+}, "elements collection should return elements or RadioNodeLists")
+test(function() {
+ var fs = document.forms.form.appendChild(document.createElement("fieldset"));
+ fs.name = "fs";
+ assert_equals(document.forms.form.elements.fs, fs);
+ fs.parentNode.removeChild(fs);
+}, "elements collection should include fieldsets")
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-nameditem-02.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-nameditem-02.html
new file mode 100644
index 0000000000..c25e554de1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-nameditem-02.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>form.elements: parsing</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-form-elements">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#parsing-main-intr">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="test">
+<form id=form>
+<table>
+<tr>
+<td><input type="radio" name="radio1" id="r1" value=1></td>
+<td><input type="radio" name="radio2" id="r2" value=2></td>
+<input type="radio" name="radio0" id="r0" value=0>
+</tr>
+</table>
+</form>
+</div>
+<script>
+test(function() {
+ var form = document.getElementById("form");
+ assert_array_equals(form.elements,
+ [document.getElementById("r0"),
+ document.getElementById("r1"),
+ document.getElementById("r2")]);
+}, "form.elements should work correctly in the face of table syntax errors")
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-sameobject.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-sameobject.html
new file mode 100644
index 0000000000..1c1aa5d3dd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-elements-sameobject.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Testing [SameObject] on the 'elements' attribute on the 'form' element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+
+<form>
+ <input>
+</form>
+
+<script>
+test(function() {
+ var form = document.querySelector('form');
+ var elements = form.elements;
+ assert_equals(elements, form.elements);
+ form.appendChild(document.createElement('input'));
+ assert_equals(elements, form.elements);
+}, "[SameObject] should apply to 'elements' attr on <form>");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-indexed-element.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-indexed-element.html
new file mode 100644
index 0000000000..5ea96d3d1b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-indexed-element.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>form.elements: indexed</title>
+<link rel="author" title="Ivan.Yang" href="mailto:jsyangwenjie@gmail.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="test">
+<form id=form>
+<input type="radio" name="radio1" id="r1" value=1>
+<input type="radio" name="radio2" id="r2" value=2>
+</form>
+</div>
+<script>
+test(function() {
+ var form = document.getElementById("form");
+ assert_equals(form[0], document.getElementById("r1"));
+ assert_equals(form[1], document.getElementById("r2"));
+ assert_equals(form[2], undefined);
+ assert_equals(form[-1], undefined);
+}, "form.elements should be accessed correctly by index")
+
+test(function(){
+ var form = document.getElementById("form");
+ var old_item = form[0];
+ var old_desc = Object.getOwnPropertyDescriptor(form, 0);
+ assert_equals(old_desc.value, old_item);
+ assert_true(old_desc.enumerable);
+ assert_true(old_desc.configurable);
+ assert_false(old_desc.writable);
+
+ Object.prototype[0] = 5;
+ this.add_cleanup(function () { delete Object.prototype[0]; });
+ assert_equals(form[0], old_item);
+
+ delete form[0];
+ assert_equals(form[0], old_item);
+
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ delete form[0];
+ });
+ assert_equals(form[0], old_item);
+}, 'Trying to delete an indexed property name should never work');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-length.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-length.html
new file mode 100644
index 0000000000..3326809fc6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-length.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>Form_length</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <p><input type=hidden name="custname"></p>
+ <p><input type=hidden name="custtel"></p>
+ <p><input type=hidden name="custemail"></p>
+
+ </form>
+ <script>
+
+ var form = document.getElementById("input_form");
+ var len = form.length;
+
+ test(function() {
+ assert_equals(len, 3, "length attribute is not correct.");
+ });
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-nameditem.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-nameditem.html
new file mode 100644
index 0000000000..7b7d573615
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-nameditem.html
@@ -0,0 +1,418 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Form named getter</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<!-- XXX Nothing tests id attributes yet. -->
+<!-- XXX We also need tests for moving inputs and forms in the DOM. -->
+<form>
+<input type=button name=button>
+<input type=radio name=radio value=x>
+<input type=radio name=radio value=y>
+<input type=radio name=radio value=z>
+</form>
+
+<form>
+<button name=l1></button>
+<fieldset name=l2></fieldset>
+<input type=hidden name=l3>
+<input type=text name=l4>
+<input type=search name=l5>
+<input type=tel name=l6>
+<input type=url name=l7>
+<input type=email name=l8>
+<input type=password name=l9>
+<input type=datetime name=l10>
+<input type=date name=l11>
+<input type=month name=l12>
+<input type=week name=l13>
+<input type=time name=l14>
+<input type=datetime-local name=l15>
+<input type=number name=l16>
+<input type=range name=l17>
+<input type=color name=l18>
+<input type=checkbox name=l19>
+<input type=radio name=l20>
+<input type=file name=l21>
+<input type=submit name=l22>
+<input type=image name=l23>
+<input type=reset name=l24>
+<input type=button name=l25>
+<input type=foo name=l26>
+<input name=l27>
+<object name=l28></object>
+<output name=l29></output>
+<select name=l30></select>
+<textarea name=l31></textarea>
+</form>
+
+<form>
+<!-- EventTarget -->
+<input type=radio name=addEventListener>
+<input type=radio name=removeEventListener>
+<input type=radio name=dispatchEvent>
+
+<!-- Node -->
+<input type=radio name=nodeType>
+<input type=radio name=nodeName>
+<input type=radio name=ownerDocument>
+
+<!-- Element -->
+<input type=radio name=namespaceURI>
+<input type=radio name=prefix>
+<input type=radio name=localName>
+
+<!-- HTMLElement -->
+<input type=radio name=title>
+<input type=radio name=lang>
+<input type=radio name=dir>
+
+<!-- HTMLFormElement -->
+<input type=radio name=acceptCharset>
+<input type=radio name=action>
+<input type=radio name=autocomplete>
+<input type=radio name=enctype>
+<input type=radio name=encoding>
+<input type=radio name=method>
+<input type=radio name=name>
+<input type=radio name=noValidate>
+<input type=radio name=target>
+<input type=radio name=elements>
+<input type=radio name=length>
+<input type=radio name=submit>
+<input type=radio name=reset>
+<input type=radio name=checkValidity>
+</form>
+
+<img name=x>
+<form></form><!-- no child nodes -->
+<img name=y>
+<form><!-- a child node --></form>
+<img name=z>
+
+<input form=a name=b>
+<form id=a></form>
+<input form=c name=d>
+<input form=c name=d>
+<form id=c></form>
+<script>
+test(function() {
+ var form = document.getElementsByTagName("form")[0]
+ assert_equals(form.item, undefined)
+ assert_false("item" in form)
+}, "Forms should not have an item method")
+
+test(function() {
+ var form = document.getElementsByTagName("form")[0]
+ assert_equals(form.namedItem, undefined)
+ assert_false("namedItem" in form)
+}, "Forms should not have a namedItem method")
+
+test(function() {
+ var form = document.getElementsByTagName("form")[0]
+ var button = document.getElementsByTagName("input")[0]
+ assert_equals(button.type, "button")
+ assert_equals(form.button, button)
+ var desc = Object.getOwnPropertyDescriptor(form, "button");
+ assert_equals(desc.value, button);
+ assert_false(desc.writable);
+ assert_true(desc.configurable);
+ assert_false(desc.enumerable);
+ assert_equals(form.button.length, undefined)
+}, "Name for a single element should work")
+
+test(function() {
+ var form = document.getElementsByTagName("form")[0]
+ assert_equals(form.radio.item(-1), null)
+ assert_array_equals([0, 1, 2].map(function(i) {
+ return form.radio.item(i).value
+ }), ["x", "y", "z"])
+ assert_equals(form.radio.item(3), null)
+}, "Calling item() on the NodeList returned from the named getter should work")
+
+test(function() {
+ var form = document.getElementsByTagName("form")[0]
+ assert_equals(form.radio.length, 3)
+ assert_equals(form.radio[-1], undefined)
+ assert_array_equals([0, 1, 2].map(function(i) {
+ return form.radio[i].value
+ }), ["x", "y", "z"])
+ assert_equals(form.radio[3], undefined)
+}, "Indexed getter on the NodeList returned from the named getter should work")
+
+test(function() {
+ var form = document.getElementsByTagName("form")[0]
+ var indices = [-1, 0, 1, 2, 3]
+ indices.forEach(function(i) {
+ assert_throws_js(TypeError, function() {
+ form.radio(i)
+ })
+ })
+}, "Invoking a legacycaller on the NodeList returned from the named getter " +
+ "should not work")
+
+test(function() {
+ var form = document.getElementsByTagName("form")[1]
+ for (var i = 1; i <= 31; ++i) {
+ if (i == 23) {
+ // input type=image
+ assert_equals(form["l" + i], undefined)
+ } else {
+ assert_equals(form["l" + i], form.children[i - 1])
+ }
+ }
+}, "All listed elements except input type=image should be present in the form")
+
+test(function() {
+ var names = [
+ // EventTarget
+ "addEventListener", "removeEventListener", "dispatchEvent",
+ // Node
+ "nodeType", "nodeName", "ownerDocument",
+ // Element
+ "namespaceURI", "prefix", "localName",
+ // HTMLElement
+ "title", "lang", "dir",
+ // HTMLFormElement
+ "acceptCharset", "action", "autocomplete", "enctype", "encoding", "method",
+ "name", "noValidate", "target", "elements", "length", "submit", "reset",
+ "checkValidity"
+ ]
+ var form = document.getElementsByTagName("form")[2]
+ names.forEach(function(name, i) {
+ assert_equals(form[name], form.children[i])
+ })
+}, "Named elements should override builtins")
+
+test(function() {
+ var form = document.getElementsByTagName("form")[3]
+ assert_equals(form.x, undefined, "x should not be associated with the form")
+ assert_equals(form.y, undefined, "y should not be associated with the form")
+ assert_equals(form.z, undefined, "z should not be associated with the form")
+ assert_equals(form[0], undefined, "The form should not have supported property indices")
+ assert_equals(form.length, 0)
+}, "Named items outside the form should not be returned (no children)")
+
+test(function() {
+ var form = document.getElementsByTagName("form")[4]
+ assert_equals(form.x, undefined, "x should not be associated with the form")
+ assert_equals(form.y, undefined, "y should not be associated with the form")
+ assert_equals(form.z, undefined, "z should not be associated with the form")
+ assert_equals(form[0], undefined, "The form should not have supported property indices")
+ assert_equals(form.length, 0)
+}, "Named items outside the form should not be returned (one child)")
+
+test(function() {
+ var form = document.getElementsByTagName("form")[5]
+ assert_equals(form.id, "a")
+
+ var input = document.getElementsByName("b")[0]
+ assert_equals(input.localName, "input")
+ assert_equals(input.getAttribute("form"), "a")
+
+ assert_equals(form.b, input);
+}, "The form attribute should be taken into account for named getters (single element)")
+
+test(function() {
+ var form = document.getElementsByTagName("form")[6]
+ assert_equals(form.id, "c")
+
+ var input1 = document.getElementsByName("d")[0]
+ assert_equals(input1.localName, "input")
+ assert_equals(input1.getAttribute("form"), "c")
+
+ var input2 = document.getElementsByName("d")[1]
+ assert_equals(input2.localName, "input")
+ assert_equals(input2.getAttribute("form"), "c")
+
+ var desc = Object.getOwnPropertyDescriptor(form, "d");
+ assert_equals(desc.value, form.d);
+ assert_false(desc.writable);
+ assert_true(desc.configurable);
+ assert_false(desc.enumerable);
+
+ assert_true(form.d instanceof NodeList, "form.d should be a NodeList")
+ assert_array_equals(form.d, [input1, input2])
+}, "The form attribute should be taken into account for named getters (multiple elements)")
+
+test(function() {
+ var f = document.body.appendChild(document.createElement("form"))
+ f.id = "f"
+ var g = f.appendChild(document.createElement("form"))
+ g.id = "g"
+ var input = g.appendChild(document.createElement("input"))
+ input.name = "x"
+ assert_equals(f.x, undefined)
+ assert_equals(g.x, input)
+}, "Input should only be a named property on the innermost form that contains it")
+
+test(function() {
+ var form = document.getElementsByTagName("form")[1];
+ var old_item = form["l1"];
+ var old_desc = Object.getOwnPropertyDescriptor(form, "l1");
+ assert_equals(old_desc.value, old_item);
+ assert_false(old_desc.enumerable);
+ assert_true(old_desc.configurable);
+ assert_false(old_desc.writable);
+
+ form["l1"] = 5;
+ assert_equals(form["l1"], old_item);
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ form["l1"] = 5;
+ });
+ assert_throws_js(TypeError, function() {
+ Object.defineProperty(form, "l1", { value: 5 });
+ });
+
+ delete form["l1"];
+ assert_equals(form["l1"], old_item);
+
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ delete form["l1"];
+ });
+ assert_equals(form["l1"], old_item);
+
+}, 'Trying to set an expando that would shadow an already-existing named property');
+
+test(function() {
+ var form = document.getElementsByTagName("form")[1];
+ var old_item = form["new-name"];
+ var old_desc = Object.getOwnPropertyDescriptor(form, "new-name");
+ assert_equals(old_item, undefined);
+ assert_equals(old_desc, undefined);
+
+ form["new-name"] = 5;
+ assert_equals(form["new-name"], 5);
+
+ var input = document.createElement("input");
+ this.add_cleanup(function () {input.remove();});
+ input.name = "new-name";
+ form.appendChild(input);
+
+ assert_equals(form["new-name"], 5);
+
+ delete form["new-name"];
+ assert_equals(form["new-name"], input);
+}, 'Trying to set an expando that shadows a named property that gets added later');
+
+test(function() {
+ var form = document.getElementsByTagName("form")[1];
+ var old_item = form["new-name2"];
+ var old_desc = Object.getOwnPropertyDescriptor(form, "new-name2");
+ assert_equals(old_item, undefined);
+ assert_equals(old_desc, undefined);
+
+ Object.defineProperty(form, "new-name2", { configurable: false, writable:
+ false, value: 5 });
+ assert_equals(form["new-name2"], 5);
+
+ var input = document.createElement("input");
+ this.add_cleanup(function () {input.remove();});
+ input.name = "new-name2";
+ form.appendChild(input);
+
+ assert_equals(form["new-name2"], 5);
+
+ delete form["new-name2"];
+ assert_equals(form["new-name2"], 5);
+
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ delete form["new-name2"];
+ });
+ assert_equals(form["new-name2"], 5);
+}, 'Trying to set a non-configurable expando that shadows a named property that gets added later');
+
+test(function() {
+ var form = document.getElementsByTagName("form")[1];
+
+ var i1 = document.createElement("input");
+ i1.name = "past-name1";
+ i1.id = "past-id1"
+
+ assert_equals(form["past-name1"], undefined);
+ assert_equals(form["past-id1"], undefined);
+ form.appendChild(i1);
+ assert_equals(form["past-name1"], i1);
+ assert_equals(form["past-id1"], i1);
+
+ i1.name = "twiddled-name1";
+ i1.id = "twiddled-id1";
+ assert_equals(form["past-name1"], i1);
+ assert_equals(form["twiddled-name1"], i1);
+ assert_equals(form["past-id1"], i1);
+ assert_equals(form["twiddled-id1"], i1);
+
+ i1.name = "twiddled-name2";
+ i1.id = "twiddled-id2";
+ assert_equals(form["past-name1"], i1);
+ assert_equals(form["twiddled-name1"], i1);
+ assert_equals(form["twiddled-name2"], i1);
+ assert_equals(form["past-id1"], i1);
+ assert_equals(form["twiddled-id1"], i1);
+ assert_equals(form["twiddled-id2"], i1);
+
+ i1.removeAttribute("id");
+ i1.removeAttribute("name");
+ assert_equals(form["past-name1"], i1);
+ assert_equals(form["twiddled-name1"], i1);
+ assert_equals(form["twiddled-name2"], i1);
+ assert_equals(form["past-id1"], i1);
+ assert_equals(form["twiddled-id1"], i1);
+ assert_equals(form["twiddled-id2"], i1);
+
+ i1.remove();
+ assert_equals(form["past-name1"], undefined);
+ assert_equals(form["twiddled-name1"], undefined);
+ assert_equals(form["twiddled-name2"], undefined);
+ assert_equals(form["past-id1"], undefined);
+ assert_equals(form["twiddled-id1"], undefined);
+ assert_equals(form["twiddled-id2"], undefined);
+
+ var i2 = document.createElement("input");
+ i2.name = "past-name2";
+ i2.id = "past-id2";
+
+ assert_equals(form["past-name2"], undefined);
+ assert_equals(form["past-id2"], undefined);
+ form.appendChild(i2);
+ assert_equals(form["past-name2"], i2);
+ assert_equals(form["past-id2"], i2);
+
+ i2.name = "twiddled-name3";
+ i2.id = "twiddled-id3";
+ assert_equals(form["past-name2"], i2);
+ assert_equals(form["twiddled-name3"], i2);
+ assert_equals(form["past-id2"], i2);
+ assert_equals(form["twiddled-id3"], i2);
+
+ i2.name = "twiddled-name4";
+ i2.id = "twiddled-id4";
+ assert_equals(form["past-name2"], i2);
+ assert_equals(form["twiddled-name3"], i2);
+ assert_equals(form["twiddled-name4"], i2);
+ assert_equals(form["past-id2"], i2);
+ assert_equals(form["twiddled-id3"], i2);
+ assert_equals(form["twiddled-id4"], i2);
+
+ i2.removeAttribute("id");
+ i2.removeAttribute("name");
+ assert_equals(form["past-name2"], i2);
+ assert_equals(form["twiddled-name3"], i2);
+ assert_equals(form["twiddled-name4"], i2);
+ assert_equals(form["past-id2"], i2);
+ assert_equals(form["twiddled-id3"], i2);
+ assert_equals(form["twiddled-id4"], i2);
+
+ i2.setAttribute("form", "c");
+ assert_equals(form["past-name2"], undefined);
+ assert_equals(form["twiddled-name3"], undefined);
+ assert_equals(form["twiddled-name4"], undefined);
+ assert_equals(form["past-id2"], undefined);
+ assert_equals(form["twiddled-id3"], undefined);
+ assert_equals(form["twiddled-id4"], undefined);
+}, "Past names map should work correctly");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/form-requestsubmit.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-requestsubmit.html
new file mode 100644
index 0000000000..cbc46cc7d3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-requestsubmit.html
@@ -0,0 +1,215 @@
+<!DOCTYPE html>
+<title>form.requestSubmit() tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe name="iframe" src="about:blank"></iframe>
+
+<script>
+test(() => {
+ document.body.insertAdjacentHTML('afterbegin', '<form>' +
+ '<input type="reset">' +
+ '<input type="text">' +
+ '<button type="reset"></button>' +
+ '<button type="button"></button>' +
+ '</form>');
+ let form = document.querySelector('form');
+ assert_throws_js(TypeError, () => {
+ form.requestSubmit(document.body);
+ });
+ for (let control of form.elements) {
+ assert_throws_js(TypeError, () => { form.requestSubmit(control); });
+ }
+}, 'Passing an element which is not a submit button should throw');
+
+test(() => {
+ document.body.insertAdjacentHTML('afterbegin', `<form>
+ <input form="" type="submit">
+ <button form="form2" type="submit"></button>
+ </form>
+ <form id="form2"></form>`);
+ let form = document.querySelector('form');
+ let submitButton = document.createElement('button');
+ submitButton.type = 'submit';
+ assert_throws_dom('NotFoundError', () => {
+ form.requestSubmit(submitButton);
+ });
+
+ let buttons = form.querySelectorAll('input, button');
+ assert_equals(buttons.length, 2);
+ for (let control of buttons) {
+ assert_throws_dom('NotFoundError', () => { form.requestSubmit(control) },
+ control.outerHTML);
+ }
+}, 'Passing a submit button not owned by the context object should throw');
+
+test(() => {
+ document.body.insertAdjacentHTML('afterbegin', `<input type=submit form="form1">
+ <form id="form1" target="_blank">
+ <button type="submit"></button>
+ <button></button>
+ <button type="invalid"></button>
+ <input type="submit">
+ <input type="image">
+ </form>`);
+ let form = document.querySelector('form');
+ let didDispatchSubmit = false;
+ form.addEventListener('submit', event => { event.preventDefault(); didDispatchSubmit = true; });
+
+ assert_equals(form.elements.length, 5);
+ for (let control of form.elements) {
+ didDispatchSubmit = false;
+ form.requestSubmit(control);
+ assert_true(didDispatchSubmit, `${control.outerHTML} should submit the form`);
+ }
+ // <input type=image> is not in form.elements.
+ let control = form.querySelector('[type=image]');
+ didDispatchSubmit = false;
+ form.requestSubmit(control);
+ assert_true(didDispatchSubmit, `${control.outerHTML} should submit the form`);
+}, 'requestSubmit() should accept button[type=submit], input[type=submit], and input[type=image]');
+
+test(() => {
+ document.body.insertAdjacentHTML('afterbegin', '<form><input required></form>');
+ let form = document.querySelector('form');
+ let invalidControl = form.querySelector('input:invalid');
+ let didDispatchInvalid = false;
+ invalidControl.addEventListener('invalid', e => { didDispatchInvalid = true; });
+
+ form.requestSubmit();
+ assert_true(didDispatchInvalid);
+}, 'requestSubmit() should trigger interactive form validation');
+
+test(() => {
+ document.body.insertAdjacentHTML('afterbegin',
+ '<form><input type=submit></form>');
+ let form = document.querySelector('form');
+ let submitButton = form.elements[0];
+ let submitCounter = 0;
+ form.addEventListener('submit', e => {
+ ++submitCounter;
+ form.requestSubmit();
+ e.preventDefault();
+ }, {once: true});
+ form.requestSubmit();
+ assert_equals(submitCounter, 1, 'requestSubmit() + requestSubmit()');
+
+ submitCounter = 0;
+ form.addEventListener('submit', e => {
+ ++submitCounter;
+ submitButton.click();
+ e.preventDefault();
+ }, {once: true});
+ form.requestSubmit();
+ assert_equals(submitCounter, 1, 'requestSubmit() + click()');
+
+ submitCounter = 0;
+ form.addEventListener('submit', e => {
+ ++submitCounter;
+ form.requestSubmit();
+ e.preventDefault();
+ }, {once: true});
+ submitButton.click();
+ assert_equals(submitCounter, 1, 'click() + requestSubmit()');
+}, 'requestSubmit() doesn\'t run form submission reentrantly');
+
+test(() => {
+ document.body.insertAdjacentHTML('afterbegin',
+ '<form><input type=submit><input required></form>');
+ let form = document.querySelector('form');
+ let submitButton = form.elements[0];
+ let invalidControl = form.elements[1];
+ let invalidCounter = 0;
+ invalidControl.addEventListener('invalid', e => {
+ ++invalidCounter;
+ if (invalidCounter < 10)
+ form.requestSubmit();
+ }, {once: true});
+ form.requestSubmit();
+ assert_equals(invalidCounter, 1, 'requestSubmit() + requestSubmit()');
+
+ invalidCounter = 0;
+ invalidControl.addEventListener('invalid', e => {
+ ++invalidCounter;
+ if (invalidCounter < 10)
+ submitButton.click();
+ }, {once: true});
+ form.requestSubmit();
+ assert_equals(invalidCounter, 1, 'requestSubmit() + click()');
+
+ invalidCounter = 0;
+ invalidControl.addEventListener('invalid', e => {
+ ++invalidCounter;
+ if (invalidCounter < 10)
+ form.requestSubmit();
+ }, {once: true});
+ submitButton.click();
+ assert_equals(invalidCounter, 1, 'click() + requestSubmit()');
+}, 'requestSubmit() doesn\'t run interactive validation reentrantly');
+
+test(() => {
+ let form = document.createElement('form');
+ let submitCounter = 0;
+ form.addEventListener('submit', e => { ++submitCounter; e.preventDefault(); });
+ form.requestSubmit();
+ assert_equals(submitCounter, 0);
+}, 'requestSubmit() for a disconnected form should not submit the form');
+
+async_test(t => {
+ window.addEventListener('load', t.step_func(() => {
+ document.body.insertAdjacentHTML('afterbegin', `
+<form action="/common/blank.html">
+<input required>
+<input type=submit formnovalidate formtarget=iframe name=s value=v>
+</form>`);
+ let form = document.body.querySelector('form');
+ let iframe = document.body.querySelector('iframe');
+ assert_true(form.matches(':invalid'), 'The form is invalid.');
+ // The form should be submitted though it is invalid.
+ iframe.addEventListener('load', t.step_func_done(() => {
+ assert_not_equals(iframe.contentWindow.location.search.indexOf('s=v'), -1);
+ }));
+ form.requestSubmit(form.querySelector('[type=submit]'));
+ }));
+}, 'The value of the submitter should be appended, and form* ' +
+ 'attributes of the submitter should be handled.');
+
+test(() => {
+ document.body.insertAdjacentHTML('afterbegin', `<form>
+ <input name="n1" value="v1">
+ <button type="submit" name="n2" value="v2"></button>
+ </form>
+ <form id="form2"></form>`);
+ let form = document.querySelector('form');
+ let formDataInEvent = null;
+ let submitter = form.querySelector('button[type=submit]');
+ form.addEventListener('submit', e => {
+ e.preventDefault();
+ formDataInEvent = new FormData(e.target);
+ });
+
+ form.requestSubmit(submitter);
+ assert_equals(formDataInEvent.get('n1'), 'v1');
+ assert_false(formDataInEvent.has('n2'));
+}, 'The constructed FormData object should not contain an entry for the submit button that was used to submit the form.');
+
+async_test(t => {
+ document.body.insertAdjacentHTML('afterbegin', `<form>
+ <button type="submit" name="n1" value="v1" disabled=""></button>
+ </form>`);
+ let form = document.querySelector('form');
+ let formDataInEvent = null;
+ let submitter = form.querySelector('button[type=submit]');
+
+ form.addEventListener("submit", t.step_func_done(ev => {
+ ev.preventDefault();
+ formDataInEvent = new FormData(ev.target);
+ assert_false(formDataInEvent.has('n1'));
+ assert_equals(ev.target, form);
+ }));
+
+ form.requestSubmit(submitter);
+
+}, "Using requestSubmit on a disabled button (via disabled attribute) should trigger submit but not be visible in FormData");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-no-action-with-base.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-no-action-with-base.html
new file mode 100644
index 0000000000..b3599a45e6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-no-action-with-base.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<base href="target/"></base>
+
+<form>
+ <input type="text" name="name" value="value">
+ <input type="submit" value="Submit">
+</form>
+
+<script>
+"use strict";
+
+if (window.location.search.startsWith("?name=value")) {
+ // The action pointed to ourself, so the form submitted something
+ window.parent.success(window.location.href);
+} else {
+ const form = document.querySelector("form");
+ form.submit();
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-no-action.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-no-action.html
new file mode 100644
index 0000000000..c0c2ad0330
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-no-action.html
@@ -0,0 +1,18 @@
+<!doctype html>
+
+<form>
+ <input type="text" name="name" value="value">
+ <input type="submit" value="Submit">
+</form>
+
+<script>
+"use strict";
+
+if (window.location.search.startsWith("?name=value")) {
+ // The action pointed to ourself, so the form submitted something
+ window.parent.success(window.location.href);
+} else {
+ const form = document.querySelector("form");
+ form.submit();
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-with-action-and-base.sub.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-with-action-and-base.sub.html
new file mode 100644
index 0000000000..edb101bece
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-with-action-and-base.sub.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<base href="target/"></base>
+
+<form action="{{GET[action]}}">
+ <input type="text" name="name" value="value">
+ <input type="submit" value="Submit">
+</form>
+
+<script>
+"use strict";
+
+if (window.location.search.startsWith("?name=value")) {
+ // The action pointed to ourself, so the form submitted something
+ window.parent.success(window.location.href);
+} else {
+ const form = document.querySelector("form");
+ form.submit();
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-with-action.sub.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-with-action.sub.html
new file mode 100644
index 0000000000..97e800a561
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/form-with-action.sub.html
@@ -0,0 +1,18 @@
+<!doctype html>
+
+<form action="{{GET[action]}}">
+ <input type="text" name="name" value="value">
+ <input type="submit" value="Submit">
+</form>
+
+<script>
+"use strict";
+
+if (window.location.search.startsWith("?name=value")) {
+ // The action pointed to ourself, so the form submitted something
+ window.parent.success(window.location.href);
+} else {
+ const form = document.querySelector("form");
+ form.submit();
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/target/form-action-url-target.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/target/form-action-url-target.html
new file mode 100644
index 0000000000..d509f21924
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/resources/target/form-action-url-target.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<script>
+"use strict";
+window.parent.success(window.location.href);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/anchor-active-contenteditable.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/anchor-active-contenteditable.html
new file mode 100644
index 0000000000..88a9a35cc5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/anchor-active-contenteditable.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<link rel="help" href="http://crbug.com/1007941">
+
+<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>
+
+<!-- This behavior is not explicitly specified. -->
+
+<a id=anchorid href="nonexistant">anchor</a>
+
+<script>
+anchorid.addEventListener('mousedown', () => {
+ anchorid.contentEditable = true;
+});
+
+promise_test(async () => {
+ await test_driver.click(anchorid);
+ assert_equals(document.querySelector(':active'), null);
+}, 'Anchor elements should not stay :active when contentEditable is enabled.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/anchor-contenteditable-navigate.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/anchor-contenteditable-navigate.html
new file mode 100644
index 0000000000..e958f10df8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/anchor-contenteditable-navigate.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+
+<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>
+
+<!-- This behavior is not explicitly specified. -->
+
+<a id=anchorid href="javascript:window.anchornavigated = true;">anchor</a>
+
+<script>
+promise_test(async () => {
+ window.anchornavigated = false;
+
+ anchorid.contentEditable = true;
+ await test_driver.click(anchorid);
+
+ assert_false(window.anchornavigated, "Anchor's javascript: url was run.");
+
+}, 'Anchor elements should not be able to navigate if they have contentEditable.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/button.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/button.html
new file mode 100644
index 0000000000..3c826a9754
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/button.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>input type button</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#button-state-(type=button)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form id=f1>
+ <input type=button id=b1 name=b1>
+</form>
+<form>
+ <input id=i1 value="foo">
+ <input type=button id=b2 name=b2>
+</form>
+<form>
+ <input type=radio id=i2 checked name=b3>
+ <input type=button id=b3 name=b3>
+</form>
+<form>
+ <input type=checkbox id=i3>
+ <input type=button id=b4 name=b4>
+</form>
+
+<script>
+ var t = async_test("clicking on button should not submit a form"),
+ b1 = document.getElementById('b1'),
+ b2 = document.getElementById('b2'),
+ b3 = document.getElementById('b3'),
+ b4 = document.getElementById('b4'),
+ i1 = document.getElementById('i1'),
+ i2 = document.getElementById('i2'),
+ i3 = document.getElementById('i3');
+
+ test(function(){
+ assert_false(b1.willValidate);
+ }, "the element is barred from constraint validation");
+
+ document.getElementById('f1').onsubmit = t.step_func(function(e) {
+ e.preventDefault();
+ assert_unreached("form has been submitted");
+ });
+
+ t.step(function() {
+ b1.click();
+ });
+ t.done();
+
+ test(function(){
+ i1.value = "bar";
+ b2.click();
+ assert_equals(i1.value, "bar");
+ }, "clicking on button should not reset other form fields");
+
+ test(function(){
+ assert_true(i2.checked);
+ b3.click();
+ assert_true(i2.checked);
+ }, "clicking on button should not unchecked radio buttons");
+
+ test(function(){
+ assert_false(i3.indeterminate);
+ b4.click();
+ assert_false(i3.indeterminate);
+ }, "clicking on button should not change its indeterminate IDL attribute");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-onblur-with-click.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-onblur-with-click.html
new file mode 100644
index 0000000000..2d5d008622
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-onblur-with-click.html
@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<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>
+<style>
+* {
+ font-size: 20px;
+}
+</style>
+</head>
+<body>
+
+<!-- This behavior is not explicitly specified. -->
+
+<input type=checkbox id=cb1 checked> <label for=cb1>ghi</label>
+<input type=radio id=r1 checked> <label for=r1>jkl</label>
+<label id=lc>abc <input type=checkbox id=cb2 checked></label>
+<label id=lr>def <input type=radio id=r2 checked></label>
+
+<script>
+promise_test(async () => {
+ await new Promise(resolve => {
+ addEventListener("load", resolve, { once: true });
+ });
+}, "Wait for load");
+
+const tabKey = "\uE004";
+promise_test(async t => {
+ const checkbox = document.querySelector("input[type=checkbox]");
+ // pointerdown on the checkbox
+ await (new test_driver.Actions()
+ .pointerMove(2, 2, { origin: checkbox })
+ .pointerDown())
+ .send();
+ t.add_cleanup(async () => {
+ // Release the pointer
+ await (new test_driver.Actions().pointerUp()).send();
+ });
+ assert_equals(document.querySelector("input:active"), checkbox,
+ "Checkboxes should be :active while it is pressed");
+
+ // Press tab
+ await (new test_driver.Actions().keyDown(tabKey).keyUp(tabKey)).send();
+ assert_equals(document.querySelector(":active"), null,
+ "Checkboxes should not be :active after tab is used to change focus.");
+}, "Checkboxes should clear :active when the user tabs away from them while pressing it with a pointing device");
+
+promise_test(async t => {
+ const radio = document.querySelector("input[type=radio]");
+ // pointerdown on the radio
+ await (new test_driver.Actions()
+ .pointerMove(2, 2, { origin: radio })
+ .pointerDown())
+ .send();
+ t.add_cleanup(async () => {
+ // Release the pointer
+ await (new test_driver.Actions().pointerUp()).send();
+ });
+ assert_equals(document.querySelector("input:active"), radio,
+ "Radios should be :active while it is pressed");
+
+ // Press tab
+ await (new test_driver.Actions().keyDown(tabKey).keyUp(tabKey)).send();
+ assert_equals(document.querySelector(":active"), null,
+ "Radios should not be :active after tab is used to change focus.");
+}, "Radios should clear :active when the user tabs away from them while pressing it with a pointing device");
+
+promise_test(async t => {
+ const checkbox = document.querySelector("label > input[type=checkbox]");
+ const label = checkbox.parentElement;
+ // pointerdown on the label
+ await (new test_driver.Actions()
+ .pointerMove(2, 2, { origin: label })
+ .pointerDown())
+ .send();
+ t.add_cleanup(async () => {
+ // Release the pointer
+ await (new test_driver.Actions().pointerUp()).send();
+ });
+ assert_equals(document.querySelector("input:active"), checkbox,
+ "Checkboxes should be :active while the label is pressed");
+
+ // Press tab
+ await (new test_driver.Actions().keyDown(tabKey).keyUp(tabKey)).send();
+ assert_equals(document.querySelector(":active"), null,
+ "Checkboxes should not be :active after tab is used to change focus.");
+}, "Checkboxes should clear :active when the user tabs away from them while pressing the parent label with a pointing device");
+
+promise_test(async t => {
+ const radio = document.querySelector("label > input[type=radio]");
+ const label = radio.parentElement;
+ const radioRect = radio.getBoundingClientRect();
+ const labelRect = label.getBoundingClientRect();
+ // pointerdown on the label
+ await (new test_driver.Actions()
+ .pointerMove(2, 2, { origin: label })
+ .pointerDown())
+ .send();
+ t.add_cleanup(async () => {
+ // Release the pointer
+ await (new test_driver.Actions().pointerUp()).send();
+ });
+ assert_equals(document.querySelector("input:active"), radio,
+ "Radios should be :active while the label is pressed");
+
+ // Press tab
+ await (new test_driver.Actions().keyDown(tabKey).keyUp(tabKey)).send();
+ assert_equals(document.querySelector(":active"), null,
+ "Radios should not be :active after tab is used to change focus.");
+}, "Radios should clear :active when the user tabs away from them while pressing the parent label with a pointing device");
+
+promise_test(async t => {
+ const label = document.querySelector("label[for=cb1]");
+ // pointerdown on the label
+ await (new test_driver.Actions()
+ .pointerMove(2, 2, { origin: label })
+ .pointerDown())
+ .send();
+ t.add_cleanup(async () => {
+ // Release the pointer
+ await (new test_driver.Actions().pointerUp()).send();
+ });
+ assert_equals(document.querySelector("input:active"), label.control,
+ "Checkboxes should be :active while the label is pressed");
+
+ // Press tab
+ await (new test_driver.Actions().keyDown(tabKey).keyUp(tabKey)).send();
+ assert_equals(document.querySelector(":active"), null,
+ "Checkboxes should not be :active after tab is used to change focus.");
+}, "Checkboxes should clear :active when the user tabs away from them while pressing the following label with a pointing device");
+
+promise_test(async t => {
+ const label = document.querySelector("label[for=r1]");
+ // pointerdown on the label
+ await (new test_driver.Actions()
+ .pointerMove(2, 2, { origin: label })
+ .pointerDown())
+ .send();
+ t.add_cleanup(async () => {
+ // Release the pointer
+ await (new test_driver.Actions().pointerUp()).send();
+ });
+ assert_equals(document.querySelector("input:active"), label.control,
+ "Radios should be :active while the label is pressed");
+
+ // Press tab
+ await (new test_driver.Actions().keyDown(tabKey).keyUp(tabKey)).send();
+ assert_equals(document.querySelector(":active"), null,
+ "Radios should not be :active after tab is used to change focus.");
+}, "Radios should clear :active when the user tabs away from them while pressing the following label with a pointing device");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-onblur.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-onblur.html
new file mode 100644
index 0000000000..cc88996fe7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-onblur.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<link rel="help" href="http://crbug.com/1157510">
+<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>
+
+<!-- This behavior is not explicitly specified. -->
+
+<input type=checkbox id=checkbox checked>
+<input type=radio id=radio checked>
+
+<script>
+promise_test(async t => {
+ checkbox.focus();
+
+ // Hold spacebar down
+ await (new test_driver.Actions()).keyDown('\uE00D').send();
+ t.add_cleanup(async () => {
+ // Release spacebar
+ await (new test_driver.Actions()).keyUp('\uE00D').send();
+ });
+ assert_equals(document.querySelector('input:active'), checkbox,
+ 'Checkboxes should be :active while the spacebar is pressed down.');
+
+ // Press tab
+ await (new test_driver.Actions()).keyDown('\uE004').keyUp('\uE004').send();
+ assert_equals(document.querySelector(':active'), null,
+ 'Checkboxes should not be :active after tab is used to change focus.');
+}, 'Checkboxes should clear :active when the user tabs away from them while holding spacebar.');
+
+promise_test(async t => {
+ radio.focus();
+
+ // Hold spacebar down
+ await (new test_driver.Actions()).keyDown('\uE00D').send();
+ t.add_cleanup(async () => {
+ // Release spacebar
+ await (new test_driver.Actions()).keyUp('\uE00D').send();
+ });
+ assert_equals(document.querySelector('input:active'), radio,
+ 'Radios should be :active while the spacebar is pressed down.');
+
+ // Press tab
+ await (new test_driver.Actions()).keyDown('\uE004').keyUp('\uE004').send();
+ assert_equals(document.querySelector(':active'), null,
+ 'Radios should not be :active after tab is used to change focus.');
+}, 'Radios should clear :active when the user tabs away from them while holding spacebar.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-space-key-being-disabled.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-space-key-being-disabled.html
new file mode 100644
index 0000000000..5f725b85f4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-space-key-being-disabled.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Tests active state of checkbox/radio when pressing space key but it's disabled by a keydown event listener</title>
+<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>
+</head>
+<body>
+<input type="checkbox">
+<input type="radio">
+<script>
+const spaceKey = "\uE00D";
+
+function disableTarget(event) {
+ event.target.disabled = true;
+}
+
+// If a `keydown` event listener disables the event target, default event
+// handler in browser shouldn't activate the disabled element. Otherwise,
+// the browser loses a chance to inactivate the disabled element because
+// it won't get keyup events until it's enabled again.
+
+promise_test(async t => {
+ const checkbox = document.querySelector("input[type=checkbox]");
+ checkbox.focus();
+ checkbox.addEventListener("keydown", disableTarget);
+ await (new test_driver.Actions()).keyDown(spaceKey).send();
+ let released = false;
+ t.add_cleanup(async () => {
+ if (!released) {
+ await (new test_driver.Actions()).keyUp(spaceKey).send();
+ }
+ checkbox.removeEventListener("keydown", disableTarget);
+ checkbox.remove();
+ });
+ test(() => {
+ assert_equals(
+ document.querySelector("input:active"),
+ null,
+ "The checkbox shouldn't be activated"
+ );
+ }, "Space key press shouldn't activate the disabled checkbox");
+
+ await (new test_driver.Actions()).keyUp(spaceKey).send();
+ released = true;
+
+ assert_equals(
+ document.querySelector("input:active"),
+ null,
+ "The disabled checkbox should be inactivated even if activated accidentally"
+ );
+}, "Space key shouldn't active the checkbox when it's disabled by a keydown event listener");
+
+promise_test(async t => {
+ const radio = document.querySelector("input[type=radio]");
+ radio.focus();
+ radio.addEventListener("keydown", disableTarget);
+ await (new test_driver.Actions()).keyDown(spaceKey).send();
+ let released = false;
+ t.add_cleanup(async () => {
+ if (!released) {
+ await (new test_driver.Actions()).keyUp(spaceKey).send();
+ }
+ radio.removeEventListener("keydown", disableTarget);
+ radio.disabled = false;
+ });
+ test(() => {
+ assert_equals(
+ document.querySelector("input:active"),
+ null,
+ "The radio shouldn't be activated"
+ );
+ }, "Space key press shouldn't activate the disabled radio");
+
+ await (new test_driver.Actions()).keyUp(spaceKey).send();
+ released = true;
+
+ assert_equals(
+ document.querySelector("input:active"),
+ null,
+ "The disabled radio should be inactivated even if it's accidentally activated"
+ );
+}, "Space key shouldn't active the radio when it's disabled by a keydown event listener");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-space-key-prevented-default.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-space-key-prevented-default.html
new file mode 100644
index 0000000000..877cd70e29
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-space-key-prevented-default.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Tests active state of checkbox/radio when pressing space key but its default prevented</title>
+<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>
+</head>
+<body>
+<input type="checkbox">
+<input type="radio">
+<script>
+const spaceKey = "\uE00D";
+
+function preventDefault(event) {
+ event.preventDefault();
+}
+
+promise_test(async t => {
+ const checkbox = document.querySelector("input[type=checkbox]");
+ checkbox.focus();
+ checkbox.addEventListener("keydown", preventDefault);
+ await (new test_driver.Actions()).keyDown(spaceKey).send();
+ t.add_cleanup(async () => {
+ await (new test_driver.Actions()).keyUp(spaceKey).send();
+ checkbox.removeEventListener("keydown", preventDefault);
+ });
+ assert_equals(
+ document.querySelector("input:active"),
+ null,
+ "The checkbox shouldn't be activated"
+ );
+}, "Space key shouldn't active the checkbox when its default is prevented");
+
+promise_test(async t => {
+ const radio = document.querySelector("input[type=radio]");
+ radio.focus();
+ radio.addEventListener("keydown", preventDefault);
+ await (new test_driver.Actions()).keyDown(spaceKey).send();
+ t.add_cleanup(async () => {
+ await (new test_driver.Actions()).keyUp(spaceKey).send();
+ radio.removeEventListener("keydown", preventDefault);
+ });
+ assert_equals(
+ document.querySelector("input:active"),
+ null,
+ "The radio shouldn't be activated"
+ );
+}, "Space key shouldn't active the radio when its default is prevented");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-space-key-untrusted-event.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-space-key-untrusted-event.html
new file mode 100644
index 0000000000..190757d8d3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkable-active-space-key-untrusted-event.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Tests active state of checkbox/radio when pressing space key emulated with untrusted key events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<input type="checkbox">
+<input type="radio">
+<script>
+function sendSpaceKeyEvent(eventType, target) {
+ const eventData = { keyCode: 32, which: 32, key: " ", code: "Space"};
+ const spaceKeyEvent = new KeyboardEvent(eventType, eventData);
+ target.dispatchEvent(spaceKeyEvent);
+}
+
+test(t => {
+ const checkbox = document.querySelector("input[type=checkbox]");
+ checkbox.focus();
+ sendSpaceKeyEvent("keydown", checkbox);
+ t.add_cleanup(() => {
+ sendSpaceKeyEvent("keyup", checkbox);
+ });
+ assert_equals(
+ document.querySelector("input:active"),
+ null,
+ "The checkbox shouldn't be activated"
+ );
+}, "Space key shouldn't active the checkbox when space key press is emulated by untrusted events");
+
+test(t => {
+ const radio = document.querySelector("input[type=radio]");
+ radio.focus();
+ sendSpaceKeyEvent("keydown", radio);
+ t.add_cleanup(() => {
+ sendSpaceKeyEvent("keyup", radio);
+ });
+ assert_equals(
+ document.querySelector("input:active"),
+ null,
+ "The radio shouldn't be activated"
+ );
+}, "Space key shouldn't active the radio when space key press is emulated by untrusted events");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/checkbox-click-events.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkbox-click-events.html
new file mode 100644
index 0000000000..5051fdd4e6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkbox-click-events.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Checkbox click events</title>
+<link rel="author" title="jeffcarp" href="mailto:gcarpenterv@gmail.com">
+<link rel=help href="https://html.spec.whatwg.org/#checkbox-state-(type=checkbox)">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+test(() => {
+
+ const input = document.createElement("input");
+ input.type = "checkbox";
+
+ const values = [];
+
+ input.addEventListener("click", e => {
+ values.push(input.checked);
+ e.preventDefault();
+ values.push(input.checked);
+ });
+
+ input.click();
+
+ values.push(input.checked);
+ assert_array_equals(values, [true, true, false]);
+
+}, "clicking and preventDefaulting a checkbox causes the checkbox to be checked during the click handler but reverted");
+
+test(() => {
+ const input = document.createElement("input");
+ input.type = "checkbox";
+ document.body.appendChild(input);
+ const events = [];
+
+ input.addEventListener("change", () => {
+ events.push("change");
+ });
+ input.addEventListener("click", () => {
+ events.push("click");
+ });
+ input.addEventListener("input", () => {
+ events.push("input");
+ });
+
+ assert_false(input.checked);
+
+ input.click();
+
+ assert_true(input.checked);
+ assert_array_equals(events, ["click", "input", "change"]);
+
+}, "a checkbox input emits click, input, change events in order after synthetic click");
+
+test(() => {
+ const input = document.createElement("input");
+ input.type = "checkbox";
+ document.body.appendChild(input);
+ const events = [];
+
+ input.addEventListener("change", () => {
+ events.push("change");
+ });
+ input.addEventListener("click", () => {
+ events.push("click");
+ });
+ input.addEventListener("input", () => {
+ events.push("input");
+ });
+
+ assert_false(input.checked);
+
+ const event = new MouseEvent("click", { bubbles: true, cancelable: true });
+ input.dispatchEvent(event);
+
+ assert_true(input.checked);
+ assert_array_equals(events, ["click", "input", "change"]);
+
+}, "a checkbox input emits click, input, change events in order after dispatching click event");
+
+test(() => {
+ const input = document.createElement("input");
+ input.type = "checkbox";
+ document.body.appendChild(input);
+ const events = [];
+
+ input.addEventListener("change", () => {
+ events.push("change");
+ });
+ input.addEventListener("click", e => {
+ e.preventDefault();
+ events.push("click");
+ });
+ input.addEventListener("input", () => {
+ events.push("input");
+ });
+
+ assert_false(input.checked);
+
+ input.click();
+
+ assert_false(input.checked);
+ assert_array_equals(events, ["click"]);
+}, "checkbox input respects cancel behavior on synthetic clicks");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/checkbox.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkbox.html
new file mode 100644
index 0000000000..c48083d685
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/checkbox.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>input type checkbox</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox)">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#run-synthetic-click-activation-steps">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<input type=checkbox id=checkbox1>
+<input type=checkbox id=checkbox2 disabled>
+<input type=checkbox id=checkbox3>
+<input type=checkbox id=checkbox4 checked>
+<input type=checkbox id=checkbox5>
+<input type=checkbox id=checkbox6>
+<script>
+ var checkbox1 = document.getElementById('checkbox1'),
+ checkbox2 = document.getElementById('checkbox2'),
+ checkbox3 = document.getElementById('checkbox3'),
+ checkbox4 = document.getElementById('checkbox4'),
+ checkbox5 = document.getElementById('checkbox5'),
+ checkbox6 = document.getElementById('checkbox6'),
+ c1_click_fired = false,
+ c1_input_fired = false,
+ c1_change_fired = false,
+ t1 = async_test("click on mutable checkbox fires a click event, then an input event, then a change event"),
+ t2 = async_test("click on non-mutable checkbox doesn't fire the input or change event"),
+ t3 = async_test("pre-activation steps on unchecked checkbox"),
+ t4 = async_test("pre-activation steps on checked checkbox"),
+ t5 = async_test("canceled activation steps on unchecked checkbox"),
+ t6 = async_test("canceled activation steps on unchecked checkbox (indeterminate=true in onclick)");
+
+ checkbox1.onclick = t1.step_func(function(e) {
+ c1_click_fired = true;
+ assert_false(c1_input_fired, "click event should fire before input event");
+ assert_false(c1_change_fired, "click event should fire before change event");
+ assert_false(e.isTrusted, "click()-initiated click event should not be trusted");
+ });
+ checkbox1.oninput = t1.step_func(function(e) {
+ c1_input_fired = true;
+ assert_true(c1_click_fired, "input event should fire after click event");
+ assert_false(c1_change_fired, "input event should fire before change event");
+ assert_true(e.bubbles, "event should bubble");
+ assert_true(e.isTrusted, "click()-initiated event should be trusted");
+ assert_false(e.cancelable, "event should not be cancelable");
+ assert_true(checkbox1.checked, "checkbox is checked");
+ assert_false(checkbox1.indeterminate, "checkbox is not indeterminate");
+ });
+
+ checkbox1.onchange = t1.step_func(function(e) {
+ c1_change_fired = true;
+ assert_true(c1_click_fired, "change event should fire after click event");
+ assert_true(c1_input_fired, "change event should fire after input event");
+ assert_true(e.bubbles, "event should bubble")
+ assert_true(e.isTrusted, "click()-initiated event should be trusted");
+ assert_false(e.cancelable, "event should not be cancelable");
+ assert_true(checkbox1.checked, "checkbox is checked");
+ assert_false(checkbox1.indeterminate, "checkbox is not indeterminate");
+ });
+
+ checkbox2.oninput= t2.step_func(function(e) {
+ assert_unreached("event input fired");
+ });
+
+ checkbox2.onchange = t2.step_func(function(e) {
+ assert_unreached("event change fired");
+ });
+
+ t1.step(function() {
+ checkbox1.click();
+ assert_true(c1_input_fired);
+ assert_true(c1_change_fired);
+ t1.done();
+ });
+
+ t2.step(function() {
+ checkbox2.click();
+ t2.done();
+ });
+
+ t3.step(function() {
+ checkbox3.indeterminate = true;
+ checkbox3.click();
+ assert_true(checkbox3.checked);
+ assert_false(checkbox3.indeterminate);
+ t3.done();
+ });
+
+ t4.step(function() {
+ checkbox4.indeterminate = true;
+ checkbox4.click();
+ assert_false(checkbox4.checked);
+ assert_false(checkbox4.indeterminate);
+ t4.done();
+ });
+
+ checkbox5.onclick = t5.step_func(function(e) {
+ e.preventDefault();
+ /*
+ The prevention of the click doesn't have an effect until after all the
+ click event handlers have been run.
+ */
+ assert_true(checkbox5.checked);
+ assert_false(checkbox5.indeterminate);
+ t5.step_timeout(function() {
+ /*
+ The click event has finished being dispatched, so the checkedness and
+ determinateness have been toggled back by now because the event
+ was preventDefault-ed.
+ */
+ assert_false(checkbox5.checked);
+ assert_false(checkbox5.indeterminate);
+ t5.done();
+ }, 0);
+ });
+
+ t5.step(function(){
+ assert_false(checkbox5.checked);
+ assert_false(checkbox5.indeterminate);
+ checkbox5.click();
+ });
+
+ checkbox6.onclick = t6.step_func(function(e) {
+ checkbox6.indeterminate = true;
+ e.preventDefault();
+ /*
+ The prevention of the click doesn't have an effect until after all the
+ click event handlers have been run.
+ */
+ assert_true(checkbox6.checked);
+ assert_true(checkbox6.indeterminate);
+ t6.step_timeout(function() {
+ /*
+ The click event has finished being dispatched, so the checkedness and
+ determinateness have been toggled back by now because the event
+ was preventDefault-ed.
+ */
+ assert_false(checkbox6.checked);
+ assert_false(checkbox6.indeterminate);
+ t6.done();
+ }, 0);
+ });
+
+ t6.step(function(){
+ assert_false(checkbox6.checked);
+ assert_false(checkbox6.indeterminate);
+ checkbox6.click();
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/checked.xhtml b/testing/web-platform/tests/html/semantics/forms/the-input-element/checked.xhtml
new file mode 100644
index 0000000000..70aeb51097
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/checked.xhtml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>input@checked is immediately reflected to 'checked' IDL attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<input style="display: none" type="checkbox" checked="">
+<script>
+test(function(){
+assert_true(document.querySelector('input').checked, 'Examining "checked" IDL attribute value:')
+});
+</script>
+</input>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/clone.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/clone.html
new file mode 100644
index 0000000000..0f7e053baa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/clone.html
@@ -0,0 +1,150 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test input value retention upon clone</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style>form {display: none;} </style>
+<form>
+<p><input type=checkbox> This checkbox is initially unchecked.</p>
+<p><input type=checkbox checked="checked"> This checkbox is initially checked.</p>
+<p><input type=radio name=radio> This radiobutton is initially unchecked.</p>
+<p><input type=radio checked="checked" name=radio> This radiobutton is initially checked.</p>
+<p><input type=hidden value="DEFAULT
+DEFAULT"> This hidden field has the initial value "DEFAULT\nDEFAULT".</p>
+<p><input type=text value=DEFAULT> This text field has the initial value "DEFAULT".</p>
+<p><input type=search value=DEFAULT> This search field has the initial value "DEFAULT".</p>
+<p><input type=tel value=DEFAULT> This phone number field has the initial value "DEFAULT".</p>
+<p><input type=url value=https://default.invalid/> This URL field has the initial value "https://default.invalid/".</p>
+<p><input type=email value=default@default.invalid> This email field has the initial value "default@default.invalid".</p>
+<p><input type=password value=DEFAULT> This password field has the initial value "DEFAULT".</p>
+<p><input type=date value=2015-01-01> This date field has the initial value "2015-01-01".</p>
+<p><input type=month value=2015-01> This month field has the initial value "2015-01".</p>
+<p><input type=week value=2015-W01> This week field has the initial value "2015-W01".</p>
+<p><input type=time value=12:00> This time field has the initial value "12:00".</p>
+<p><input type=datetime-local value=2015-01-01T12:00> This datetime (local) field has the initial value "2015-01-01T12:00".</p>
+<p><input type=number value=1> This number field has the initial value "1".</p>
+<p><input type=range value=1> This range control has the initial value "1".</p>
+<p><input type=color value=#ff0000> This color picker has the initial value "#FF0000".</p>
+<p><input type="button" value="Clone" onclick="clone();"></p>
+</form>
+<script>
+setup(function() {
+ let form = document.getElementsByTagName("form")[0];
+ let inputs = form.getElementsByTagName("input");
+ inputs[0].checked = true;
+ inputs[1].checked = false;
+ inputs[2].checked = true;
+ inputs[4].value = "CHANGED\nCHANGED";
+ inputs[5].value = "CHANGED";
+ inputs[6].value = "CHANGED";
+ inputs[7].value = "CHANGED";
+ inputs[8].value = "https://changed.invalid/";
+ inputs[9].value = "changed@changed.invalid";
+ inputs[10].value = "CHANGED";
+ inputs[11].value = "2016-01-01";
+ inputs[12].value = "2016-01";
+ inputs[13].value = "2016-W01";
+ inputs[14].value = "12:30";
+ inputs[15].value = "2016-01-01T12:30";
+ inputs[16].value = "2";
+ inputs[17].value = "2";
+ inputs[18].value = "#00ff00";
+ let clone = form.cloneNode(true);
+ document.body.appendChild(clone);
+});
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_true(inputs[0].checked, "Should have retained checked state");
+}, "Checkbox must retain checked state.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_false(inputs[1].checked, "Should have retained unchecked state");
+}, "Checkbox must retain unchecked state.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_true(inputs[2].checked, "Should have retained checked state");
+}, "Radiobutton must retain checked state.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_false(inputs[3].checked, "Should have retained unchecked state");
+}, "Radiobutton must retain unchecked state.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[4].value, "CHANGED\nCHANGED", "Should have retained the changed value.");
+}, "Hidden field must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[5].value, "CHANGED", "Should have retained the changed value.");
+}, "Text field must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[6].value, "CHANGED", "Should have retained the changed value.");
+}, "Search field must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[7].value, "CHANGED", "Should have retained the changed value.");
+}, "Phone number field must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[8].value, "https://changed.invalid/", "Should have retained the changed value.");
+}, "URL field must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[9].value, "changed@changed.invalid", "Should have retained the changed value.");
+}, "Email field must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[10].value, "CHANGED", "Should have retained the changed value.");
+}, "Password field must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[11].value, "2016-01-01", "Should have retained the changed value.");
+}, "Date field must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[12].value, "2016-01", "Should have retained the changed value.");
+}, "Month field must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[13].value, "2016-W01", "Should have retained the changed value.");
+}, "Week field must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[14].value, "12:30", "Should have retained the changed value.");
+}, "Time field must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[15].value, "2016-01-01T12:30", "Should have retained the changed value.");
+}, "Datetime (local) field must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[16].value, "2", "Should have retained the changed value.");
+}, "Number field must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[17].value, "2", "Should have retained the changed value.");
+}, "Range control must retain changed value.");
+test(function() {
+ let clone = document.getElementsByTagName("form")[1];
+ let inputs = clone.getElementsByTagName("input");
+ assert_equals(inputs[18].value, "#00ff00", "Should have retained the changed value.");
+}, "Color picker must retain changed value.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/cloning-steps.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/cloning-steps.html
new file mode 100644
index 0000000000..fe468509e8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/cloning-steps.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Cloning of input elements</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-node-clonenode">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-clone">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-clone-ext">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#the-input-element:concept-node-clone-ext">
+<link rel="author" title="Matthew Phillips" href="mailto:matthew@matthewphillips.info">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script type=module>
+import inputTypes from "./input-types.js";
+
+test(function() {
+ var input = document.createElement("input");
+ input.value = "foo bar";
+
+ var copy = input.cloneNode();
+ assert_equals(copy.value, "foo bar");
+}, "input element's value should be cloned");
+
+test(function() {
+ var input = document.createElement("input");
+ input.value = "foo bar";
+
+ var copy = input.cloneNode();
+ copy.setAttribute("value", "something else");
+
+ assert_equals(copy.value, "foo bar");
+}, "input element's dirty value flag should be cloned, so setAttribute doesn't affect the cloned input's value");
+
+for (const inputType of inputTypes) {
+ test(function() {
+ var input = document.createElement("input");
+ input.setAttribute("type", inputType);
+ input.indeterminate = true;
+
+ var copy = input.cloneNode();
+ assert_equals(copy.indeterminate, true);
+ }, `input[type=${inputType}] element's indeterminateness should be cloned`);
+
+ test(function() {
+ var input = document.createElement("input");
+ input.setAttribute("type", inputType);
+ input.checked = true;
+
+ var copy = input.cloneNode();
+ assert_equals(copy.checked, true);
+ }, `input[type=${inputType}] element's checkedness should be cloned`);
+
+ test(function() {
+ var input = document.createElement("input");
+ input.setAttribute("type", inputType);
+ input.checked = false;
+
+ var copy = input.cloneNode();
+ copy.setAttribute("checked", "checked");
+
+ assert_equals(copy.checked, false);
+ }, `input[type=${inputType}] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness`);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/color.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/color.html
new file mode 100644
index 0000000000..6164815f66
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/color.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Form input type=color</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/multipage/common-microsyntaxes.html#colors">
+<link rel=help href="https://html.spec.whatwg.org/multipage/multipage/states-of-the-type-attribute.html#color-state-(type=color)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ var colors = [
+ {value: "", expected: "#000000", testname: "Empty value should return #000000"},
+ {expected: "#000000", testname: "Missing value should return #000000"},
+ {value: "#ffffff", expected: "#ffffff", testname: "Valid simple color: should return #ffffff"},
+ {value: "#FFFFFF", expected: "#ffffff", testname: "Valid simple color (containing LATIN CAPITAL LETTERS): should return #ffffff (converted to ASCII lowercase)"},
+ {value: "#0F0F0F", expected: "#0f0f0f", testname: "Zero-padding"},
+ {value: "#fff", expected: "#000000", testname: "Invalid simple color: not 7 characters long"},
+ {value: "fffffff", expected: "#000000", testname: "Invalid simple color: no starting # sign"},
+ {value: "#gggggg", expected: "#000000", testname: "Invalid simple color: non ASCII hex digits"},
+ {value: "foobar", expected: "#000000", testname: "Invalid simple color: foobar"},
+ {value: "#ffffff\u0000", expected: "#000000", testname: "Invalid color: trailing Null (U+0000)"},
+ {value: "#ffffff;", expected: "#000000", testname: "Invalid color: trailing ;"},
+ {value: " #ffffff", expected: "#000000", testname: "Invalid color: leading space"},
+ {value: "#ffffff ", expected: "#000000", testname: "Invalid color: trailing space"},
+ {value: " #ffffff ", expected: "#000000", testname: "Invalid color: leading+trailing spaces"},
+ {value: "crimson", expected: "#000000", testname: "Invalid color: keyword crimson"},
+ {value: "bisque", expected: "#000000", testname: "Invalid color: keyword bisque"},
+ {value: "currentColor", expected: "#000000", testname: "Invalid color: keyword currentColor"},
+ {value: "transparent", expected: "#000000", testname: "Invalid color: keyword transparent"},
+ {value: "ActiveBorder", expected: "#000000", testname: "Invalid color: keyword ActiveBorder"},
+ {value: "inherit", expected: "#000000", testname: "Invalid color: keyword inherit"},
+ {value: "rgb(1,1,1)", expected: "#000000", testname: "Invalid color: rgb(1,1,1)"},
+ {value: "rgb(1,1,1,1)", expected: "#000000", testname: "Invalid color: rgb(1,1,1,1)"},
+ {value: "#FFFFF\u1F4A9", expected: "#000000", testname: "Invalid color: PILE OF POO (U+1F4A9)"}
+ ];
+ for (var i = 0; i < colors.length; i++) {
+ var w = colors[i];
+ test(function() {
+ var input = document.createElement("input");
+ input.type = "color";
+ input.value = w.value;
+ assert_equals(input.value, w.expected);
+ }, w.testname);
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/date.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/date.html
new file mode 100644
index 0000000000..9b95b86b16
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/date.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Inputs Date</title>
+ <link rel="author" title="Morishita Hiromitsu" href="mailto:hero@asterisk-works.jp">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#date-state-(type=date)">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dates-and-times">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <h1>Inputs Date</h1>
+ <div style="display: none">
+ <input id="valid" type="date" value="2011-11-01" min="2011-01-01" max="2011-12-31" />
+ <input id="too_small_value" type="date" value="1999-01-31" min="2011-01-01" max="2011-12-31"/>
+ <input id="too_large_value" type="date" value="2099-01-31" min="2011-01-01" max="2011-12-31"/>
+ <input id="invalid_min" type="date" value="2011-01-01" min="1999-1" max="2011-12-31"/>
+ <input id="invalid_max" type="date" value="2011-01-01" min="2011-01-01" max="2011-13-162-777"/>
+ <input id="min_larger_than_max" type="date" value="2011-01-01" min="2099-01-01" max="2011-12-31"/>
+ <input id="invalid_value" type="date" value="invalid-date" min="2011-01-01" max="2011-12-31"/>
+ </div>
+
+ <div id="log"></div>
+
+ <script type="text/javascript">
+ test(function() {
+ assert_equals(document.getElementById("valid").type, "date")
+ }, "date type support on input element");
+
+ test(function() {
+ assert_equals(document.getElementById("valid").value, "2011-11-01");
+ assert_equals(document.getElementById("too_small_value").value, "1999-01-31");
+ assert_equals(document.getElementById("too_large_value").value, "2099-01-31");
+ }, "The value attribute, if specified and not empty, must have a value that is a valid date string.");
+
+ test(function() {
+ assert_equals(document.getElementById("valid").min, "2011-01-01");
+ assert_equals(document.getElementById("invalid_min").min, "1999-1");
+ }, "The min attribute must be reflected verbatim by the min property.");
+
+ test(function() {
+ assert_equals(document.getElementById("valid").max, "2011-12-31");
+ assert_equals(document.getElementById("min_larger_than_max").max, "2011-12-31");
+ assert_equals(document.getElementById("invalid_max").max, "2011-13-162-777");
+ }, "The max attribute must be reflected verbatim by the max property.");
+
+ test(function() {
+ assert_equals(document.getElementById("invalid_value").value, "");
+ }, "User agents must not allow the user to set the value to a non-empty string that is not a valid date string.");
+ test(function() {
+ var numDays = [
+ // the number of days in month month of year year is: 31 if month is 1, 3, 5, 7, 8, 10, or 12;
+ {value: "2014-01-31", expected: "2014-01-31", testname: "January has 31 days"},
+ {value: "2014-01-32", expected: "", testname: "January has 31 days"},
+ {value: "2014-03-31", expected: "2014-03-31", testname: "March has 31 days"},
+ {value: "2014-03-32", expected: "", testname: "March has 31 days"},
+ {value: "2014-05-31", expected: "2014-05-31", testname: "May has 31 days"},
+ {value: "2014-05-32", expected: "", testname: "May has 31 days"},
+ {value: "2014-07-31", expected: "2014-07-31", testname: "July has 31 days"},
+ {value: "2014-07-32", expected: "", testname: "July has 31 days"},
+ {value: "2014-08-31", expected: "2014-08-31", testname: "August has 31 days"},
+ {value: "2014-08-32", expected: "", testname: "August has 31 days"},
+ {value: "2014-10-31", expected: "2014-10-31", testname: "October has 31 days"},
+ {value: "2014-10-32", expected: "", testname: "October has 31 days"},
+ {value: "2014-12-31", expected: "2014-12-31", testname: "December has 31 days"},
+ {value: "2014-12-32", expected: "", testname: "December has 31 days"},
+ // the number of days in month month of year year is: 30 if month is 4, 6, 9, or 11;
+ {value: "2014-04-30", expected: "2014-04-30", testname: "April has 30 days"},
+ {value: "2014-04-31", expected: "", testname: "April has 30 days"},
+ {value: "2014-06-30", expected: "2014-06-30", testname: "June has 30 days"},
+ {value: "2014-06-31", expected: "", testname: "June has 30 days"},
+ {value: "2014-09-30", expected: "2014-09-30", testname: "September has 30 days"},
+ {value: "2014-09-31", expected: "", testname: "September has 30 days"},
+ {value: "2014-11-30", expected: "2014-11-30", testname: "November has 30 days"},
+ {value: "2014-11-31", expected: "", testname: "November has 30 days"},
+ // leap years
+ {value: "2014-02-28", expected: "2014-02-28", testname: "2014 is not a leap year: February has 28 days"},
+ {value: "2014-02-29", expected: "", testname: "2014 is not a leap year: February has 28 days: value should be empty"},
+ {value: "2016-02-29", expected: "2016-02-29", testname: "2016 is a leap year: February has 29 days"}
+ ];
+ for (var i = 0; i < numDays.length; i++) {
+ var input = document.createElement("input");
+ input.type = "date";
+ input.value = numDays[i].value;
+ assert_equals(input.value, numDays[i].expected, numDays[i].testname);
+ }
+ }, "Number of days");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/datetime-local-trailing-zeros.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/datetime-local-trailing-zeros.html
new file mode 100644
index 0000000000..0fb031d510
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/datetime-local-trailing-zeros.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel=help href="https://html.spec.whatwg.org/multipage/multipage/common-microsyntaxes.html#local-dates-and-times">
+<link rel=help href="https://html.spec.whatwg.org/multipage/multipage/states-of-the-type-attribute.html#local-date-and-time-state-(type=datetime-local)">
+
+<input id=input type=datetime-local value="2022-04-19T12:34:56.010">
+
+<script>
+test(() => {
+ assert_equals(input.value, '2022-04-19T12:34:56.01');
+}, 'Verifies that trailing zeros in the milliseconds portion of the date strings are removed.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/datetime-local.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/datetime-local.html
new file mode 100644
index 0000000000..2fe24d7e8a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/datetime-local.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Form input type=datetime-local</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/multipage/common-microsyntaxes.html#local-dates-and-times">
+<link rel=help href="https://html.spec.whatwg.org/multipage/multipage/states-of-the-type-attribute.html#local-date-and-time-state-(type=datetime-local)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ var datetimeLocal = [
+ {value: "", expected: "", testname: "empty value"},
+ {value: "2014-01-01T11:11:11.111", expected: "2014-01-01T11:11:11.111", testname: "datetime-local input value set to 2014-01-01T11:11:11.111 without min/max"},
+ {value: "2014-01-01 11:11:11.111", expected: "2014-01-01T11:11:11.111", testname: "datetime-local input value set to 2014-01-01 11:11:11.111 without min/max"},
+ {value: "2014-01-01 11:11", expected: "2014-01-01T11:11", testname: "datetime-local input value set to 2014-01-01 11:11 without min/max"},
+ {value: "2014-01-01 00:00:00.000", expected: "2014-01-01T00:00", testname: "datetime-local input value set to 2014-01-01 00:00:00.000 without min/max"},
+ {value: "2014-01-0 11:11", expected: "", testname: "datetime-local input value set to 2014-01-0 11:11 without min/max"},
+ {value: "2014-01-01 11:1", expected: "", testname: "datetime-local input value set to 2014-01-01 11:1 without min/max"},
+ {value: "2014-01-01 11:1d1", expected: "", testname: "invalid datetime-local input value 1"},
+ {value: "2014-01-01H11:11", expected: "", testname: "invalid datetime-local input value 2"},
+ {value: "2014-01-01 11:11:", expected: "", testname: "invalid datetime-local input value 3"},
+ {value: "2014-01-01 11-11", expected: "", testname: "invalid datetime-local input value 4"},
+ {value: "2014-01-01 11:11:123", expected: "", testname: "invalid datetime-local input value 5"},
+ {value: "2014-01-01 11:11:12.1234", expected: "", testname: "invalid datetime-local input value 6"},
+ {value: "2014-01-01 11:12", attributes: { min: "2014-01-01 11:11" }, expected: "2014-01-01T11:12", testname: "Value >= min attribute"},
+ {value: "2014-01-01 11:10", attributes: { min: "2014-01-01 11:11" }, expected: "2014-01-01T11:10", testname: "Value < min attribute"},
+ {value: "2014-01-01 11:10", attributes: { max: "2014-01-01 11:11" }, expected: "2014-01-01T11:10", testname: "Value <= max attribute"},
+ {value: "2014-01-01 11:12", attributes: { max: "2014-01-01 11:11" }, expected: "2014-01-01T11:12", testname: "Value > max attribute"}
+ ];
+ for (var i = 0; i < datetimeLocal.length; i++) {
+ var w = datetimeLocal[i];
+ test(function() {
+ var input = document.createElement("input");
+ input.type = "datetime-local";
+ input.value = w.value;
+ for(var attr in w.attributes) {
+ input[attr] = w.attributes[attr];
+ }
+ assert_equals(input.value, w.expected);
+ }, w.testname);
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/datetime-weekmonth.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/datetime-weekmonth.html
new file mode 100644
index 0000000000..4a7b66ddd1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/datetime-weekmonth.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Date and Time Inputs</title>
+ <meta name=viewport content="width=device-width, maximum-scale=1.0, user-scalable=no" />
+ <link rel="author" title="Fabrice Clari" href="mailto:f.clari@inno-group.com">
+ <link rel="author" title="Dimitri Bocquet" href="mailto:Dimitri.Bocquet@mosquito-fp7.eu">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-input-element">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-type">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-value">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-min">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-max">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-step">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-stepup">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-stepdown">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+
+ <h1>Date and Time Inputs</h1>
+ <div style="display: none">
+ <input type="month" value="2011-01" min="2011-01" max="2011-12" step="2" />
+ <input type="week" value="2011-W40" min="2011-W20" max="2011-W50" step="2" />
+ </div>
+
+ <div id="log">
+ </div>
+
+ <script type="text/javascript">
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].type, "month")}, "month type support on input element");
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].value, "2011-01")}, "[month] The value must be a value that is a valid global date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].min, "2011-01")}, "[month] The min attribute must have a value that is a valid global date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].max, "2011-12")}, "[month] The max attribute must have a value that is a valid global date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].step, "2")}, "[month] The step attribute must be expressed in seconds");
+ test(function() {assert_true(typeof(document.getElementsByTagName("input")[0].stepUp) == "function")}, "[month] stepUp method support on input 'month' element");
+ test(function() {assert_true(typeof(document.getElementsByTagName("input")[0].stepDown) == "function")}, "[month] stepDown method support on input 'month' element");
+
+ test(function() {assert_equals(document.getElementsByTagName("input")[1].type, "week")}, "week type support on input element");
+ test(function() {assert_equals(document.getElementsByTagName("input")[1].value, "2011-W40")}, "[week] The value must be a value that is a valid global date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[1].min, "2011-W20")}, "[week] The min attribute must have a value that is a valid global date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[1].max, "2011-W50")}, "[week] The max attribute must have a value that is a valid global date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[1].step, "2")}, "[week] The step attribute must be expressed in seconds");
+ test(function() {assert_true(typeof(document.getElementsByTagName("input")[1].stepUp) == "function")}, "[week] stepUp method support on input 'week' element");
+ test(function() {assert_true(typeof(document.getElementsByTagName("input")[1].stepDown) == "function")}, "[week] stepDown method support on input 'week' element");
+
+ </script>
+
+ </body>
+
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/datetime.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/datetime.html
new file mode 100644
index 0000000000..e762060ea7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/datetime.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Date and Time Inputs</title>
+ <meta name=viewport content="width=device-width, maximum-scale=1.0, user-scalable=no" />
+ <link rel="author" title="Fabrice Clari" href="mailto:f.clari@inno-group.com">
+ <link rel="author" title="Dimitri Bocquet" href="mailto:Dimitri.Bocquet@mosquito-fp7.eu">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-input-element">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-type">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-value">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-min">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-max">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-step">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-stepup">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-stepdown">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+
+ <h1>Date and Time Inputs</h1>
+ <div style="display: none">
+ <input type="date" value="2011-12-01" min="2011-12-01" max="2011-12-31" step="5" />
+ <input type="time" value= "12:00" min="11:30" max="14:00" step="600" />
+ <input type="datetime-local" value="2011-12-01T12:00" min="2011-12-01T12:00" max="2011-12-31T22:00" step="7200" />
+ </div>
+
+ <div id="log">
+ </div>
+
+ <script type="text/javascript">
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].type, "date")}, "date type support on input element");
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].value, "2011-12-01")}, "[date] The value must be a valid global date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].min, "2011-12-01")}, "[date] The min attribute must have a value that is a valid global date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].max, "2011-12-31")}, "[date] The max attribute must have a value that is a valid global date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].step, "5")}, "[date] The step attribute must be expressed in seconds");
+ test(function() {assert_true(typeof(document.getElementsByTagName("input")[0].stepUp) == "function")}, "[date] stepUp method support on input 'date' element");
+ test(function() {assert_true(typeof(document.getElementsByTagName("input")[0].stepDown) == "function")}, "[date] stepDown method support on input 'date' element");
+
+ test(function() {assert_equals(document.getElementsByTagName("input")[1].type, "time")}, "[time] time type support on input element");
+ test(function() {assert_equals(document.getElementsByTagName("input")[1].value, "12:00")}, "[time] The value must be a valid global date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[1].min, "11:30")}, "[time] The min attribute must have a value that is a valid global date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[1].max, "14:00")}, "[time] The max attribute must have a value that is a valid global date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[1].step, "600")}, "[time] The step attribute must be expressed in seconds");
+ test(function() {assert_true(typeof(document.getElementsByTagName("input")[1].stepUp) == "function")}, "[time] stepUp method support on input 'time' element");
+ test(function() {assert_true(typeof(document.getElementsByTagName("input")[1].stepDown) == "function")}, "[time] stepDown method support on input 'time' element");
+
+ test(function() {assert_equals(document.getElementsByTagName("input")[2].type, "datetime-local")}, "datetime-local type support on input element");
+ test(function() {assert_equals(document.getElementsByTagName("input")[2].value, "2011-12-01T12:00")}, "[datetime-local] The must be a valid local date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[2].min, "2011-12-01T12:00")}, "[datetime-local] The min attribute must have a value that is a valid local date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[2].max, "2011-12-31T22:00")}, "[datetime-local] The max attribute must have a value that is a valid local date and time string");
+ test(function() {assert_equals(document.getElementsByTagName("input")[2].step, "7200")}, "[datetime-local] The step attribute must be expressed in seconds");
+ test(function() {assert_true(typeof(document.getElementsByTagName("input")[2].stepUp) == "function")}, "[datetime-local] stepUp method support on input 'datetime-local' element");
+ test(function() {assert_true(typeof(document.getElementsByTagName("input")[2].stepDown) == "function")}, "[datetime-local] stepDown method support on input 'datetime-local' element");
+
+ </script>
+
+ </body>
+
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/defaultValue-clobbering.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/defaultValue-clobbering.html
new file mode 100644
index 0000000000..41ff967c19
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/defaultValue-clobbering.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<meta name="assert" content="Assigning to defaultValue does not modify text a user has already typed in.">
+
+<!-- This behavior is not explicitly specified. -->
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<div>
+ email with leading whitespace: <input id=emailinput type=email>
+</div>
+<div>
+ number with trailing incomplete exponent: <input id=numberinput type=number>
+</div>
+
+<script>
+promise_test(async () => {
+ await test_driver.send_keys(emailinput, ' user');
+ assert_false(emailinput.validity.valid, '" user" should not be a valid value for type=email.');
+
+ emailinput.defaultValue = emailinput.value;
+ assert_false(emailinput.validity.valid, 'Assigning to defaultValue should not affect input.validity.');
+}, 'Visible value and validity should not be affected when assigning to the defaultValue property for type=email.');
+
+promise_test(async () => {
+ await test_driver.send_keys(numberinput, '123e');
+ assert_false(numberinput.validity.valid, '"123e" should not be a valid value for type=number.');
+
+ numberinput.defaultValue = numberinput.value;
+ assert_false(numberinput.validity.valid, 'Assigning to defaultValue should not affect input.validity.');
+}, 'Visible value and validity should not be affected when assigning to the defaultValue property for type=number.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/disabled-click-picker-manual.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/disabled-click-picker-manual.html
new file mode 100644
index 0000000000..b77f981e6c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/disabled-click-picker-manual.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Disabled input elements must not open pickers</title>
+<p>
+ Click the Open buttons below. If clicking them does not open any pickers, then consider this as a passing test.<br>
+ (This is manual because we don't have an event to check whether the picker is opened or not.)
+</p>
+<input disabled type="color" id="color"><button>Open</button><br>
+<input disabled type="file" id="file"><button>Open</button>
+<script>
+ for (const button of document.getElementsByTagName("button")) {
+ button.onclick = () => {
+ const input = button.previousElementSibling;
+ input.dispatchEvent(new MouseEvent("click"));
+ }
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/email-set-value.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/email-set-value.html
new file mode 100644
index 0000000000..95245fb824
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/email-set-value.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Input Email setValue</title>
+<link rel="author" href="mailto:atotic@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#e-mail-state-(type=email)">
+<link rel="help" href="https://crbug.com/423785">
+<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>
+
+<input type="email">
+
+<script>
+promise_test(async () => {
+ let input = document.querySelector("input");
+ let unsanitized = ' foo@bar ';
+ let sanitized = unsanitized.trim();
+ await test_driver.send_keys(input, unsanitized);
+ input.select();
+ assert_equals(input.value, sanitized, "value is sanitized");
+ assert_equals(window.getSelection().toString(), unsanitized,
+ "visible value is unsanitized");
+ input.value = sanitized;
+ input.select();
+ assert_equals(window.getSelection().toString(), sanitized,
+ "visible value is sanitized after setValue(sanitized)");
+},
+"setValue(sanitizedValue) is reflected in visible text field content");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/email.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/email.html
new file mode 100644
index 0000000000..c187d89bad
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/email.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<title>Input Email</title>
+<link rel="author" title="Kazuki Kanamori" href="mailto:yogurito@gmail.com">
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#e-mail-state-(type=email)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<input type="email" id="single_email" value="user@example.com"/>
+<input type="email" id="multiple_email" value="user1@example.com, user2@test.com" multiple/>
+<div id="log"></div>
+
+<script type="text/javascript">
+ var single = document.getElementById('single_email'),
+ mult = document.getElementById('multiple_email');
+
+ test(function(){
+ assert_false(single.multiple);
+ }, "single_email doesn't have the multiple attribute");
+
+ test(function(){
+ single.value = 'user2@example.com\u000A';
+ assert_equals(single.value, 'user2@example.com');
+ single.value = 'user3@example.com\u000D';
+ assert_equals(single.value, 'user3@example.com');
+ }, 'value should be sanitized: strip line breaks');
+
+ test(function(){
+ single.value = 'user4@example.com';
+ assert_true(single.validity.valid);
+ single.value = 'example.com';
+ assert_false(single.validity.valid);
+ }, 'Email address validity');
+
+ test(function(){
+ single.setAttribute('multiple', true);
+ single.value = ' user@example.com , user2@example.com ';
+ assert_equals(single.value, 'user@example.com,user2@example.com');
+ single.removeAttribute('multiple');
+ assert_equals(single.value, 'user@example.com,user2@example.com');
+ }, 'When the multiple attribute is removed, the user agent must run the value sanitization algorithm');
+
+ test(function(){
+ assert_true(mult.multiple);
+ }, "multiple_email has the multiple attribute");
+
+ test(function(){
+ mult.value = ' user1@example.com , user2@test.com, user3@test.com ';
+ assert_equals(mult.value, 'user1@example.com,user2@test.com,user3@test.com');
+ }, "run the value sanitization algorithm after setting a new value");
+
+ test(function(){
+ mult.value = 'user1@example.com,user2@test.com,user3@test.com';
+ assert_true(mult.validity.valid);
+
+ mult.value = 'u,ser1@example.com,user2@test.com,user3@test.com';
+ assert_false(mult.validity.valid);
+ }, "valid value is a set of valid email addresses separated by a single ','");
+
+ test(function(){
+ mult.removeAttribute('multiple');
+ mult.value = 'user1@example.com , user2@example.com';
+ assert_equals(mult.value, 'user1@example.com , user2@example.com');
+ mult.setAttribute('multiple', true);
+ assert_equals(mult.value, 'user1@example.com,user2@example.com');
+ }, 'When the multiple attribute is set, the user agent must run the value sanitization algorithm');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/event-select-manual.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/event-select-manual.html
new file mode 100644
index 0000000000..ed0b21e9f3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/event-select-manual.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLInputElement Test: select event</title>
+<link rel="author" title="Intel" href="www.intel.com/">
+<meta name="flags" content="interact">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-select">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<form id="testForm" name="testForm">
+ <input id="testInput" type="text" value="0123456789"/>
+</form>
+
+<h2>Description</h2>
+<p>
+ This test validates that select characters in input element should fired select event.
+</p>
+
+<h2>Test steps:</h2>
+<ol>
+ <li>
+ Select any numeric characters in the input flag below
+ </li>
+</ol>
+
+<script>
+
+let input = document.getElementById("testInput");
+
+setup({explicit_done : true, explicit_timeout : true});
+
+on_event(input, "select", evt => {
+ test(() => {
+ assert_greater_than(input.value.substring(input.selectionStart, input.selectionEnd).length, 0, "Check if the select event captured when text selected");
+ });
+ done();
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/file-manual.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/file-manual.html
new file mode 100644
index 0000000000..9e2d47c423
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/file-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>input type file</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<p>Manual test: clicking on the input should open a prompt allowing you to select a file.</p>
+<input type=file id=file>
+<script>
+ setup({explicit_timeout:true});
+
+ var input = document.getElementById('file'),
+ t1 = async_test("selecting files should fire the input event at the input element"),
+ t2 = async_test("selecting files should fire the change event at the input element");
+
+ document.getElementById('file').oninput = t1.step_func_done(function(e) {
+ assert_true(e.bubbles, "input event bubbles");
+ assert_true(e.isTrusted, "input event should be trusted");
+ assert_false(e.cancelable, "input event should not be cancelable");
+ })
+ document.getElementById('file').onchange = t2.step_func_done(function(e) {
+ assert_true(e.bubbles, "change event bubbles");
+ assert_true(e.isTrusted, "change event should be trusted");
+ assert_false(e.cancelable, "change event should not be cancelable");
+ assert_true(input.files instanceof FileList);
+ assert_equals(input.value, "C:\\fakepath\\" + input.files[0].name);
+ })
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/files.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/files.html
new file mode 100644
index 0000000000..43ebd71906
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/files.html
@@ -0,0 +1,83 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLInputElement#files</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var types = [
+ "hidden",
+ "text",
+ "search",
+ "tel",
+ "url",
+ "email",
+ "password",
+ "date",
+ "month",
+ "week",
+ "time",
+ "datetime-local",
+ "number",
+ "range",
+ "color",
+ "checkbox",
+ "radio",
+ "submit",
+ "image",
+ "reset",
+ "button",
+];
+
+types.forEach(function(type) {
+ test(function() {
+ const input = document.createElement("input"),
+ input2 = document.createElement("input");
+ input.type = type;
+ input2.type = "file";
+ assert_equals(input.files, null, "files should be null");
+
+ input.files = input2.files;
+ assert_equals(input.files, null, "files should remain null as it cannot be set when it does not apply");
+ }, "files for input type=" + type);
+});
+
+test(function() {
+ var input = document.createElement("input");
+ input.type = "file";
+ assert_not_equals(input.files, null);
+ assert_true(input.files instanceof FileList, "files should be a FileList");
+ var files = input.files;
+ assert_equals(input.files, files, "files should return the same object");
+}, "files for input type=file");
+
+test(() => {
+ const i1 = document.createElement("input"),
+ i2 = document.createElement("input");
+ i1.type = "file";
+ i2.type = "file";
+
+ const files = i2.files;
+ i1.files = i2.files;
+ assert_equals(i1.files, files, "FileList should not be copied");
+ assert_equals(i2.files, files, "FileList can be shared across input elements");
+
+ i1.files = null;
+ assert_equals(i1.files, files, "files cannot be set to null");
+
+ assert_throws_js(TypeError, () => i1.files = [], "files cannot be set to an array");
+ assert_throws_js(TypeError, () => i1.files = [new File([], "x")], "files cannot be set to an array (even when it contains File objects)");
+}, "setting <input type=file>.files");
+
+test(() => {
+ const i = document.createElement("input");
+ i.type = "file";
+
+ let dt = new DataTransfer();
+
+ const files = dt.files;
+ i.files = files;
+ assert_equals(i.files, files, "FileList should not be copied");
+ assert_equals(dt.files, files, "FileList can be shared across input / DataTransfer");
+}, "setting <input type=file>.files from DataTransfer");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/focus-dynamic-type-change.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/focus-dynamic-type-change.html
new file mode 100644
index 0000000000..a1d3dfa2a2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/focus-dynamic-type-change.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Input type switch on focused input shouldn't blur</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script type=module>
+import inputTypes from "./input-types.js";
+
+function tick() {
+ return new Promise(resolve => {
+ requestAnimationFrame(() => requestAnimationFrame(resolve));
+ });
+}
+
+function test_from_to(fromType, toType) {
+ if (fromType == toType) {
+ return;
+ }
+ promise_test(async function(t) {
+ const input = document.createElement("input");
+ input.type = fromType;
+ document.body.appendChild(input);
+ input.focus();
+ assert_equals(document.activeElement, input, `${fromType} input should be focused`);
+ function onFocus() {
+ t.assert_unreached("shouldn't be getting spurious focus events");
+ }
+ function onBlur() {
+ t.assert_unreached("shouldn't be getting spurious blur events");
+ }
+ input.addEventListener("focus", onFocus);
+ input.addEventListener("blur", onBlur);
+ input.type = toType;
+ assert_equals(document.activeElement, input, `${fromType} input should be focused after change to ${toType}`);
+ await tick();
+ assert_equals(document.activeElement, input, `${fromType} input should still be focused after change to ${toType}`);
+ input.removeEventListener("focus", onFocus);
+ input.removeEventListener("blur", onBlur);
+ }, `${fromType} -> ${toType}`);
+}
+
+for (let type of inputTypes) {
+ if (type == "hidden") {
+ continue; // hidden inputs are not focusable
+ }
+ test_from_to(type, "text");
+ test_from_to("text", type);
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/hidden-charset-case-sensitive-child.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/hidden-charset-case-sensitive-child.html
new file mode 100644
index 0000000000..92c9981a11
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/hidden-charset-case-sensitive-child.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+parent.postMessage(location.href, "*");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/hidden-charset-case-sensitive.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/hidden-charset-case-sensitive.html
new file mode 100644
index 0000000000..537500c91f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/hidden-charset-case-sensitive.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/#hidden-state-(type=hidden):attr-fe-name-charset">
+<meta name="assert" content="special input@name value “_charset_” is case-sensitive">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<form target="child" method="GET" action="hidden-charset-case-sensitive-child.html">
+ <input type="hidden" name="_charset_">
+ <input type="hidden" name="_CHARSET_">
+ <input type="hidden" name="_ChArSeT_">
+ <input type="hidden" name="_charſet_">
+</form>
+<iframe name="child"></iframe>
+<script>
+// #attr-fe-name-charset only affects form submission, so we need to do that
+async_test(function() {
+ // we use a message rather than the iframe’s load event to avoid dealing with
+ // spurious load events that some browsers dispatch on the initial about:blank
+ addEventListener("message", this.step_func_done(event => {
+ const params = new URL(event.data).searchParams;
+
+ assert_equals(params.get("_charset_"), "UTF-8", "lowercase valid");
+ assert_equals(params.get("_CHARSET_"), "UTF-8", "uppercase valid");
+ assert_equals(params.get("_ChArSeT_"), "UTF-8", "mixed case invalid");
+ assert_equals(params.get("_charſet_"), "", "non-ASCII invalid");
+ }));
+
+ document.querySelector("form").submit();
+}, "keyword _charset_");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/hidden.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/hidden.html
new file mode 100644
index 0000000000..9274b5cddb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/hidden.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Hidden input element</title>
+ <link rel="author" title="Kinuko Yasuda" href="mailto:kinuko@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#hidden-state-(type=hidden)">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+
+ <body>
+ <h1>Hidden input element</h1>
+ <div style="display: none">
+
+ <input id="hidden" type="hidden" />
+ <input id="hidden_with_value" type="hidden" value="foo" />
+
+ </div>
+ <div id="log"></div>
+ <script type="text/javascript">
+
+ test(
+ function() {
+ assert_equals(document.getElementById("hidden").value, "");
+ assert_equals(document.getElementById("hidden_with_value").value, "foo");
+ }, "Value returns the current value for hidden");
+
+ test(
+ function() {
+ document.getElementById("hidden").value = "A";
+ assert_equals(document.getElementById("hidden").value, "A");
+ document.getElementById("hidden").value = "B";
+ assert_equals(document.getElementById("hidden").value, "B");
+ }, "Setting value changes the current value for hidden");
+
+ test(
+ function() {
+ assert_equals(document.getElementById("hidden").files, null);
+ }, "files attribute must return null for hidden");
+
+ test(
+ function() {
+ assert_equals(document.getElementById("hidden").valueAsDate, null);
+ }, "valueAsDate attribute must return null for hidden");
+
+ test(
+ function() {
+ assert_equals(document.getElementById("hidden").valueAsNumber, NaN);
+ }, "valueAsNumber attribute must return NaN for hidden");
+
+ test(
+ function() {
+ assert_equals(document.getElementById("hidden").list, null);
+ }, "list attribute must return null for hidden");
+
+ test(
+ function() {
+ var el = document.getElementById("hidden");
+ assert_throws_dom("InvalidStateError", function() { el.stepDown(); }, "");
+ }, "stepDown does not apply for hidden");
+
+ test(
+ function() {
+ var el = document.getElementById("hidden");
+ assert_throws_dom("InvalidStateError", function() { el.stepUp(); }, "");
+ }, "stepUp does not apply for hidden");
+
+ test(function(){
+ var el = document.getElementById("hidden");
+ assert_false(el.willValidate);
+ }, "input type=hidden is barred from constraint validation");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/image-click-form-data.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/image-click-form-data.html
new file mode 100644
index 0000000000..87b77e4805
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/image-click-form-data.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Check form-data for image submit button with non-empty 'value' attribute</title>
+<link rel="author" title="Shanmuga Pandi" href="mailto:shanmuga.m@samsung.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-form-data-set">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+// promise_test instead of async_test because this test use window.success, and so can't run at the same time.
+
+promise_test(t => {
+ return new Promise(resolve => {
+ window.success = t.step_func(locationLoaded => {
+ const expected = (new URL("resources/image-submit-click.html?name.x=0&name.y=0", location.href)).href;
+ assert_equals(locationLoaded, expected);
+ resolve();
+ });
+
+ const iframe = document.createElement("iframe");
+ iframe.src = "resources/image-submit-click.html";
+ document.body.appendChild(iframe);
+ });
+}, "Image submit button should not add extra form data if 'value' attribute is present with non-empty value");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/image01-ref.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/image01-ref.html
new file mode 100644
index 0000000000..62c141d960
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/image01-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>input type image reference file</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<img src="/media/poster.png"/>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/image01.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/image01.html
new file mode 100644
index 0000000000..e9028dceec
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/image01.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>input type image</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#image-button-state-(type=image)">
+<link rel="match" href="image01-ref.html">
+<input type=image id=image src="/media/poster.png">
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-checkvalidity.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-checkvalidity.html
new file mode 100644
index 0000000000..b336204fcc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-checkvalidity.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>input_checkValidity</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <p><input type='hidden' id='input_text'></p>
+ </form>
+ <script>
+
+ var input = document.getElementById("input_text");
+
+ try
+ {
+ var ret = input.checkValidity();
+
+ test(function() {
+ assert_equals(ret, true, "calling of checkValidity method is failed.");
+ });
+ }
+ catch (e) {
+ test(function() {
+ assert_unreached("Error is raised.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-form-detach-style-crash.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-form-detach-style-crash.html
new file mode 100644
index 0000000000..5472563763
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-form-detach-style-crash.html
@@ -0,0 +1,17 @@
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1800543">
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+ b.setCustomValidity("x")
+ a.reportValidity()
+ c.appendChild(a)
+ document.adoptNode(d)
+ document.documentElement.style.display = 'none'
+})
+</script>
+<form id="a">
+<textarea id="b">a</textarea>
+</form>
+<dl id="d">
+<canvas id="c"></canvas>
+</dl>
+<input form="a">
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-height.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-height.html
new file mode 100644
index 0000000000..dea4f41765
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-height.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>input_height</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ name="input_form">
+ <p><input type='image' id='input_text'></p>
+ </form>
+
+ <script>
+
+ var input_text = document.getElementById("input_text");
+ input_text.height = 30;
+
+ if (typeof(input_text.height) == "number") {
+ test(function() {
+ assert_equals(input_text.height, 30, "formTarget attribute is not correct.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("height attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-labels.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-labels.html
new file mode 100644
index 0000000000..77f4d8b31a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-labels.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>input_labels</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <p><label>Full name:<label>(name)<input name=fn id='input_text1'> <small>Format: First Last</small></label></label></p>
+ <p><label>Age: <input name=age type=number min=0 id='input_text2'></label></p>
+ <p><label>Post code: <input name=pc> <small>Format: AB12 3CD</small></label></p>
+ </form>
+ <script>
+
+ var input1 = document.getElementById("input_text1");
+ var input2 = document.getElementById("input_text2");
+
+ if (typeof(input1.labels) == "object") {
+ if (input1.labels.length == 2 && input2.labels.length == 1) {
+ test(function() {
+ assert_true(true, "labels attribute is correct.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("labels attribute is not correct.");
+ });
+ }
+ } else {
+ test(function() {
+ assert_unreached("labels attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-list.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-list.html
new file mode 100644
index 0000000000..006a8fbd8f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-list.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>input list attribute</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>input_list</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ name="input_form">
+ <datalist id="thelist">
+ <option value="one">one</option>
+ <option value="two">two</option>
+ </datalist>
+
+ <p id="non_datalist_first">
+ <datalist id="non_datalist_first">
+ <option value="one">one</option>
+ <option value="two">two</option>
+ </datalist>
+
+ <datalist id="datalist_first">
+ <option value="one">one</option>
+ <option value="two">two</option>
+ </datalist>
+ <p id="datalist_first">
+
+ <p><input list="thelist" id='input_with_list'></p>
+ <p><input id='input_without_list'></p>
+ <p><input list="input_with_list" id='input_with_nondatalist_list'></p>
+ <p><input list="not_an_id" id='input_with_missing_list'></p>
+ <p><input list="non_datalist_first" id='input_with_non_datalist_first'></p>
+ <p><input list="datalist_first" id='input_with_datalist_first'></p>
+ </form>
+
+ <script>
+ test(function() {
+ assert_equals(document.getElementById("input_with_list").list, document.getElementById("thelist"));
+ }, "getting .list of input must return the datalist with that id");
+ test(function() {
+ assert_equals(document.getElementById("input_without_list").list, null);
+ }, "getting .list of input must return null if it has no list attribute");
+ test(function() {
+ assert_equals(document.getElementById("input_with_nondatalist_list").list, null);
+ }, "getting .list of input must return null if the list attribute is a non-datalist's id");
+ test(function() {
+ assert_equals(document.getElementById("input_with_missing_list").list, null);
+ }, "getting .list of input must return null if the list attribute is no element's id");
+ test(function() {
+ assert_equals(document.getElementById("input_with_non_datalist_first").list, null);
+ }, "getting .list of input must return null if the list attribute is used in a non-datalist earlier than a datalist");
+ test(function() {
+ assert_equals(document.getElementById("input_with_datalist_first").list, document.querySelector("datalist#datalist_first"));
+ }, "getting .list of input must return the datalist with that id even if a later non-datalist also has the id");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-seconds-leading-zeroes.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-seconds-leading-zeroes.html
new file mode 100644
index 0000000000..d94bee1029
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-seconds-leading-zeroes.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<html>
+ <head>
+ <title>HTMLInputElement leading zeroes in seconds/millis</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <h3>input times and datetimes with leading zeroes in seconds/millis</h3>
+ <!-- This test ensures that seconds and milliseconds are being
+ output with the appropriate field widths when sanitizing
+ datetime-locals and times, e.g. that we don't see "12:30:1".
+ The spec is not specific about how much precision to use
+ in a sanitized time string, but an invalid string would
+ fail at .valueAsNumber -->
+ <hr>
+ <div id="log"></div>
+
+ <input id="inp">
+ <script>
+ var inp=document.getElementById("inp");
+ var cases = [
+ ["datetime-local", "2000-01-01T12:30:01", 946729801000],
+ ["datetime-local", "2000-01-01T12:30:00.5", 946729800500],
+ ["datetime-local", "2000-01-01T12:30:00.04", 946729800040],
+ ["datetime-local", "2000-01-01T12:30:00.003", 946729800003],
+
+ ["time", "12:30:01", 45001000],
+ ["time", "12:30:00.5", 45000500],
+ ["time", "12:30:00.04", 45000040],
+ ["time", "12:30:00.003", 45000003],
+ ];
+
+ for (var i in cases) {
+ var c = cases[i];
+ test(function() {
+ inp.setAttribute("type", c[0]);
+ inp.value = c[1];
+ assert_equals(inp.valueAsNumber, c[2]);
+ },"Expected valueAsNumber=" +c[2] + " from " + c[1]);
+ if (c[0] == "datetime-local") {
+ test(function() {
+ inp.setAttribute("type", c[0]);
+ inp.value = c[1];
+ assert_in_array(inp.value, [c[1], c[1].replace("T", " ")]);
+ },"Expected digits unchanged in round-trip of " + c[1])
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-setcustomvalidity.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-setcustomvalidity.html
new file mode 100644
index 0000000000..accb24d8f9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-setcustomvalidity.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<title>input setCustomValidity</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<input id='input_test'>
+
+<script>
+
+test(() => {
+ let elem = document.getElementById("input_test");
+ assert_false(elem.validity.customError);
+ elem.setCustomValidity("custom error");
+ assert_true(elem.validity.customError);
+}, "input setCustomValidity is correct")
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepdown-weekmonth.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepdown-weekmonth.html
new file mode 100644
index 0000000000..c50f67fce5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepdown-weekmonth.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<title>Forms</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h3>input_stepDown</h3>
+<input type="month" id="month_input" min="2011-02" step="1" value="2010-02">
+<input type="week" id="week_input" min="2011-W02" step="1" value="2010-W02">
+
+<script>
+ function testStepDownOverflow(id, value, type) {
+ test(function() {
+ var input = document.getElementById(id);
+ input.stepDown();
+ assert_equals(input.value, value, "value shouldn't change.");
+ }, "Calling stepDown() on input - " + type + " - where value < min should not modify value.");
+ }
+
+ testStepDownOverflow("month_input", "2010-02", "month");
+ testStepDownOverflow("week_input", "2010-W02", "week");
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepdown.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepdown.html
new file mode 100644
index 0000000000..3cd246f015
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepdown.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<title>Forms</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h3>input_stepDown</h3>
+<input type='number' id='input_number'>
+<input type="number" id="number_input" min="300" step="1" value="200">
+<input type="date" id="date_input" min="2011-02-10" step="1" value="2010-02-10">
+<input type="datetime-local" id="dtl_input" min="2011-02-10T20:13" step="1" value="2010-02-10T20:13">
+<input type="time" id="time_input" min="21:13" step="60" value="20:13">
+
+<script>
+ var input_number = document.getElementById("input_number");
+ input_number.max = "30";
+ input_number.step = "3";
+ input_number.value = "30";
+ input_number.stepDown(5);
+
+ if (typeof(input_number.stepDown) == "function") {
+ test(function() {
+ assert_equals(input_number.value, "15", "call of stepDown method is failed.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("stepDown attribute is not exist.");
+ });
+ }
+
+ function testStepDownOverflow(id, value, type) {
+ test(function() {
+ var input = document.getElementById(id);
+ input.stepDown();
+ assert_equals(input.value, value, "value shouldn't change.");
+ }, "Calling stepDown() on input - " + type + " - where value < min should not modify value.");
+ }
+
+ testStepDownOverflow("number_input", "200", "number");
+ testStepDownOverflow("date_input", "2010-02-10", "date");
+ testStepDownOverflow("dtl_input", "2010-02-10T20:13", "datetime-local");
+ testStepDownOverflow("time_input", "20:13", "time");
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepup-weekmonth.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepup-weekmonth.html
new file mode 100644
index 0000000000..09316b0854
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepup-weekmonth.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<title>Forms</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h3>input_stepUp</h3>
+<input type="month" id="month_input" max="2009-02" step="1" value="2010-02">
+<input type="week" id="week_input" max="2009-W02" step="1" value="2010-W02">
+
+<script>
+ function testStepUpOverflow(id, value, type) {
+ test(function() {
+ var input = document.getElementById(id);
+ input.stepUp();
+ assert_equals(input.value, value, "value shouldn't change.");
+ }, "Calling stepUp() on input -" + type + "- where value > max should not modify value.");
+ }
+
+ testStepUpOverflow("month_input", "2010-02", "month");
+ testStepUpOverflow("week_input", "2010-W02", "week");
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepup.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepup.html
new file mode 100644
index 0000000000..f6f97eecbe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-stepup.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<title>Forms</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h3>input_stepUp</h3>
+<input type='number' id='input_number'> <br/>
+<input type="number" id="number_input" max="100" step="1" value="200">
+<input type="date" id="date_input" max="2009-02-10" step="1" value="2010-02-10">
+<input type="datetime-local" id="dtl_input" max="2009-02-10T20:13" step="1" value="2010-02-10T20:13">
+<input type="time" id="time_input" max="19:13" step="60" value="20:13">
+
+<script>
+
+ var input_number = document.getElementById("input_number");
+ input_number.max = "30";
+ input_number.step = "3";
+ input_number.value = "0";
+ input_number.stepUp(5);
+
+ if (typeof(input_number.stepUp) == "function") {
+ test(function() {
+ assert_equals(input_number.value, "15", "call of stepUp method is failed.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("stepUp attribute is not exist.");
+ });
+ }
+
+ function testStepUpOverflow(id, value, type) {
+ test(function() {
+ var input = document.getElementById(id);
+ input.stepUp();
+ assert_equals(input.value, value, "value shouldn't change.");
+ }, "Calling stepUp() on input -" + type + "- where value > max should not modify value.");
+ }
+
+ testStepUpOverflow("number_input", "200", "number");
+ testStepUpOverflow("date_input", "2010-02-10", "date");
+ testStepUpOverflow("dtl_input", "2010-02-10T20:13", "datetime-local");
+ testStepUpOverflow("time_input", "20:13", "time");
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-submit-remove-jssubmit.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-submit-remove-jssubmit.html
new file mode 100644
index 0000000000..f992ff9ed5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-submit-remove-jssubmit.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-2">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe name="frame" id="frame"></iframe>
+<form id="form" target="frame" action="does_not_exist.html">
+ <input id="input" name="name" value="foo">
+ <input id="submitbutton" type="submit"></input>
+</form>
+
+<script>
+async_test(t => {
+ window.addEventListener('load', () => {
+ const frame = document.getElementById('frame');
+ frame.addEventListener('load', t.step_func_done(() => {
+ const expected = (new URL("does_not_exist.html?name=bar", location.href)).href;
+ assert_equals(frame.contentWindow.location.href, expected);
+ }));
+
+ const form = document.getElementById('form');
+ const input = document.getElementById('input');
+ const submitButton = document.getElementById('submitbutton');
+ submitButton.addEventListener('click', event => {
+ submitButton.remove();
+ form.submit();
+ input.value = "bar";
+ form.submit();
+ input.value = "baz";
+ });
+
+ submitButton.click();
+ });
+}, 'This test will pass if a form navigation successfully occurs when clicking a <input type=submit> element with a onclick event handler which removes the input and then calls form.submit().');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-button.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-button.html
new file mode 100644
index 0000000000..0f269355a5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-button.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<head>
+<title>input type button</title>
+<link rel="author" title="Takeharu.Oshida" href="mailto:georgeosddev@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#button-state-(type=button)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<div id="hide" style="display">
+ <input type="button"/>
+ <input type="button" value="BUTTON"/>
+ <form action="/" method="get" onsubmit="isSubmitted = true;return false;">
+ <input type="button" value="mutable"/>
+ </form>
+ <form action="/" method="get" onsubmit="isSubmitted = true;return false;">
+ <input type="button" value="immutable" disabled/>
+ </form>
+</div>
+<script>
+var isSubmitted = false;
+var buttons = document.getElementsByTagName("input");
+
+test(function() {
+ assert_equals(buttons[0].click(), undefined, "The input element represents a button with no default behavior");
+},"default behavior");
+
+test(function() {
+ assert_equals(buttons[0].value, "", "It must be the empty string");
+},"empty value attribute");
+
+test(function() {
+ document.getElementById("hide").style.display = "block";
+ assert_not_equals(buttons[0].offsetWidth, buttons[1].offsetWidth, "If the element has a value attribute, the button's label must be the value of that attribute");
+ document.getElementById("hide").style.display = "none";
+},"label value");
+
+test(function() {
+ isSubmitted = false;
+ buttons[2].click();
+ assert_equals(isSubmitted, false, "If the element is mutable, the element's activation behavior is to do nothing.");
+},"mutable element's activation behavior is to do nothing.");
+
+test(function() {
+ isSubmitted = false;
+ buttons[3].click()
+ assert_equals(isSubmitted, false, "If the element is immutable, the element has no activation behavior.");
+},"immutable element has no activation behavior.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-checkbox.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-checkbox.html
new file mode 100644
index 0000000000..7dd2f26b12
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-checkbox.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<head>
+<title>input type checkbox</title>
+<link rel="author" title="Gary Gao" href="mailto:angrytoast@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div style="display:none;">
+ <input id="checkbox_default" type="checkbox" width="20" />
+
+ <input id="checkbox_checked" type="checkbox" checked />
+
+ <input id="checkbox_indeterminate" type="checkbox" />
+
+ <input id="checkbox_default_value" type="checkbox" />
+</div>
+
+<div id="log"></div>
+
+<script>
+ var checkbox_default = document.getElementById('checkbox_default'),
+ checkbox_checked = document.getElementById('checkbox_checked'),
+ checkbox_indeterminate = document.getElementById('checkbox_indeterminate'),
+ checkbox_default_value = document.getElementById('checkbox_default_value');
+
+ test(function() {
+ assert_false(checkbox_default.checked);
+ }, "default checkbox has no checkedness state");
+
+ test(function() {
+ assert_true(checkbox_checked.checked);
+ }, "checkbox with initial state set to checked has checkedness state");
+
+ test(function() {
+ checkbox_default.checked = 'chicken'
+ assert_true(checkbox_default.checked);
+ }, "changing the checked attribute to a string sets the checkedness state");
+
+ test(function() {
+ assert_false(checkbox_indeterminate.indeterminate);
+ }, "a checkbox has an indeterminate state set to false onload");
+
+ test(function() {
+ checkbox_indeterminate.indeterminate = true,
+ assert_true(checkbox_indeterminate.indeterminate);
+ }, "on setting, a checkbox's indeterminate state must be set to the new value and returns the last value it was set to");
+
+ test(function() {
+ assert_equals(checkbox_default_value.value, 'on');
+ }, "default/on: on getting, if the element has a value attribute, it must return that attribute's value; otherwise, it must return the string 'on'");
+
+ test(function() {
+ checkbox_default_value.value = 'chicken'
+ assert_equals(checkbox_default_value.value, 'chicken');
+ }, "on getting, if the element has a value attribute, it must return that attribute's value");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-types.js b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-types.js
new file mode 100644
index 0000000000..4456751052
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-types.js
@@ -0,0 +1,24 @@
+export default [
+ "button",
+ "checkbox",
+ "color",
+ "date",
+ "datetime-local",
+ "email",
+ "file",
+ "hidden",
+ "image",
+ "month",
+ "number",
+ "password",
+ "radio",
+ "range",
+ "reset",
+ "search",
+ "submit",
+ "tel",
+ "text",
+ "time",
+ "url",
+ "week",
+];
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-untrusted-key-event.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-untrusted-key-event.html
new file mode 100644
index 0000000000..eb96b6fd95
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-untrusted-key-event.html
@@ -0,0 +1,225 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Forms</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<form id="input_form">
+ <fieldset>
+ <input type="radio" name="radio" value="1">
+ <input type="radio" name="radio" value="2">
+ </fieldset>
+</form>
+<script type="module">
+import inputTypes from "./input-types.js";
+
+const form = document.querySelector("form");
+form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ assert_true(false, 'form should not be submitted');
+});
+
+const radioButton = document.querySelector("input[type=radio]");
+radioButton.addEventListener("click", function(e) {
+ assert_true(false, `input radio should not be clicked`);
+});
+radioButton.addEventListener("focus", function(e) {
+ assert_true(false, `input radio should not be focused on`);
+});
+radioButton.addEventListener("change", function(e) {
+ assert_true(false, `input radio should not be changed`);
+});
+radioButton.addEventListener("input", function(e) {
+ assert_true(false, `input radio should not have been inputted`);
+});
+
+// Create and append input elements
+for (const inputType of inputTypes) {
+ if (inputType == "radio") {
+ continue;
+ }
+
+ let input = document.createElement("input");
+ input.type = inputType;
+ form.appendChild(input);
+
+ input.addEventListener("click", function(e) {
+ assert_true(false, `input ${inputType} should not be clicked`);
+ });
+ input.addEventListener("focus", function(e) {
+ assert_true(false, `input ${inputType} should not be focused on`);
+ });
+ input.addEventListener("change", function(e) {
+ assert_true(false, `input ${inputType} should not be changed`);
+ });
+ input.addEventListener("input", function(e) {
+ assert_true(false, `input ${inputType} should not have been inputted`);
+ });
+}
+
+// Start tests
+for (const inputType of inputTypes) {
+ let input = document.querySelector(`input[type=${inputType}]`);
+
+ test(() => {
+ // keyCode: Enter
+ input.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ keyCode: 13,
+ })
+ );
+
+ // key: Enter
+ input.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ key: "Enter",
+ })
+ );
+
+ // keyCode: Space
+ input.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ keyCode: 32,
+ })
+ );
+
+ // key: Space
+ input.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ key: " ",
+ })
+ );
+
+ // keyCode: Tab
+ input.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ keyCode: 9,
+ })
+ );
+
+ // key: Tab
+ input.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ key: "Tab",
+ })
+ );
+
+ // keyCode: ArrowUp
+ input.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ keyCode: 38,
+ })
+ );
+
+ // key: ArrowUp
+ input.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ key: "ArrowUp",
+ })
+ );
+ }, `Dispatching untrusted keypress events to input ${inputType} should not cause submission, click, change, input, or focus events`);
+
+ test(() => {
+ // keyCode: Enter
+ input.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ keyCode: 13,
+ })
+ );
+ input.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ keyCode: 13,
+ })
+ );
+
+ // key: Enter
+ input.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ key: "Enter",
+ })
+ );
+ input.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ key: "Enter",
+ })
+ );
+
+ // keyCode: Space
+ input.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ keyCode: 32,
+ })
+ );
+ input.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ keyCode: 32,
+ })
+ );
+
+ // key: Space
+ input.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ key: " ",
+ })
+ );
+ input.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ key: " ",
+ })
+ );
+
+ // keyCode: Tab
+ input.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ keyCode: 9,
+ })
+ );
+ input.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ keyCode: 9,
+ })
+ );
+
+ // key: Tab
+ input.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ key: "Tab",
+ })
+ );
+ input.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ key: "Tab",
+ })
+ );
+
+ // keyCode: ArrowUp
+ input.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ keyCode: 38,
+ })
+ );
+ input.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ keyCode: 38,
+ })
+ );
+
+ // key: ArrowUp
+ input.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ key: "ArrowUp",
+ })
+ );
+ input.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ key: "ArrowUp",
+ })
+ );
+ }, `Dispatching untrusted keyup/keydown events to input ${inputType} should not cause submission, click, change, input, or focus events`);
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-validationmessage.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-validationmessage.html
new file mode 100644
index 0000000000..775c06f06e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-validationmessage.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>input_validationMessage</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <p><input type='hidden' id='input_text'></p>
+ </form>
+ <script>
+
+ var input = document.getElementById("input_text");
+
+ if (typeof(input.validationMessage) == "string") {
+ test(function() {
+ assert_equals(input.validationMessage, "", "validationMessage attribute is not correct.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("validationMessage attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-validity.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-validity.html
new file mode 100644
index 0000000000..719144d511
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-validity.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>input_validity</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <p><input type='hidden' id='input_text'></p>
+ </form>
+ <script>
+
+ var input = document.getElementById("input_text");
+
+ if (typeof(input.validity) == "object") {
+ test(function() {
+ assert_equals(input.validity.valueMissing, false, "validity attribute is not correct.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("validity attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-value-invalidstateerr.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-value-invalidstateerr.html
new file mode 100644
index 0000000000..78e6624e7c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-value-invalidstateerr.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>input_value_INVALID_STATE_ERR</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ name="input_form">
+ <p><input type='file' id='input_file'></p>
+ </form>
+
+ <script>
+
+ var input_file = document.getElementById("input_file");
+ try {
+ input_file.value = "val";
+ test(function() {
+ assert_unreached("INVALID_STATE_ERR error is not raised.");
+ });
+ } catch (e) {
+ test(function() {
+ assert_equals(e.code, e["INVALID_STATE_ERR"], "INVALID_STATE_ERR error is not raised.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasdate-invalidstateerr.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasdate-invalidstateerr.html
new file mode 100644
index 0000000000..bd49a15fc8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasdate-invalidstateerr.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>input_valueAsDate_INVALID_STATE_ERR</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ name="input_form">
+ <p><input type='checkbox' id='input_checkbox'></p>
+ </form>
+
+ <script>
+ var input_checkbox = document.getElementById("input_checkbox");
+ try {
+ input_checkbox.valueAsDate = new Date('2011-11-01');
+ test(function() {
+ assert_reached("INVALID_STATE_ERR error is not raised.");
+ });
+ }
+ catch (e) {
+ test(function() {
+ assert_equals(e.code, e["INVALID_STATE_ERR"], "INVALID_STATE_ERR error is not raised.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasdate-stepping.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasdate-stepping.html
new file mode 100644
index 0000000000..0985611031
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasdate-stepping.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>valueAsDate stepping</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>input_valueAsDate_stepping</h3>
+ <!-- This test verifies that valueAsDate reads and writes Date values,
+ that those values step by the correct default step, and that the values
+ represent the correct times.
+ -->
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ name="input_form">
+ <p><input type='date' id='input_date'></p>
+ <p><input type='time' id='input_time'></p>
+ <p><input type='week' id='input_week'></p>
+ <p><input type='month' id='input_month'></p>
+ </form>
+
+ <script>
+ function test_stepping(inputType, stringValue, steppedString, baseMillis, stepAmount) {
+ test(function() {
+ // put date in, constructed from a UTC timestamp so the test doesn't
+ // vary by local timezone
+ input = document.getElementById("input_" + inputType);
+ input.valueAsDate = new Date(baseMillis)
+
+ // get string out (using startsWith here to allow for optional
+ // seconds and milliseconds)
+ var sanitizedStr = input.value;
+ assert_true(sanitizedStr.startsWith(stringValue),
+ "The input value [" + sanitizedStr + "] must resemble [" + stringValue + "]");
+
+ // get date out
+ var sanitized = input.valueAsDate;
+ assert_equals(sanitized.getTime(), baseMillis, "The input valueAsDate must represent the same time as the original Date.")
+
+ // step up, get new date out
+ input.stepUp()
+ var steppedDate = input.valueAsDate;
+ assert_equals(steppedDate.getTime(), baseMillis + stepAmount, "Stepping must be by the correct amount")
+
+ // get new string out
+ var steppedStrOut = input.value;
+ assert_true(steppedStrOut.startsWith(steppedString),
+ "The changed input value [" + steppedStrOut + "] must resemble ["+steppedString+"]");
+
+ // step back down, get first date out again
+ input.stepDown()
+ var backDown = input.valueAsDate;
+ assert_equals(backDown.getTime(), baseMillis, "Stepping back down must return the date to its original value");
+
+ }, inputType + " should step correctly");
+ }
+
+ var millis_per_day = 24 * 60 * 60 * 1000;
+
+ // jan 1 midnight, step 1 day to jan 2
+ test_stepping("date", "1970-01-01", "1970-01-02", 0, millis_per_day);
+
+ // jan 1 midnight, step 1 minute to 00:01:00
+ test_stepping("time", "00:00", "00:01", 0, 60 * 1000);
+
+ // jan 1 midnight, step 31 days to feb 1
+ test_stepping("month", "1970-01", "1970-02", 0, 31 * millis_per_day);
+
+ // monday jan 5 1970 midnight, step 7 days to jan 12
+ // (this has to start on a monday for stepping up and down to return)
+ test_stepping("week", "1970-W02", "1970-W03", 4 * millis_per_day, 7 * millis_per_day);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasdate.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasdate.html
new file mode 100644
index 0000000000..894983add2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasdate.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<html>
+ <head>
+ <title>HTMLInputElement valueAsDate</title>
+ <link rel="author" title="pmdartus" href="mailto:dartus.pierremarie@gmail.com">
+ <link rel=help href="https://html.spec.whatwg.org/#dom-input-valueasdate">
+
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <h3>input_valueAsDate</h3>
+ <hr>
+ <div id="log"></div>
+
+ <input id="input_date" type="date" />
+ <input id="input_month" type="month" />
+ <input id="input_week" type="week" />
+ <input id="input_time" type="time" />
+
+ <script>
+ "use strict";
+
+ function testValueAsDateGetter(type, element, cases) {
+ for (const [actualValue, expectedValueAsDate] of cases) {
+ test(
+ () => {
+ element.value = actualValue;
+
+ const actualValueAsDate = element.valueAsDate;
+ if (actualValueAsDate instanceof Date) {
+ assert_equals(
+ actualValueAsDate.getTime(),
+ expectedValueAsDate.getTime(),
+ `valueAsDate returns an invalid date (actual: ${actualValueAsDate.toISOString()}, ` +
+ `expected: ${expectedValueAsDate.toISOString()})`
+ );
+ } else {
+ assert_equals(actualValueAsDate, expectedValueAsDate);
+ }
+ },
+ `valueAsDate getter on type ${type} (with value: ${JSON.stringify(actualValue)})`
+ );
+ }
+ }
+
+ function testValueAsDateSetter(type, element, cases) {
+ for (const [valueDateStr, expectedValue] of cases) {
+ test(() => {
+ element.valueAsDate = new Date(valueDateStr);
+ assert_equals(element.value, expectedValue);
+ }, `valueAsDate setter on type ${type} (new Date(${JSON.stringify(valueDateStr)}))`);
+ }
+ }
+
+ const dateInput = document.getElementById("input_date");
+ testValueAsDateGetter("date", dateInput, [
+ ["", null],
+ ["0000-12-10", null],
+ ["2019-00-12", null],
+ ["2019-12-00", null],
+ ["2019-13-10", null],
+ ["2019-02-29", null],
+ ["2019-12-10", new Date("2019-12-10T00:00:00.000Z")],
+ ["2016-02-29", new Date("2016-02-29T00:00:00.000Z")] // Leap year
+ ]);
+ testValueAsDateSetter("date", dateInput, [
+ ["2019-12-10T00:00:00.000Z", "2019-12-10"],
+ ["2016-02-29T00:00:00.000Z", "2016-02-29"] // Leap year
+ ]);
+
+ const monthInput = document.getElementById("input_month");
+ testValueAsDateGetter("month", monthInput, [
+ ["", null],
+ ["0000-12", null],
+ ["2019-00", null],
+ ["2019-12", new Date("2019-12-01T00:00:00.000Z")]
+ ]);
+ testValueAsDateSetter("month", monthInput, [["2019-12-01T00:00:00.000Z", "2019-12"]]);
+
+ const weekInput = document.getElementById("input_week");
+ testValueAsDateGetter("week", weekInput, [
+ ["", null],
+ ["0000-W50", null],
+ ["2019-W00", null],
+ ["2019-W60", null],
+ ["2019-W50", new Date("2019-12-09T00:00:00.000Z")]
+ ]);
+ testValueAsDateSetter("week", weekInput, [["2019-12-09T00:00:00.000Z", "2019-W50"]]);
+
+ const timeInput = document.getElementById("input_time");
+ testValueAsDateGetter("time", timeInput, [
+ ["", null],
+ ["24:00", null],
+ ["00:60", null],
+ ["00:00", new Date("1970-01-01T00:00:00.000Z")],
+ ["12:00", new Date("1970-01-01T12:00:00.000Z")],
+ ["23:59", new Date("1970-01-01T23:59:00.000Z")]
+ ]);
+ testValueAsDateSetter("time", timeInput, [
+ ["1970-01-01T00:00:00.000Z", "00:00"],
+ ["1970-01-01T12:00:00.000Z", "12:00"],
+ ["1970-01-01T23:59:00.000Z", "23:59"]
+ ]);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasnumber-invalidstateerr.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasnumber-invalidstateerr.html
new file mode 100644
index 0000000000..a3187ff3fb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasnumber-invalidstateerr.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>input_valueAsNumber_INVALID_STATE_ERR</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ name="input_form">
+ <p><input type='checkbox' id='input_checkbox'></p>
+ </form>
+
+ <script>
+
+ var input_checkbox = document.getElementById("input_checkbox");
+ try {
+ input_checkbox.valueAsNumber = 5;
+ }
+ catch (e) {
+ test(function() {
+ assert_equals(e.code, e["INVALID_STATE_ERR"], "INVALID_STATE_ERR error is not raised.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasnumber-stepping.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasnumber-stepping.html
new file mode 100644
index 0000000000..c93c25b23f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasnumber-stepping.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>valueAsNumber stepping</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>input_valueAsNumber_stepping</h3>
+ <!-- This test verifies that valueAsNumber reads and writes number values,
+ that those values step by the correct default step, and that the values
+ represent the correct milliseconds/months.
+ -->
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ name="input_form">
+ <p><input type='date' id='input_date'></p>
+ <p><input type='time' id='input_time'></p>
+ <p><input type='week' id='input_week'></p>
+ <p><input type='month' id='input_month'></p>
+ <p><input type='datetime-local' id='input_datetime-local'></p>
+ <p><input type='range' id='input_range'></p>
+ <p><input type='number' id='input_number'></p>
+ </form>
+
+ <script>
+ function test_stepping(inputType, stringValue, steppedString, baseNumber, stepAmount) {
+ test(function() {
+ // put number in
+ input = document.getElementById("input_" + inputType);
+ input.valueAsNumber = baseNumber
+
+ // get string out
+ // startsWith is here to allow for optional seconds and milliseconds.
+ // the replace("T", " ") fallback is for https://github.com/web-platform-tests/wpt/issues/20994
+ var sanitizedStr = input.value;
+ assert_true(sanitizedStr.startsWith(stringValue) || sanitizedStr.startsWith(stringValue.replace("T", " ")),
+ "The input value [" + sanitizedStr + "] must resemble [" + stringValue + "]");
+
+ // get number out
+ var sanitized = input.valueAsNumber;
+ assert_equals(sanitized, baseNumber, "The input valueAsNumber must equal the original number.")
+
+ // step up, get new date out
+ input.stepUp()
+ var steppedNumber = input.valueAsNumber;
+ assert_equals(steppedNumber, baseNumber + stepAmount, "Stepping must be by the correct amount")
+
+ // get new string out
+ var steppedStrOut = input.value;
+ assert_true(steppedStrOut.startsWith(steppedString) || steppedStrOut.startsWith(steppedString.replace("T", " ")),
+ "The changed input value [" + steppedStrOut + "] must resemble [" + steppedString + "]");
+
+ // step back down, get first date out again
+ input.stepDown()
+ var backDown = input.valueAsNumber;
+ assert_equals(backDown, baseNumber, "Stepping back down must return the number to its original value");
+
+ }, inputType + " should step correctly");
+ }
+
+ var millis_per_day = 24 * 60 * 60 * 1000;
+
+ // jan 1 midnight, step 1 day to jan 2
+ test_stepping("date", "1970-01-01", "1970-01-02", 0, millis_per_day);
+
+ // jan 1 midnight, step 1 minute to 00:01:00
+ test_stepping("time", "00:00", "00:01", 0, 60 * 1000);
+
+ // jan 1 midnight, step 1 month (not counting by milliseconds) to feb 1
+ test_stepping("month", "1970-01", "1970-02", 0, 1);
+
+ // monday jan 5 1970 midnight, step 7 days to jan 12
+ // (this has to start on a monday for stepping up and down to return)
+ test_stepping("week", "1970-W02", "1970-W03", 4 * millis_per_day, 7 * millis_per_day);
+
+ // jan 1 midnight, step 1 minute to 00:01:00
+ test_stepping("datetime-local", "1970-01-01T00:00", "1970-01-01T00:01", 0, 60 * 1000);
+
+ // numbers, for which the default step is 1
+ test_stepping("range", "22", "23", 22, 1);
+ test_stepping("number", "24", "25", 24, 1);
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasnumber.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasnumber.html
new file mode 100644
index 0000000000..1af75eafa3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-valueasnumber.html
@@ -0,0 +1,151 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<html>
+ <head>
+ <title>HTMLInputElement valueAsNumber</title>
+ <link rel="author" title="pmdartus" href="mailto:dartus.pierremarie@gmail.com">
+ <link rel=help href="https://html.spec.whatwg.org/#dom-input-valueasnumber">
+
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <h3>input_valueAsNumber</h3>
+ <hr>
+ <div id="log"></div>
+
+ <input id="input_date" type="date" />
+ <input id="input_month" type="month" />
+ <input id="input_week" type="week" />
+ <input id="input_time" type="time" />
+ <input id="input_datetime-local" type="datetime-local" />
+ <input id="input_number" type="number" />
+ <input id="input_range" type="range" min="0" max="100" />
+
+ <script>
+ "use strict";
+
+ function testValueAsNumberGetter(type, element, cases) {
+ for (const [value, expectedValueAsNumber] of cases) {
+ test(
+ () => {
+ element.value = value;
+ assert_equals(element.valueAsNumber, expectedValueAsNumber);
+ },
+ `valueAsNumber getter on type ${type} (actual value: ${value}, ` +
+ `expected valueAsNumber: ${expectedValueAsNumber})`
+ );
+ }
+ }
+
+ function testValueAsNumberSetter(type, element, cases) {
+ for (const [valueAsNumber, expectedValue] of cases) {
+ test(
+ () => {
+ element.valueAsNumber = valueAsNumber;
+ assert_equals(element.value, expectedValue);
+ },
+ `valueAsNumber setter on type ${type} (actual valueAsNumber: ${valueAsNumber}, ` +
+ `expected value: ${expectedValue})`
+ );
+ }
+ }
+
+ const dateInput = document.getElementById("input_date");
+ testValueAsNumberGetter("date", dateInput, [
+ ["", NaN],
+ ["0000-12-10", NaN],
+ ["2019-00-12", NaN],
+ ["2019-12-00", NaN],
+ ["2019-13-10", NaN],
+ ["2019-02-29", NaN],
+ ["2019-12-10", 1575936000000],
+ ["2016-02-29", 1456704000000] // Leap year
+ ]);
+ testValueAsNumberSetter("date", dateInput, [
+ [0, "1970-01-01"],
+ [1575936000000, "2019-12-10"],
+ [1456704000000, "2016-02-29"] // Leap year
+ ]);
+
+ const monthInput = document.getElementById("input_month");
+ testValueAsNumberGetter("month", monthInput, [
+ ["", NaN],
+ ["0000-12", NaN],
+ ["2019-00", NaN],
+ ["2019-12", 599]
+ ]);
+ testValueAsNumberSetter("month", monthInput, [[599, "2019-12"]]);
+
+ const weekInput = document.getElementById("input_week");
+ testValueAsNumberGetter("week", weekInput, [
+ ["", NaN],
+ ["0000-W50", NaN],
+ ["2019-W00", NaN],
+ ["2019-W60", NaN],
+ ["2019-W50", 1575849600000]
+ ]);
+ testValueAsNumberSetter("week", weekInput, [
+ [0, "1970-W01"],
+ [1575849600000, "2019-W50"]
+ ]);
+
+ const timeInput = document.getElementById("input_time");
+ testValueAsNumberGetter("time", timeInput, [
+ ["", NaN],
+ ["24:00", NaN],
+ ["00:60", NaN],
+ ["00:00", 0],
+ ["12:00", 12 * 3600 * 1000],
+ ["23:59", ((23 * 3600) + (59 * 60)) * 1000]
+ ]);
+ testValueAsNumberSetter("time", timeInput, [
+ [0, "00:00"],
+ [12 * 3600 * 1000, "12:00"],
+ [((23 * 3600) + (59 * 60)) * 1000, "23:59"]
+ ]);
+
+ const dateTimeLocalInput = document.getElementById("input_datetime-local");
+ testValueAsNumberGetter("datetime-local", dateTimeLocalInput, [
+ ["", NaN],
+ ["2019-12-10T00:00", 1575936000000],
+ ["2019-12-10T12:00", 1575979200000]
+ ]);
+ testValueAsNumberSetter("datetime-local", dateTimeLocalInput, [
+ [1575936000000, "2019-12-10T00:00"],
+ [1575979200000, "2019-12-10T12:00"]
+ ]);
+
+ const numberInput = document.getElementById("input_number");
+ testValueAsNumberGetter("number", numberInput, [
+ ["", NaN],
+ ["123", 123],
+ ["123.456", 123.456],
+ ["1e3", 1000],
+ ["1e", NaN],
+ ["-123", -123]
+ ]);
+ testValueAsNumberSetter("number", numberInput, [
+ [123, "123"],
+ [123.456, "123.456"],
+ [1e3, "1000"],
+ [-123, "-123"]
+ ]);
+
+ const rangeInput = document.getElementById("input_range");
+ testValueAsNumberGetter("range", rangeInput, [
+ ["", 50],
+ ["0", 0],
+ ["50", 50],
+ ["100", 100],
+ ["-10", 0], // Realign to the min
+ ["110", 100] // Realign to the max
+ ]);
+ testValueAsNumberSetter("range", rangeInput, [
+ [0, "0"],
+ [50, "50"],
+ [100, "100"]
+ ]);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-whitespace.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-whitespace.html
new file mode 100644
index 0000000000..8c3c20e877
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-whitespace.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+ <head>
+ <title>Chrome whitespace bug</title>
+ <link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+ <link rel="help" href="https://crbug.com/1309014">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <style>
+ [data-foo] { color: red; }
+ div input { color: inherit; }
+ </style>
+ </head>
+ <body>
+ <div id="container" data-foo="foo"><input id="input1"></input></div>
+ <script>
+ async_test(t => {
+ let container = document.getElementById('container');
+ let input = document.getElementById('input1');
+ input.onkeypress = function(e) {
+ container.removeAttribute('data-foo');
+ input.style.display = 'block';
+ };
+ test_driver.send_keys(input, "a b")
+ .then(t.step_func(() => {
+ assert_equals(input.value, "a b");
+ t.done();
+ }));
+ }, "whitespace should not be eaten after parent style recalculation");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-width.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-width.html
new file mode 100644
index 0000000000..5278ff77e1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-width.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>input_width</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ name="input_form">
+ <p><input type='image' id='input_text'></p>
+ </form>
+
+ <script>
+
+ var input_text = document.getElementById("input_text");
+ input_text.width = 30;
+
+ if (typeof(input_text.width) == "number") {
+ test(function() {
+ assert_equals(input_text.width, 30, "width attribute is not correct.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("width attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-willvalidate.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-willvalidate.html
new file mode 100644
index 0000000000..e4bcf2e11e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-willvalidate.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Forms</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p>
+ <h3>input_willValidate</h3>
+ </p>
+
+ <hr>
+
+ <div id="log"></div>
+
+ <form method="post"
+ enctype="application/x-www-form-urlencoded"
+ action=""
+ id="input_form">
+ <p><input type='hidden' id='input_text'></p>
+ </form>
+ <script>
+
+ var input = document.getElementById("input_text");
+
+ if (typeof(input.willValidate) == "boolean") {
+ test(function() {
+ assert_equals(input.willValidate, false, "willValidate attribute is not correct.");
+ });
+ } else {
+ test(function() {
+ assert_unreached("willValidate attribute is not exist.");
+ });
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/invalid-datalist-options-crash.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/invalid-datalist-options-crash.html
new file mode 100644
index 0000000000..7cdd55196c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/invalid-datalist-options-crash.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-input-element">
+<input max="0" list="ticks" type="range">
+<datalist id="ticks">
+ <option value="0"></option>
+</datalist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/large-step-crash.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/large-step-crash.html
new file mode 100644
index 0000000000..6c7d577546
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/large-step-crash.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1310229">
+<script>
+const input = document.createElement('input');
+input.type = 'range';
+input.max = "06146014076123948948236985915694585937453938739248525313667193356954648912174625325457686181245605159230507050382951965923880139416566171456307667108838599671206701390275757535304375074544995161254818024615";
+input.step = "55244276720723476767813103100759083382064508394993167470137";
+input.stepUp();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/maxlength-manual.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/maxlength-manual.html
new file mode 100644
index 0000000000..fdf6c26441
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/maxlength-manual.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>input max length</title>
+ <link rel="author" title="Sam Gibson" href="mailto:sam@ifdown.net">
+ <link rel=help href="https://html.spec.whatwg.org/multipage/forms.html#the-maxlength-and-minlength-attributes">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+
+ <body>
+ <div id="log"></div>
+ <p>Type a letter anywhere into the input field (do not select any text, or otherwise manipulate the input)</p>
+ <input type=text maxlength=4 id=only-four value="inpu"></input>
+
+ <script>
+ var input;
+ setup(function() {
+ input = document.getElementById('only-four');
+ }, {explicit_done: true, explicit_timeout: true});
+
+
+ on_event(input, 'keyup', function(event) {
+ if ((event.keyCode >= 65 && event.keyCode <= 90) ||
+ (event.keyCode >= 97 && event.keyCode <= 122)) {
+ test(function() {
+ assert_equals(input.value, "inpu");
+ }, 'input content should limit to maxlength')
+
+ done();
+ }
+ });
+ </script>
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/maxlength-number.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/maxlength-number.html
new file mode 100644
index 0000000000..1e1d9f694c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/maxlength-number.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>input type=number maxlength</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<input type="number" maxlength="1">
+
+<script>
+ async_test(t => {
+ let elem = document.getElementsByTagName("input")[0];
+ test_driver.send_keys(elem, "1234")
+ .then(t.step_func(() => {
+ assert_equals(elem.value, "1234");
+ t.done();
+ }));
+ }, "maxlength doesn't apply to input type=number");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/maxlength.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/maxlength.html
new file mode 100644
index 0000000000..da5d18d00a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/maxlength.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>input max length</title>
+ <link rel="author" title="Sam Gibson" href="mailto:sam@ifdown.net">
+ <link rel=help href="https://html.spec.whatwg.org/multipage/forms.html#the-maxlength-and-minlength-attributes">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+
+ <body>
+ <h1>Text input element</h1>
+
+ <div style="display: none">
+ <input id="none" />
+ <input id="negative" type="-5" />
+ <input id="non-numeric" type="not-a-number" />
+ <input id="assign-negative" />
+ <input id="assign-non-numeric" />
+ </div>
+
+ <div id="log"></div>
+
+ <script type="text/javascript">
+ test(
+ function() {
+ assert_equals(document.getElementById("none").maxLength, -1);
+ }, "Unset maxlength is -1");
+
+ test(
+ function() {
+ assert_equals(document.getElementById("negative").maxLength, -1);
+ }, "Negative maxlength is always -1");
+
+ test(
+ function() {
+ assert_equals(document.getElementById("non-numeric").maxLength, -1);
+ }, "Non-numeric maxlength is -1");
+
+ test(
+ function() {
+ assert_throws_dom("INDEX_SIZE_ERR", function() {
+ document.getElementById("assign-negative").maxLength = -5;
+ });
+ }, "Assigning negative integer throws IndexSizeError");
+
+ test(
+ function() {
+ document.getElementById("assign-non-numeric").maxLength = "not-a-number";
+ assert_equals(document.getElementById("assign-non-numeric").maxLength, 0);
+ }, "Assigning non-numeric to maxlength sets maxlength to 0");
+ </script>
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/minlength.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/minlength.html
new file mode 100644
index 0000000000..6748e30eaf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/minlength.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>input min length</title>
+ <link rel="author" title="Taryn Hill" href="mailto:Phrohdoh@gmail.com">
+ <link rel=help href="https://html.spec.whatwg.org/multipage/forms.html#the-minlength-and-minlength-attributes">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+
+ <body>
+ <h1>Text input element</h1>
+
+ <div style="display: none">
+ <input id="none" />
+ <input id="negative" minlength=-5 />
+ <input id="non-numeric" minlength="not-a-number" />
+ <input id="assign-negative" />
+ <input id="assign-non-numeric" />
+ </div>
+
+ <div id="log"></div>
+
+ <script type="text/javascript">
+ test(
+ function() {
+ assert_equals(document.getElementById("none").minLength, -1);
+ }, "Unset minlength is -1");
+
+ test(
+ function() {
+ assert_equals(document.getElementById("negative").minLength, -1);
+ }, "Negative minlength is always -1");
+
+ test(
+ function() {
+ assert_equals(document.getElementById("non-numeric").minLength, -1);
+ }, "Non-numeric minlength is -1");
+
+ test(
+ function() {
+ assert_throws_dom("INDEX_SIZE_ERR", function() {
+ document.getElementById("assign-negative").minLength = -5;
+ });
+ }, "Assigning negative integer throws IndexSizeError");
+
+ test(
+ function() {
+ document.getElementById("assign-non-numeric").minLength = "not-a-number";
+ assert_equals(document.getElementById("assign-non-numeric").minLength, 0);
+ }, "Assigning non-numeric to minlength sets minlength to 0");
+ </script>
+ </body>
+</html>
+
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/month.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/month.html
new file mode 100644
index 0000000000..99be9bca67
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/month.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Inputs Month</title>
+ <link rel="author" title="Morishita Hiromitsu" href="mailto:hero@asterisk-works.jp">
+ <link rel="author" title="kaseijin" href="mailto:pcmkas@gmail.com">
+ <link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#months">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#month-state-(type=month)">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <h1>Inputs Month</h1>
+ <div style="display: none">
+ <input id="valid_value_1" type="month" value="20133-12" />
+ <input id="valid_value_2" type="month" value="2013-12" />
+ <input id="valid_value_3" type="month" value="0003-01" />
+ <input id="valid" type="month" value="2011-11" min="2011-01" max="2011-12" />
+ <input id="invalid_value" type="month" value="invalid-month" min="2011-01" max="2011-12"/>
+ <input id="value_can_be_empty_string" type="month" value="2013-06" />
+ <input id="invalid_value_with_two_digits_year" type="month" value="13-06" />
+ <input id="invalid_value_is_set" type="month" />
+ <input id="step_attribute_is_invalid_value" type="month" value="2013-06" step="invalid_step_value" />
+ <input id="invalid_month_too_high" type="month" value="2013-13" />
+ <input id="invalid_month_too_low" type="month" value="2013-00" />
+ <input id="invalid_year_all_zero" type="month" value="0000-10" />
+ <input id="invalid_month_with_one_number" type="month" value="2013-1" />
+ <input id="invalid_month_non_numerical" type="month" value="2013-abc" />
+ <input id="invalid_date_additional_tuples" type="month" value="2013-11-1-1" />
+ </div>
+
+ <div id="log"></div>
+
+ <script>
+ test(function() {
+ assert_equals(document.getElementById("valid_value_1").value, "20133-12")
+ }, "year can be more than four digits");
+
+ test(function() {
+ assert_equals(document.getElementById("valid_value_2").value, "2013-12")
+ }, "valid value test");
+
+ test(function() {
+ assert_equals(document.getElementById("valid_value_3").value, "0003-01")
+ }, "year can contain prefixes of zero, as long as there are at least four digits");
+
+ test(function() {
+ assert_equals(document.getElementById("valid").type, "month")
+ }, "month type support on input element");
+
+ test(function() {
+ assert_equals(document.getElementById("invalid_value").value, "")
+ }, "User agents must not allow the user to set the value to a non-empty string that is not a valid month string.");
+
+ test(function() {
+ document.getElementById("value_can_be_empty_string").value = "";
+ assert_equals(document.getElementById("value_can_be_empty_string").value, "")
+ }, "Month value can be empty string.");
+
+ test(function() {
+ assert_equals(document.getElementById("invalid_value_with_two_digits_year").value, "")
+ }, "When value attribute has two digits year value, the value,which is invalid, must return empty string.");
+
+ test(function() {
+ document.getElementById("invalid_value_is_set").value = "invalid value";
+ assert_equals(document.getElementById("invalid_value_is_set").value, "")
+ }, "When value is set with invalid value, the value must return empty string.");
+
+ test(function() {
+ document.getElementById("step_attribute_is_invalid_value").stepUp();
+ assert_equals(document.getElementById("step_attribute_is_invalid_value").value, "2013-07")
+ }, "When step attribute is given invalid value, it must ignore the invalid value and use defaul value instead.");
+
+ test(function() {
+ assert_equals(document.getElementById("invalid_month_too_high").value, "");
+ }, "Month should be <= 13: If the value of the element is not a valid month string, then set it to the empty string instead.");
+
+ test(function() {
+ assert_equals(document.getElementById("invalid_month_too_low").value, "");
+ }, "Month should be > 0: If the value of the element is not a valid month string, then set it to the empty string instead.>");
+
+ test(function() {
+ assert_equals(document.getElementById("invalid_year_all_zero").value, "");
+ }, "Year should be > 0: If the value of the element is not a valid year string, then set it to the empty string instead.>");
+
+ test(function() {
+ assert_equals(document.getElementById("invalid_month_with_one_number").value, "");
+ }, "Month should be two digits: If the value of the element is not a valid month string, then set it to the empty string instead.>");
+
+ test(function() {
+ assert_equals(document.getElementById("invalid_month_non_numerical").value, "");
+ }, "Month should be two digits not characters: If the value of the element is not a valid month string, then set it to the empty string instead.>");
+
+ test(function() {
+ assert_equals(document.getElementById("invalid_date_additional_tuples").value, "");
+ }, "Value should be two parts: If the value of the element is not a valid month string, then set it to the empty string instead.>");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder-cr.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder-cr.html
new file mode 100644
index 0000000000..06f07cbbd8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder-cr.html
@@ -0,0 +1 @@
+<!doctype html> <html class="reftest-wait"> <meta charset="utf-8"> <title>input multiline placeholder (CR)</title> <link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#the-placeholder-attribute"> <meta name="assert" content="input element's placeholder strips newlines (CR)"> <link rel="match" href="multiline-placeholder-ref.html"> <input placeholder="this is a multiline placeholder"> <input placeholder="this is&#xd;a multiline&#xd;&#xd;placeholder"> <input id="dynamic"> <script> document.querySelector("#dynamic") .setAttribute("placeholder", "this is\ra multiline\r\rplaceholder"); document.documentElement.classList.remove("reftest-wait"); </script> </html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder-crlf.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder-crlf.html
new file mode 100644
index 0000000000..b4336e24d2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder-crlf.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>input multiline placeholder (CRLF)</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#the-placeholder-attribute">
+<meta name="assert" content="input element's placeholder strips newlines (CRLF)">
+<link rel="match" href="multiline-placeholder-ref.html">
+<input placeholder="this is
+a multiline
+
+placeholder">
+<input placeholder="this is&#xd;&#xa;a multiline&#xd;&#xa;&#xd;&#xa;placeholder">
+<input id="dynamic">
+<script>
+ document.querySelector("#dynamic")
+ .setAttribute("placeholder", "this is\r\na multiline\r\n\r\nplaceholder");
+ document.documentElement.classList.remove("reftest-wait");
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder-ref.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder-ref.html
new file mode 100644
index 0000000000..2812f86e1e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder-ref.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<meta charset=utf-8>
+<input placeholder="this isa multilineplaceholder">
+<input placeholder="this isa multilineplaceholder">
+<input placeholder="this isa multilineplaceholder">
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder.html
new file mode 100644
index 0000000000..4d2ec43c3f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/multiline-placeholder.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>input multiline placeholder</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#the-placeholder-attribute">
+<meta name="assert" content="input element's placeholder strips newlines">
+<link rel="match" href="multiline-placeholder-ref.html">
+<input placeholder="this is
+a multiline
+
+placeholder">
+<input placeholder="this is&#xa;a multiline&#xa;&#xa;placeholder">
+<input id="dynamic">
+<script>
+ document.querySelector("#dynamic")
+ .setAttribute("placeholder", "this is\na multiline\n\nplaceholder");
+ document.documentElement.classList.remove("reftest-wait");
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/number-disabled.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/number-disabled.html
new file mode 100644
index 0000000000..11cb82fdda
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/number-disabled.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>disabled works properly for number inputs</title>
+<link rel="help" href="https://html.spec.whatwg.org/#enabling-and-disabling-form-controls:-the-disabled-attribute">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1461706">
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<input type="number" disabled>
+<input type="number" disabled style="-moz-appearance: textfield; -webkit-appearance: textfield">
+<script>
+ test(function() {
+ for (const element of Array.from(document.querySelectorAll('input'))) {
+ element.focus();
+ assert_true(element.disabled);
+ assert_equals(document.activeElement, document.body);
+ }
+ }, "disabled works on number input regardless of appearance");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/number.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/number.html
new file mode 100644
index 0000000000..7d93f20898
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/number.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Form input type=number</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#number-state-(type=number)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ var numbers = [
+ {value: "", expected: "", testname: "empty value"},
+ {value: "11", expected: "11", testname: "value = 11"},
+ {value: "11.12", expected: "11.12", testname: "value = 11.12"},
+ {value: "-11111", expected: "-11111", testname: "value = -11111"},
+ {value: "-11111.123", expected: "-11111.123", testname: "value = -11111.123"},
+ {value: "1e2", expected: "1e2", testname: "value = 1e2"},
+ {value: "1E2", expected: "1E2", testname: "value = 1E2"},
+ {value: "1e+2", expected: "1e+2", testname: "value = 1e+2"},
+ {value: "1e-2", expected: "1e-2", testname: "value = 1e-2"},
+ {value: "1d+2", expected: "", testname: "value is not a valid floating-point number: 1d+2"},
+ {value: "foobar", expected: "", testname: "value not a valid floating-point number: random string"},
+ {value: "11", attributes: { min: "10" }, expected: "11", testname: "Value >= min attribute"},
+ {value: "9", attributes: { min: "10" }, expected: "9", testname: "Value < min attribute"},
+ {value: "19", attributes: { max: "20" }, expected: "19", testname: "Value <= max attribute"},
+ {value: "21", attributes: { max: "20" }, expected: "21", testname: "Value > max attribute"},
+ {value: ".1", expected: ".1", testname: "value with a leading '.'"},
+ {value: "1.", expected: "", testname: "value ending with '.'"},
+ {value: "-0", expected: "-0", testname: "value = -0"},
+ {value: "Infinity", expected: "", testname: " value = Infinity"},
+ {value: "-Infinity", expected: "", testname: "value = -Infinity"},
+ {value: "NaN", expected: "", testname: "value = NaN"},
+ {value: "9007199254740993", expected: "9007199254740993", testname: "value = 2^53+1"},
+ {value: "2e308", expected: "", testname: "value >= Number.MAX_VALUE"},
+ {value: "1e", expected: "", testname: "value = 1e"},
+ {value: "+1", expected: "", testname: "value = +1"},
+ {value: "+", expected: "", testname: "value = '+'"},
+ {value: "-", expected: "", testname: "value = '-'"},
+ {value: "\t1", expected: "", testname: "value with a leading tab"},
+ {value: "\n1", expected: "", testname: "value with a leading newline"},
+ {value: "\f1", expected: "", testname: "value with a leading form feed"},
+ {value: "\r1", expected: "", testname: "value with a leading carriage return"},
+ {value: " 1", expected: "", testname: "value with a leading space"},
+ {value: "1trailing junk", expected: "", testname: "value = 1trailing junk"}
+ ];
+ for (var i = 0; i < numbers.length; i++) {
+ var w = numbers[i];
+ test(function() {
+ var input = document.createElement("input");
+ input.type = "number";
+ input.value = w.value;
+ for(var attr in w.attributes) {
+ input[attr] = w.attributes[attr];
+ }
+ assert_equals(input.value, w.expected);
+ }, w.testname);
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/password-delete-space.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/password-delete-space.html
new file mode 100644
index 0000000000..78b3c966b8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/password-delete-space.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Backspace with trailing white space in password field</title>
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1400844">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#password-state-%28type=password%29">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<input id="target" type="password" value=" ">
+
+<script>
+promise_test(async () => {
+ target.focus();
+ target.selectionStart = 2;
+ await test_driver.send_keys(target, '\uE003');
+ assert_equals(target.value, " ");
+}, "Backspace with trailing white space in password field");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/password.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/password.html
new file mode 100644
index 0000000000..aac54aa1c7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/password.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Password input element</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#password-state-%28type=password%29">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div style="display: none">
+<input id="password" type="password" />
+<input id=password2 type=password value="password">
+<input id="password_with_value" type="password" value="foobar" />
+</div>
+<script type="text/javascript">
+ setup(function() {
+ window.password = document.getElementById("password");
+ });
+
+ test(function() {
+ assert_equals(password.value, "");
+ assert_equals(document.getElementById("password_with_value").value, "foobar");
+ }, "Value returns the current value for password");
+
+ test(function() {
+ password.value = "A";
+ assert_equals(password.value, "A");
+ assert_equals(password.getAttribute("value"), null);
+ password.value = "B";
+ assert_equals(password.value, "B");
+ assert_equals(password.getAttribute("value"), null);
+ }, "Setting value changes the current value for password, but not the value content attribute");
+
+ test(function() {
+ // Any LF (\n) must be stripped.
+ password.value = "\nAB";
+ assert_equals(password.value, "AB");
+ password.value = "A\nB";
+ assert_equals(password.value, "AB");
+ password.value = "AB\n";
+ assert_equals(password.value, "AB");
+
+ // Any CR (\r) must be stripped.
+ password.value = "\rAB";
+ assert_equals(password.value, "AB");
+ password.value = "A\rB";
+ assert_equals(password.value, "AB");
+ password.value = "AB\r";
+ assert_equals(password.value, "AB");
+
+ // Any combinations of LF CR must be stripped.
+ password.value = "\r\nAB";
+ assert_equals(password.value, "AB");
+ password.value = "A\r\nB";
+ assert_equals(password.value, "AB");
+ password.value = "AB\r\n";
+ assert_equals(password.value, "AB");
+ password.value = "\r\nA\n\rB\r\n";
+ assert_equals(password.value, "AB");
+ }, "Value sanitization algorithm should strip line breaks for password");
+
+ var pass = document.getElementById('password2');
+
+ test(function(){
+ assert_equals(pass.value, "password");
+ pass.value = " pass word ";
+ assert_equals(pass.value, " pass word ");
+ }, "sanitization algorithm doesn't strip leading and trailing whitespaces");
+
+ test(function(){
+ pass.value = "pass\u000Aword";
+ assert_equals(pass.value, "password");
+ pass.value = "\u000Apassword\u000A";
+ assert_equals(pass.value, "password");
+ pass.value = "pass\u000Dword";
+ assert_equals(pass.value, "password");
+ pass.value = "\u000Dpassword\u000D";
+ assert_equals(pass.value, "password");
+ }, "sanitization algorithm strips line breaks");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/pattern_attribute.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/pattern_attribute.html
new file mode 100644
index 0000000000..ef01c29727
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/pattern_attribute.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Pattern Attribute</title>
+ <meta name=viewport content="width=device-width, maximum-scale=1.0, user-scalable=no" />
+ <link rel="author" title="Fabrice Clari" href="mailto:f.clari@inno-group.com">
+ <link rel="author" title="Dimitri Bocquet" href="mailto:Dimitri.Bocquet@mosquito-fp7.eu">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-input-pattern">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+
+ <body>
+
+
+ <h1>Pattern Attribute</h1>
+ <div style="display: none">
+ <input pattern="[a-z]{3}" value="abcd" title="three letters max"/>
+ </div>
+
+ <div id="log">
+ </div>
+
+ <script type="text/javascript">
+
+
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].getAttribute("pattern"), "[a-z]{3}")}, "pattern attribute support on input element");
+
+ </script>
+
+ </body>
+
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-double-activate-pseudo.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-double-activate-pseudo.html
new file mode 100644
index 0000000000..287dc7d58e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-double-activate-pseudo.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+
+<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>
+
+<!-- This behavior is not explicitly specified. -->
+
+<input type=radio id=radioinput>
+
+<script>
+ promise_test(async () => {
+ await test_driver.send_keys(radioinput, ' ');
+ await test_driver.send_keys(radioinput, ' ');
+ assert_equals(document.querySelector(':active'), null,
+ `If the radio doesn't have the :active pseudo selector, nothing else should either.`);
+ }, `<input type=radio> shouldn't have the :active pseudo element after pressing the spacebar twice.`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-groupname-case.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-groupname-case.html
new file mode 100644
index 0000000000..3c54aca3e7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-groupname-case.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>radio group name case-sensitive</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#radio-button-group">
+<!-- See also: https://github.com/whatwg/html/issues/1666 -->
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<input id=r1 type="radio" name="sImPlE">
+<input id=r2 type="radio" name="simple">
+<input id=r3 type="radio" name="SIMPLE">
+
+<input id=r4 type="radio" name="paSSfield-killroyß">
+<input id=r5 type="radio" name="passfield-killroyß">
+<input id=r6 type="radio" name="PASSFIELD-KILLROYß">
+<input id=r7 type="radio" name="paſſfield-killroyß">
+<input id=r8 type="radio" name="passfield-&#x212a;illroyß">
+<input id=r9 type="radio" name="paßfield-killroyß">
+<input id=r10 type="radio" name="paẞfield-killroyß">
+<input id=r11 type="radio" name="passfield-killroyẞ">
+<input id=r12 type="radio" name="passfield-killroyß">
+<input id=r13 type="radio" name="passfıeld-killroyß">
+<input id=r14 type="radio" name="passfİeld-killroyß">
+
+<input id=r15 type="radio" name="глупый">
+<input id=r16 type="radio" name="глупы&#x438;&#x306;">
+<input id=r17 type="radio" name="ГЛУПЫЙ">
+<input id=r18 type="radio" name="ГЛУПЫ&#x418;&#x306;">
+
+<input id=r19 type="radio" name="åωk">
+<input id=r20 type="radio" name="ÅΩK">
+<input id=r21 type="radio" name="&#x212b;ωk">
+<input id=r22 type="radio" name="å&#x2126;k">
+<input id=r23 type="radio" name="åω&#x212a;">
+
+<input id=r24 type="radio" name="blah1">
+<input id=r25 type="radio" name="blah&#x2460;">
+<input id=r26 type="radio" name="bl&#x24b6;h1">
+<input id=r27 type="radio" name="bl&#x24d0;h1">
+
+<input id=r28 type="radio" name="t&Eacute;dz5アパートFi">
+<input id=r29 type="radio" name="T&Eacute;DZ5アパートFi">
+<input id=r30 type="radio" name="T&eacute;&#x01F1;&#x2075;アパートFi">
+<input id=r31 type="radio" name="t&Eacute;dz5&#x3300;Fi">
+<input id=r32 type="radio" name="t&Eacute;dz5&#x30A2;&#x30CF;&#x309A;&#x30FC;&#x30C8;Fi">
+<input id=r34 type="radio" name="T&Eacute;DZ⁵アパートFi">
+<input id=r35 type="radio" name="T&Eacute;DZ5アパートfi">
+
+<input id=r36 type="radio" name="ΣΣ">
+<input id=r37 type="radio" name="σς">
+
+<script>
+"use strict";
+const notGroups = {
+ "sImPlE": ["r1" ,"r2", "r3"],
+ "paSSfield-killroyß": ["r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r13", "r14"],
+ "глупый": ["r15", "r16", "r17", "r18"],
+ "åωk": ["r19", "r20", "r21", "r22", "r23"],
+ "blah1": ["r24", "r25", "r26", "r27"],
+ "tÉdz5アパートFi": ["r28", "r29", "r30", "r31", "r32", "r34", "r35"],
+ "ΣΣ": ["r36", "r37"]
+};
+
+for (let notGroupLabel of Object.keys(notGroups)) {
+ test(() => {
+ const ids = notGroups[notGroupLabel];
+ const radios = ids.map(id => document.getElementById(id));
+
+ for (let radio of radios) {
+ radio.checked = true;
+ }
+
+ for (let radio of radios) {
+ assert_true(radio.checked, `${radio.name} must be checked`);
+ }
+ }, `Among names like ${notGroupLabel}, everything must be checkable at the same time`);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-input-cancel.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-input-cancel.html
new file mode 100644
index 0000000000..fc2796b041
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-input-cancel.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<title>Radio input cancel behavior reverts state</title>
+<link rel="author" title="jeffcarp" href="mailto:gcarpenterv@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/#radio-button-state-(type=radio)">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+test(() => {
+ const input = document.createElement("input");
+ input.type = "radio";
+ document.body.appendChild(input);
+ const events = [];
+
+ input.addEventListener("change", () => {
+ events.push("change");
+ });
+ input.addEventListener("click", e => {
+ // cancel click event
+ e.preventDefault();
+ events.push("click");
+ });
+ input.addEventListener("input", () => {
+ events.push("input");
+ });
+
+ assert_false(input.checked);
+
+ input.click();
+
+ assert_false(input.checked);
+
+ // only click event called
+ assert_array_equals(events, ["click"]);
+
+}, "radio input cancel behavior reverts state");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-morphed.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-morphed.html
new file mode 100644
index 0000000000..b7b8658948
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-morphed.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Morphed radio input</title>
+<link rel="author" title="Kagami Sascha Rosylight" href="mailto:krosylight@mozilla.com">
+<link rel="help" href="https://html.spec.whatwg.org/#radio-button-state-(type=radio)">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<input id="radio" type="radio" name="name_7" checked>
+<input id="text" name="name_7" checked>
+<script>
+ "use strict";
+
+ test(() => {
+ text.type = 'radio';
+ assert_false(radio.checked);
+ }, "Setting type attribute must unset checkedness of other elements");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-multiple-selected.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-multiple-selected.html
new file mode 100644
index 0000000000..83d42032a5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-multiple-selected.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Multiple required input radio elements</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<form id='testForm'>
+ <input type=radio name=foo required checked>
+ <input type=radio name=foo required>
+ <input type=submit>
+</form>
+<script>
+ test(function() {
+ assert_true(document.getElementById('testForm').reportValidity());
+ }, "Form should be valid since one of the radio elements is checked");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/radio.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio.html
new file mode 100644
index 0000000000..7f183f8367
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio.html
@@ -0,0 +1,351 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>input type radio</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<input type=radio name=group1 id=radio1>
+<input type=radio name=group1 id=radio2>
+
+<input type=radio name=groüp2 id=radio3>
+<input type=radio name=groüp2 id=radio4>
+
+<input type=radio id=radio5>
+<input type=radio id=radio6 disabled>
+
+<input type=radio name="group5" id=radio71 checked>
+<input type=radio name="group5" id=radio72>
+
+<input type=radio name=group3 id=radio8 checked>
+<input type=radio name=group3 id=radio9>
+<input type=radio name=group4 id=radio10>
+<input type=radio name=group4 id=radio11 checked>
+
+<form id="testform"></form>
+<input type=radio form=testform name=group6 id=radio12 checked>
+<input type=radio form=testform name=group6 id=radio13>
+<input type=radio form=testform name=group6 id=radio14>
+
+<script>
+ var radio1 = document.getElementById('radio1'),
+ radio2 = document.getElementById('radio2'),
+ radio3 = document.getElementById('radio3'),
+ radio4 = document.getElementById('radio4'),
+ radio5 = document.getElementById('radio5'),
+ radio6 = document.getElementById('radio6'),
+ radio71 = document.getElementById('radio71'),
+ radio72 = document.getElementById('radio72'),
+ radio8 = document.getElementById('radio8'),
+ radio9 = document.getElementById('radio9'),
+ radio10 = document.getElementById('radio10'),
+ radio11 = document.getElementById('radio11'),
+ radio12 = document.getElementById('radio12'),
+ radio13 = document.getElementById('radio13'),
+ radio14 = document.getElementById('radio14'),
+ testform = document.getElementById('testform'),
+ t1 = async_test("click on mutable radio fires click event, then input event, then change event"),
+ t3 = async_test("click on non-mutable radio doesn't fire the input event"),
+ t4 = async_test("click on non-mutable radio doesn't fire the change event"),
+ t5 = async_test("canceled activation steps on unchecked radio"),
+ input_fired = false,
+ change_fired = false;
+
+ test(function(){
+ assert_false(radio1.checked);
+ assert_false(radio2.checked);
+ radio1.checked = true;
+ assert_true(radio1.checked);
+ assert_false(radio2.checked);
+ radio2.checked = true;
+ assert_false(radio1.checked);
+ assert_true(radio2.checked);
+ }, "only one control of a radio button group can have its checkedness set to true");
+
+ test(function(){
+ assert_false(radio3.checked);
+ assert_false(radio4.checked);
+ radio3.checked = true;
+ assert_true(radio3.checked);
+ assert_false(radio4.checked);
+ radio4.checked = true;
+ assert_false(radio3.checked);
+ assert_true(radio4.checked);
+ }, "radio inputs with non-ASCII name attributes belong to the same radio button group");
+
+ test(function(){
+ assert_true(radio8.checked);
+ assert_false(radio9.checked);
+ assert_false(radio10.checked);
+ assert_true(radio11.checked);
+ radio9.name="group4";
+ radio9.checked = true;
+ assert_true(radio8.checked);
+ assert_true(radio9.checked);
+ assert_false(radio10.checked);
+ assert_false(radio11.checked);
+ }, "changing the name of a radio input element and setting its checkedness to true makes all the other elements' checkedness in the same radio button group be set to false");
+
+ test(function(){
+ radio12.remove();
+ assert_true(radio12.checked);
+ assert_false(radio13.checked);
+ assert_false(radio14.checked);
+ radio13.checked = true;
+ assert_true(radio13.checked);
+ assert_false(radio14.checked);
+ radio13.removeAttribute("form");
+ radio14.removeAttribute("form");
+ assert_true(radio13.checked);
+ assert_false(radio14.checked);
+ radio14.checked = true;
+ assert_false(radio13.checked);
+ assert_true(radio14.checked);
+ radio13.setAttribute("form", "testform");
+ radio14.setAttribute("form", "testform");
+ radio13.checked = true;
+ assert_true(radio13.checked);
+ assert_false(radio14.checked);
+ testform.remove();
+ assert_true(radio13.checked);
+ assert_false(radio14.checked);
+ }, "moving radio input element out of or into a form should still work as expected");
+
+ radio5.onclick = t1.step_func(function(e) {
+ click_fired = true;
+ assert_false(input_fired, "click event should fire before input event");
+ assert_false(change_fired, "click event should fire before change event");
+ assert_false(e.isTrusted, "click()-initiated click event shouldn't be trusted");
+ });
+
+ radio5.oninput = t1.step_func(function(e) {
+ input_fired = true;
+ assert_true(click_fired, "input event should fire after click event");
+ assert_false(change_fired, "input event should fire before change event");
+ assert_true(e.bubbles, "input event should bubble")
+ assert_true(e.isTrusted, "input event should be trusted");
+ assert_false(e.cancelable, "input event should not be cancelable");
+ });
+
+ radio5.onchange = t1.step_func(function(e) {
+ change_fired = true;
+ assert_true(click_fired, "change event should fire after click event");
+ assert_true(input_fired, "change event should fire after input event");
+ assert_true(e.bubbles, "change event should bubble")
+ assert_true(e.isTrusted, "change event should be trusted");
+ assert_false(e.cancelable, "change event should not be cancelable");
+ });
+
+ radio6.oninput= t3.step_func_done(function(e) {
+ assert_unreached("event input fired");
+ });
+
+ radio6.onchange = t4.step_func_done(function(e) {
+ assert_unreached("event change fired");
+ });
+
+ t1.step(function() {
+ radio5.click();
+ assert_true(input_fired);
+ t1.done();
+ });
+
+ t3.step(function(){
+ radio6.click();
+ t3.done();
+ t4.done();
+ });
+
+ radio72.onclick = t5.step_func_done(function(e){
+ assert_false(radio71.checked, "click on radio should uncheck other radio in same group");
+ assert_true(radio72.checked, "click on radio should check that radio");
+ e.preventDefault();
+ // The cancelation of the click doesn't have an effect until after all the click event handlers have been run.
+ assert_false(radio71.checked, "radio remains unchecked immediately after click event on other radio in same group is canceled");
+ assert_true(radio72.checked, "clicked radio remains checked immediately after click event is canceled");
+ });
+
+ t5.step(function(){
+ assert_true(radio71.checked, "initially checked radio should be checked");
+ assert_false(radio72.checked, "other radios in same group as initially-checked radio should be unchecked");
+ radio72.click();
+ // Now that the click event has been fully dispatched, its cancelation has taken effect.
+ assert_true(radio71.checked, "canceled click event on radio should leave the previously-checked radio checked");
+ assert_false(radio72.checked, "canceled click event on previously-unchecked radio should leave that radio unchecked");
+ });
+
+ test(() => {
+ const container = document.createElement('div');
+ container.innerHTML =
+ '<input type=radio name=n1><span><input type=radio name=n1 checked></span>' +
+ '<form><input type=radio name=n1 checked></form>';
+ const radios = container.querySelectorAll('input');
+ assert_false(radios[0].checked, 'Sanity check: The first radio should be unchecked');
+ assert_true(radios[1].checked, 'Sanity check: The second radio should be checked');
+ assert_true(radios[2].checked, 'Sanity check: The third radio should be checked');
+
+ radios[0].checked = true;
+ assert_true(radios[0].checked, 'The first radio should be checked after setting checked');
+ assert_false(radios[1].checked, 'The second radio should be unchecked after setting checked');
+ assert_true(radios[2].checked, 'The third radio should be checked after setting checked');
+
+ radios[1].required = true;
+ assert_false(radios[0].validity.valueMissing, 'The first radio should be valid');
+ assert_false(radios[1].validity.valueMissing, 'The second radio should be valid');
+ assert_false(radios[2].validity.valueMissing, 'The third radio should be valid');
+
+ radios[0].remove();
+ assert_false(radios[0].validity.valueMissing, 'The first radio should be valid because of no required');
+ assert_true(radios[1].validity.valueMissing, 'The second radio should be invalid***');
+ assert_false(radios[2].validity.valueMissing, 'The third radio should be valid');
+
+ radios[0].required = true;
+ radios[0].checked = false;
+ assert_true(radios[0].validity.valueMissing, 'The first radio should be invalid because of required');
+ }, 'Radio buttons in an orphan tree should make a group');
+
+ test(() => {
+ const container = document.createElement('div');
+ container.innerHTML =
+ '<form>' +
+ '<input type=radio name=group1 id=radio1 checked>' +
+ '<input type=radio name=group1 id=radio2>' +
+ '</form>' +
+ '<form>' +
+ '<input type=radio name=group1 id=radio3 checked>' +
+ '<input type=radio name=group1 id=radio4>' +
+ '</form>' +
+ '<input type=radio name=group1 id=radio5 checked>' +
+ '<input type=radio name=group1 id=radio6>';
+ const radio1 = container.querySelector('#radio1');
+ const radio2 = container.querySelector('#radio2');
+ const radio3 = container.querySelector('#radio3');
+ const radio4 = container.querySelector('#radio4');
+ const radio5 = container.querySelector('#radio5');
+ const radio6 = container.querySelector('#radio6');
+
+ // initial conditions
+ assert_true(radio1.checked, 'radio1 should be checked');
+ assert_false(radio2.checked, 'radio2 should be unchecked');
+ assert_true(radio3.checked, 'radio3 should be checked');
+ assert_false(radio4.checked, 'radio4 should be unchecked');
+ assert_true(radio5.checked, 'radio5 should be checked');
+ assert_false(radio6.checked, 'radio6 should be unchecked');
+
+ radio2.checked = true;
+ assert_false(radio1.checked, 'radio1 should be unchecked');
+ assert_true(radio2.checked, 'radio2 should be checked');
+ assert_true(radio3.checked, 'radio3 should remain checked');
+ assert_true(radio5.checked, 'radio5 should remain checked');
+
+ radio4.checked = true;
+ assert_false(radio1.checked, 'radio1 should remain unchecked');
+ assert_true(radio2.checked, 'radio2 should remain checked');
+ assert_false(radio3.checked, 'radio3 should be unchecked');
+ assert_true(radio4.checked, 'radio4 should be checked');
+ assert_true(radio5.checked, 'radio5 should remain checked');
+
+ radio6.checked = true;
+ assert_false(radio1.checked, 'radio1 should remain unchecked');
+ assert_true(radio2.checked, 'radio2 should remain checked');
+ assert_false(radio3.checked, 'radio3 should remain unchecked');
+ assert_true(radio4.checked, 'radio4 should remain checked');
+ assert_false(radio5.checked, 'radio5 should be unchecked');
+ assert_true(radio6.checked, 'radio6 should be checked');
+ }, "Radio buttons in different groups (because they have different form owners or no form owner) do not affect each other's checkedness");
+
+ test(() => {
+ const container = document.createElement('div');
+ container.innerHTML =
+ '<form>' +
+ '<input type=radio name=group1 id=radio1 checked>' +
+ '<input type=radio name=group1 id=radio2>' +
+ '<input type=radio name=group1 id=radio3>' +
+ '<input type=radio name=group1 id=radio4>' +
+ '</form>';
+ const radio1 = container.querySelector('#radio1');
+ const radio2 = container.querySelector('#radio2');
+ const radio3 = container.querySelector('#radio3');
+ const radio4 = container.querySelector('#radio4');
+
+ // initial conditions
+ assert_true(radio1.checked, 'radio1 should be checked');
+ assert_false(radio2.checked, 'radio2 should be unchecked');
+ assert_false(radio3.checked, 'radio3 should be unchecked');
+ assert_false(radio4.checked, 'radio4 should be unchecked');
+
+ radio3.remove();
+ radio4.remove();
+ radio3.checked = true;
+ radio4.checked = true;
+ assert_true(radio1.checked, 'radio1 should remain checked');
+ assert_false(radio2.checked, 'radio2 should remain unchecked');
+ assert_true(radio3.checked, 'radio3 should be checked');
+ assert_true(radio4.checked, 'radio4 should be checked');
+ }, "Radio buttons in different groups (because they are not in the same tree) do not affect each other's checkedness");
+
+ test(() => {
+ const container = document.createElement('div');
+ container.innerHTML =
+ '<form>' +
+ '<input type=radio name=group1 id=radio1 checked>' +
+ '<input type=radio name=group1 id=radio2>' +
+ '<input type=radio name=group2 id=radio3 checked>' +
+ '<input type=radio name=group2 id=radio4>' +
+ '<input type=radio name="" id=radio5 checked>' +
+ '<input type=radio name="" id=radio6>' +
+ '<input type=radio id=radio7 checked>' +
+ '<input type=radio id=radio8>' +
+ '</form>';
+ const radio1 = container.querySelector('#radio1');
+ const radio2 = container.querySelector('#radio2');
+ const radio3 = container.querySelector('#radio3');
+ const radio4 = container.querySelector('#radio4');
+ const radio5 = container.querySelector('#radio5');
+ const radio6 = container.querySelector('#radio6');
+ const radio7 = container.querySelector('#radio7');
+ const radio8 = container.querySelector('#radio8');
+
+ // initial conditions
+ assert_true(radio1.checked, 'radio1 should be checked');
+ assert_false(radio2.checked, 'radio2 should be unchecked');
+ assert_true(radio3.checked, 'radio3 should be checked');
+ assert_false(radio4.checked, 'radio4 should be unchecked');
+ assert_true(radio5.checked, 'radio5 should be checked');
+ assert_false(radio6.checked, 'radio6 should be unchecked');
+ assert_true(radio7.checked, 'radio7 should be checked');
+ assert_false(radio8.checked, 'radio8 should be unchecked');
+
+ radio2.checked = true;
+ assert_false(radio1.checked, 'radio1 should be unchecked');
+ assert_true(radio2.checked, 'radio2 should be checked');
+ assert_true(radio3.checked, 'radio3 should remain checked');
+ assert_false(radio4.checked, 'radio4 should remain unchecked');
+ assert_true(radio5.checked, 'radio5 should remain checked');
+ assert_false(radio6.checked, 'radio6 should remain unchecked');
+ assert_true(radio7.checked, 'radio7 should remain checked');
+ assert_false(radio8.checked, 'radio8 should remain unchecked');
+
+ radio6.checked = true;
+ assert_false(radio1.checked, 'radio1 should remain unchecked');
+ assert_true(radio2.checked, 'radio2 should remain checked');
+ assert_true(radio3.checked, 'radio3 should remain checked');
+ assert_false(radio4.checked, 'radio4 should remain unchecked');
+ assert_true(radio5.checked, 'radio5 should remain checked');
+ assert_true(radio6.checked, 'radio6 should be checked');
+ assert_true(radio7.checked, 'radio7 should remain checked');
+
+ radio8.checked = true;
+ assert_false(radio1.checked, 'radio1 should remain unchecked');
+ assert_true(radio2.checked, 'radio2 should remain checked');
+ assert_true(radio3.checked, 'radio3 should remain checked');
+ assert_false(radio4.checked, 'radio4 should remain unchecked');
+ assert_true(radio5.checked, 'radio5 should remain checked');
+ assert_true(radio6.checked, 'radio6 should remain checked');
+ assert_true(radio7.checked, 'radio7 should remain checked');
+ assert_true(radio8.checked, 'radio8 should be checked');
+
+ }, "Radio buttons in different groups (because they have different name attribute values, or no name attribute) do not affect each other's checkedness");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-2.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-2.html
new file mode 100644
index 0000000000..3277dfc07f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-2.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>range input Tests</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<input type="range" id="r00" min="0" max="100" step="20" value="40" style="display:none">
+<input type="range" id="r01" min="0" max="1" step=".1" value=".2" style="display:none">
+<input type="range" id="r02" style="display:none">
+<input type="range" id="r03" style="display:none">
+<input type="range" id="r04" style="display:none">
+
+<script>
+test(function rangeElementTest0() {
+ document.getElementById('r00').value = "";
+ assert_equals(document.getElementById('r00').type, "range");
+ assert_equals(document.getElementById('r00').value, "60");
+}, "range input value set to ''");
+
+test(function rangeElementTest1() {
+ document.getElementById('r01').value = .6;
+ assert_equals(document.getElementById('r01').type, "range");
+ assert_equals(document.getElementById('r01').value, "0.6");
+}, "range input value set to an integer");
+
+test(function rangeElementTest2() {
+ assert_equals(document.getElementById('r02').type, "range");
+ assert_equals(document.getElementById('r02').value, "50");
+}, "range input value equals 50");
+
+test(function rangeElementTest3() {
+ document.getElementById('r03').value = 200;
+ assert_equals(document.getElementById('r03').type, "range");
+ assert_equals(document.getElementById('r03').value, "100");
+}, "range input value equals 100");
+
+test(function rangeElementTest4() {
+ document.getElementById('r04').value = 2.1;
+ assert_equals(document.getElementById('r04').type, "range");
+ assert_equals(document.getElementById('r04').value, "2");
+}, "range input value equals 2");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-intrinsic-size-ref.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-intrinsic-size-ref.html
new file mode 100644
index 0000000000..48beaea3f6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-intrinsic-size-ref.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <meta charset="utf-8">
+ <title>Reference: type=range intrinsic size</title>
+ <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1512066">
+ <style>
+html,body {
+ color:black; background-color:white; font:16px/1 monospace;
+}
+
+.flex {
+ display: inline-flex;
+ width: 0;
+ border: 1px solid;
+ justify-items:start;
+}
+.flex2 {
+ display: inline-flex;
+ border: 1px solid;
+ justify-items:start;
+}
+.grid {
+ display: inline-grid;
+ grid: auto / 0;
+ border: 1px solid;
+ justify-items:start;
+}
+.grid2 {
+ display: inline-grid;
+ border: 1px solid;
+ justify-items:start;
+}
+.ib {
+ display: inline-block;
+ width: 0;
+ border: 1px solid;
+ justify-items:start;
+}
+
+input {
+ width: max-content;
+ min-width: 0;
+}
+input.min {
+ min-width: min-content;
+}
+input.mbp0 {
+ margin-left: 0;
+ margin-right: 0;
+ padding: 0;
+ border: 0;
+}
+ </style>
+</head>
+<body>
+
+<div class="flex"><input type="range" class="min"></div><br>
+<div class="flex"><input type="range" style="width:0"></div><br>
+<div class="flex"><input type="range" class="min"></div><br>
+<div class="flex"><input type="range" class="min"></div><br>
+<div class="flex"><input type="range" class="min"></div><br>
+<br>
+
+<div class="flex2"><input type="range"></div>
+<div class="flex2" style="width:3px"><input type="range" style="width:3px" class="mbp0"></div>
+<div class="flex2" style="width:30px"><input type="range" class="mbp0"></div>
+<div class="flex2"><input type="range"></div>
+<div class="flex2"><input type="range"></div>
+<div class="flex2"><input type="range"></div>
+<div class="flex2"><input type="range"></div>
+<br>
+
+<div class="grid"><input type="range" style="width:0"></div><br>
+<div class="grid"><input type="range" style="width:0"></div><br>
+<div class="grid" style="justify-items:start"><input type="range"></div><br>
+
+<div class="grid2"><input type="range"></div>
+<div class="grid2"><input type="range" style="min-width:0"></div>
+<div class="grid2" style="width:3px"><input type="range" style="width:3px" class="mbp0"></div>
+<div class="flex2" style="width:30px"><input type="range" class="mbp0"></div>
+<div class="flex2" style="width:30px"><input type="range" class="mbp0"></div>
+<div class="grid2" style="justify-items:start"><input type="range"></div>
+
+<br>
+
+<div class="ib"><input type="range"></div><br>
+<div class="ib"><input type="range"></div><br>
+
+<input type="range">
+<input type="range"
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-intrinsic-size.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-intrinsic-size.html
new file mode 100644
index 0000000000..ce37faf89b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-intrinsic-size.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <meta charset="utf-8">
+ <title>Test: type=range intrinsic size</title>
+ <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1512066">
+ <link rel="match" href="range-intrinsic-size-ref.html">
+ <style>
+html,body {
+ color:black; background-color:white; font:16px/1 monospace;
+}
+
+.flex {
+ display: inline-flex;
+ width: 0;
+ border: 1px solid;
+}
+.flex2 {
+ display: inline-flex;
+ border: 1px solid;
+}
+.grid {
+ display: inline-grid;
+ grid: auto / 0;
+ border: 1px solid;
+}
+.grid2 {
+ display: inline-grid;
+ border: 1px solid;
+}
+.ib {
+ display: inline-block;
+ width: 0;
+ border: 1px solid;
+}
+input.mbp0 {
+ margin-left: 0;
+ margin-right: 0;
+ padding: 0;
+ border: 0;
+}
+ </style>
+</head>
+<body>
+
+<div class="flex"><input type="range"></div><br>
+<div class="flex"><input type="range" style="min-width:0"></div><br>
+<div class="flex" style="justify-items:start"><input type="range"></div><br>
+<div class="flex" style="-webkit-box-pack: start"><input type="range"></div><br>
+<div class="flex" style="-webkit-box-pack: start; justify-content: flex-start;"><input type="range"></div><br>
+<br>
+
+<div class="flex2"><input type="range"></div>
+<div class="flex2" style="width:3px"><input type="range" style="min-width:0" class="mbp0"></div>
+<div class="flex2" style="width:30px"><input type="range" style="min-width:0" class="mbp0"></div>
+<div class="flex2"><input type="range" style="min-width:0"></div>
+<div class="flex2" style="justify-items:start"><input type="range"></div>
+<div class="flex2" style="-webkit-box-pack: start"><input type="range"></div>
+<div class="flex2" style="-webkit-box-pack: start; justify-content: flex-start;"><input type="range"></div>
+<br>
+
+<div class="grid"><input type="range"></div><br>
+<div class="grid"><input type="range" style="min-width:0"></div><br>
+<div class="grid" style="justify-items:start"><input type="range"></div><br>
+
+<div class="grid2"><input type="range"></div>
+<div class="grid2"><input type="range" style="min-width:0"></div>
+<div class="grid2" style="width:3px"><input type="range" style="min-width:0" class="mbp0"></div>
+<div class="grid2" style="width:30px"><input type="range" style="min-width:0" class="mbp0"></div>
+<div class="grid2" style="grid:auto/30px"><input type="range" class="mbp0"></div>
+<div class="grid2" style="justify-items:start"><input type="range"></div>
+
+<br>
+
+<div class="ib"><input type="range"></div><br>
+<div class="ib"><input type="range" style="min-width:0"></div><br>
+
+<input type="range" style="width:min-content;">
+<input type="range" style="width:max-content;">
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-added-repaint.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-added-repaint.html
new file mode 100644
index 0000000000..344f36527b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-added-repaint.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html class=reftest-wait>
+<title>The range is repainted if a list ID is added</title>
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1805105">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel=match href=range-tick-marks-05-ref.html>
+<script src=/common/reftest-wait.js></script>
+<input type=range step=3 value=1 min=-5 max=5>
+<datalist id=tickmarks>
+ <option value=4>
+ <option value=-2>
+</datalist>
+<script>
+ requestAnimationFrame(() =>
+ requestAnimationFrame(() => {
+ document.querySelector("input[type=range]").setAttribute("list", "tickmarks");
+ takeScreenshot();
+ }));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-change-repaint.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-change-repaint.html
new file mode 100644
index 0000000000..b5bee03b3b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-change-repaint.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html class=reftest-wait>
+<title>The range is repainted if its list attribute changes</title>
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1805105">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel=match href=range-tick-marks-05-ref.html>
+<script src=/common/reftest-wait.js></script>
+<input type=range step=3 value=1 min=-5 max=5 list=firstlist>
+<datalist id=firstlist>
+ <option value=1></option>
+ <option value=-5></option>
+</datalist>
+<datalist id=secondlist>
+ <option value=-2>
+ <option value=4>
+</datalist>
+<script>
+ requestAnimationFrame(() =>
+ requestAnimationFrame(() => {
+ const range = document.querySelector("input[type=range]");
+ range.setAttribute("list", "secondlist");
+ takeScreenshot();
+ }));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-duplicate-id-repaint.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-duplicate-id-repaint.html
new file mode 100644
index 0000000000..0a2a90b500
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-duplicate-id-repaint.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<html class=reftest-wait>
+<title>The range is repainted if the ID identifies a different list</title>
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1805105">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel=match href=range-tick-marks-05-ref.html>
+<script src=/common/reftest-wait.js></script>
+<input type=range step=3 value=1 min=-5 max=5 list=firstlist>
+<datalist id=firstlist>
+ <option value=1></option>
+ <option value=-5></option>
+</datalist>
+<datalist id=secondlist>
+ <option value=4>
+ <option value=-2>
+</datalist>
+<script>
+ requestAnimationFrame(() =>
+ requestAnimationFrame(() => {
+ const firstList = document.querySelector("datalist#firstlist");
+ const secondList = document.querySelector("datalist#secondlist");
+ secondList.id = "firstlist";
+ firstList.parentNode.insertBefore(secondList, firstList);
+ takeScreenshot();
+ }));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-nonexistent.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-nonexistent.html
new file mode 100644
index 0000000000..72fb19b9c8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-list-nonexistent.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html class=reftest-wait>
+<title>The range is repainted if the ID first identifies no list, then a list takes on that ID</title>
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1805105">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel=match href=range-tick-marks-05-ref.html>
+<script src=/common/reftest-wait.js></script>
+<input type=range step=3 value=1 min=-5 max=5 list=nonexistentlist>
+<datalist>
+ <option value=4>
+ <option value=-2>
+</datalist>
+<script>
+ requestAnimationFrame(() =>
+ requestAnimationFrame(() => {
+ const dataListWithIDOfNonExistentList = document.querySelector("datalist");
+ dataListWithIDOfNonExistentList.id = "nonexistentlist";
+ takeScreenshot();
+ }));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-option-add-repaint.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-option-add-repaint.html
new file mode 100644
index 0000000000..31631b0c59
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-option-add-repaint.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html class=reftest-wait>
+<title>The range is repainted if an option is added to the range's list</title>
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1805105">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel=match href=range-tick-marks-05-ref.html>
+<script src=/common/reftest-wait.js></script>
+<input type=range step=3 value=1 min=-5 max=5 list=tickmarks>
+<datalist id=tickmarks>
+ <option value=4></option>
+</datalist>
+<script>
+ requestAnimationFrame(() =>
+ requestAnimationFrame(() => {
+ const dataList = document.querySelector("datalist");
+ const toAdd = document.createElement("option");
+ toAdd.value = -2;
+ dataList.appendChild(toAdd);
+ takeScreenshot();
+ }));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-option-remove-repaint.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-option-remove-repaint.html
new file mode 100644
index 0000000000..672e371bfb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-option-remove-repaint.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html class=reftest-wait>
+<title>The range is repainted if an option is removed from the range's list</title>
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1805105">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel=match href=range-tick-marks-05-ref.html>
+<script src=/common/reftest-wait.js></script>
+<input type=range step=3 value=1 min=-5 max=5 list=tickmarks>
+<datalist id=tickmarks>
+ <option value=-2></option>
+ <option value=1 id=to-remove></option>
+ <option value=4></option>
+</datalist>
+<script>
+ requestAnimationFrame(() =>
+ requestAnimationFrame(() => {
+ document.querySelector("option#to-remove").remove();
+ takeScreenshot();
+ }));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-option-value-change-repaint.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-option-value-change-repaint.html
new file mode 100644
index 0000000000..7dcace4e47
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-option-value-change-repaint.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html class=reftest-wait>
+<title>The range is repainted if the value of an option in the range's list changes</title>
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1805105">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel=match href=range-tick-marks-05-ref.html>
+<script src=/common/reftest-wait.js></script>
+<input type=range step=3 value=1 min=-5 max=5 list=tickmarks>
+<datalist id=tickmarks>
+ <option value=-2></option>
+ <option value=1 id=to-change></option>
+</datalist>
+<script>
+ requestAnimationFrame(() =>
+ requestAnimationFrame(() => {
+ const toChange = document.querySelector("option#to-change");
+ toChange.value = 4;
+ takeScreenshot();
+ }));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-restore-oninput-onchange-event.https.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-restore-oninput-onchange-event.https.html
new file mode 100644
index 0000000000..f73c5e6f63
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-restore-oninput-onchange-event.https.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/pull/7283">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1266468">
+
+<link rel=author href="mailto:gulukesh@gmail.com">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1131234">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+function runTest(type, testValue) {
+ promise_test(async () => {
+ const w = window.open(`resources/${type}-restore-events.html`);
+ // Unfortunately, navigating |w| doesn't fire load events in this parent
+ // window, so we have to make the child window manually tell this parent
+ // window when it has loaded.
+ await new Promise(resolve => window.loadResolver = resolve);
+ // We can't navigate the child window until after a setTimeout.
+ await new Promise(resolve => step_timeout(resolve, 0));
+
+ assert_not_equals(
+ w.document.querySelector('input').value,
+ testValue,
+ `Test shouldn't start with the new value already in the input.`);
+ w.document.querySelector('input').value = testValue;
+
+ w.location.href = 'resources/loadresolver.html';
+ await new Promise(resolve => window.loadResolver = resolve);
+
+ w.history.back();
+ await new Promise(resolve => window.loadResolver = resolve);
+ // The value doesn't get restored until after a setTimeout.
+ await new Promise(resolve => step_timeout(resolve, 0));
+
+ assert_equals(w.document.querySelector('input').value, testValue,
+ 'The input should have its value restored.');
+
+ assert_false(w.seeninput || false,
+ 'The input event should not have been fired after restoration.');
+ assert_false(w.seenchange || false,
+ 'The change event should not have been fired after restoration.');
+
+ w.close();
+ }, `Verifies that form restoration does not fire input or change events for <input type=${type}>.`);
+}
+
+runTest('range', '8');
+runTest('text', 'foo');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-setattribute-value-ref.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-setattribute-value-ref.html
new file mode 100644
index 0000000000..71a44cdf2f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-setattribute-value-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>range input element setAttribute value appearance</title>
+
+<p>Test passes if the range element below visually has its slider at 2/10 from the left</p>
+
+<input type=range min=0 max=10 value=2></input>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-setattribute-value.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-setattribute-value.html
new file mode 100644
index 0000000000..3a03a5b6fe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-setattribute-value.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#the-input-element">
+<link rel="match" href="range-setattribute-value-ref.html">
+<title>range input element setAttribute value appearance</title>
+
+<p>Test passes if the range element below visually has its slider at 2/10 from the left</p>
+
+<script>
+window.onload = () => {
+
+ const input = document.createElement('input');
+ input.type = 'range';
+ input.min = 0;
+ input.max = 10;
+ document.body.appendChild(input);
+
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ input.setAttribute('value', 2);
+ document.documentElement.classList.remove('reftest-wait');
+ });
+ });
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-01-notref.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-01-notref.html
new file mode 100644
index 0000000000..58192bec8e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-01-notref.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<title>LTR range input with datalist reference</title>
+<input type="range" min="-100" max="100" value="0" step="10" name="power" list="powers">
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-01.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-01.html
new file mode 100644
index 0000000000..225422dc4d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-01.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>LTR range input with datalist</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range)">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=841942">
+<link rel="author" href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel="mismatch" href="range-tick-marks-01-notref.html">
+<input type="range" min="-100" max="100" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="0">
+ <option value="-30">
+ <option value="30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-02-ref.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-02-ref.html
new file mode 100644
index 0000000000..3d5b323470
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-02-ref.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>RTL range input with datalist reference</title>
+<style>
+ input[type=range] {
+ transform: scaleX(-1);
+ }
+</style>
+<input type="range" min="-100" max="100" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="0">
+ <option value="-30">
+ <option value="30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-02.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-02.html
new file mode 100644
index 0000000000..453e650b2f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-02.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>RTL range input with datalist</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range)">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/dom.html#attr-dir-rtl">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=841942">
+<link rel="author" href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel="match" href="range-tick-marks-02-ref.html">
+<input type="range" min="-100" max="100" value="0" step="10" name="power" list="powers" dir="rtl">
+<datalist id="powers">
+ <option value="0">
+ <option value="-30">
+ <option value="30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-03-notref.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-03-notref.html
new file mode 100644
index 0000000000..5c15233a31
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-03-notref.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<title>max and min attributes applied to range input with datalist reference</title>
+<input type="range" min="-100" max="100" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="0">
+ <option value="-30">
+ <option value="30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-03.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-03.html
new file mode 100644
index 0000000000..e067013bdd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-03.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>max and min attributes applied to range input with datalist</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range)">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#the-min-and-max-attributes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=841942">
+<link rel="author" href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel="mismatch" href="range-tick-marks-03-notref.html">
+<input type="range" min="-40" max="40" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="0">
+ <option value="-30">
+ <option value="30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-04-ref.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-04-ref.html
new file mode 100644
index 0000000000..a6afaff7ee
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-04-ref.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>no range tick marks for disabled datalist elements reference</title>
+<input type="range" min="-100" max="100" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="-30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-04.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-04.html
new file mode 100644
index 0000000000..6fb0e930a1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-04.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>no range tick marks for disabled datalist elements</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range)">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#htmldatalistelement">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-disabled">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=841942">
+<link rel="author" href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel="match" href="range-tick-marks-04-ref.html">
+<input type="range" min="-100" max="100" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="0" disabled>
+ <option value="-30">
+ <option value="30" disabled>
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-05-ref.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-05-ref.html
new file mode 100644
index 0000000000..f144af3880
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-05-ref.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>no range tick marks for range tick marks that are step mismatches reference</title>
+<input type=range step=3 value=1 min=-5 max=5 list=degrees>
+<datalist id=degrees>
+ <option value=-2>
+ <option value=4>
+</datalist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-05.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-05.html
new file mode 100644
index 0000000000..a65c7b7946
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range-tick-marks-05.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>no range tick marks for range tick marks that are step mismatches</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range)">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1803303">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel=match href=range-tick-marks-05-ref.html>
+<input type=range step=3 value=1 min=-5 max=5 list=degrees>
+<datalist id=degrees>
+ <option value=-4>
+ <option value=-2>
+ <option value=0>
+ <option value=2>
+ <option value=4>
+</datalist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/range.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/range.html
new file mode 100644
index 0000000000..093c60ba57
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range.html
@@ -0,0 +1,241 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Input Range</title>
+ <meta name=viewport content="width=device-width, maximum-scale=1.0, user-scalable=no" />
+ <link rel="author" title="Fabrice Clari" href="mailto:f.clari@inno-group.com">
+ <link rel="author" title="Dimitri Bocquet" href="mailto:Dimitri.Bocquet@mosquito-fp7.eu">
+ <link rel="author" title="Tomoyuki SHIMIZU" href="mailto:tomoyuki.labs@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-input-element">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-type">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-min">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-max">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#range-state-(type=range)">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-stepup">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-stepdown">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#best-representation-of-the-number-as-a-floating-point-number">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+
+ <body>
+
+
+ <h1>Input Range</h1>
+ <div style="display:none">
+ <input type="range" id="range_basic" min=0 max=5 />
+ <input type="range" id="value_smaller_than_min" min=0 max=5 value=-10 />
+ <input type="range" id="value_larger_than_max" min=0 max=5 value=7 />
+ <input type="range" id="empty_attributes" />
+ <input type="range" id="value_not_specified" min=2 max=6 />
+ <input type="range" id="control_step_mismatch" min=0 max=7 step=2 />
+ <input type="range" id="max_smaller_than_min" min=2 max=-3 />
+ <input type="range" id="default_step_scale_factor_1" min=5 max=12.6 value=6.7 />
+ <input type="range" id="default_step_scale_factor_2" min=5.3 max=12 value=6.7 />
+ <input type="range" id="float_step_scale_factor" min=5.3 max=12 step=0.5 value=6.7 />
+ <input type="range" id="stepup" min=3 max=14 value=6 step=3 />
+ <input type="range" id="stepdown" min=3 max=11 value=9 step=3 />
+ <input type="range" id="stepup_beyond_max" min=3 max=14 value=9 step=3 />
+ <input type="range" id="stepdown_beyond_min" min=3 max=11 value=6 step=3 />
+ <input type="range" id="illegal_min_and_max" min="ab" max="f" />
+ <input type="range" id="illegal_value_and_step" min=0 max=5 value="ppp" step="xyz" />
+ <input type="range" id="should_skip_whitespace" value=" 123"/>
+ <input type="range" id="exponent_value1" value=""/>
+ <input type="range" id="exponent_value2" value=""/>
+ </div>
+
+ <div id="log">
+ </div>
+
+ <script type="text/javascript">
+
+ test(
+ function() {
+ assert_equals(document.getElementById('range_basic').type, "range");
+ },
+ "range type support on input element"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('range_basic').min, "0")
+ },
+ "min attribute support on input element"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('range_basic').max, "5")
+ },
+ "max attribute support on input element"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('illegal_min_and_max').min, "ab")
+ },
+ "Illegal value of min attribute"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('illegal_min_and_max').max, "f")
+ },
+ "Illegal value of max attribute"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('illegal_value_and_step').value, "3")
+ },
+ "Converting an illegal string to the default value"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('illegal_value_and_step').step, "xyz")
+ },
+ "Illegal value of step attribute"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('value_smaller_than_min').value, "0")
+ },
+ "the value is set to min when a smaller value than min attribute is given"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('value_larger_than_max').value, "5")
+ },
+ "the value is set to max when a larger value than max attribute is given"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('empty_attributes').min, "")
+ },
+ "default value of min attribute in input type=range"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('empty_attributes').max, "")
+ },
+ "default value of max attribute in input type=range"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('value_not_specified').value, "4")
+ },
+ "default value when min and max attributes are given (= min plus half the difference between min and max)"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('control_step_mismatch').value, "4")
+ },
+ "default value with step control when both min and max attributes are given"
+ );
+
+ // Chrome would result in different value out of the range between min and max. Why?
+ test(
+ function() {
+ assert_equals(document.getElementById('max_smaller_than_min').value, "2")
+ },
+ "default value when both min and max attributes are given, while min > max"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('default_step_scale_factor_1').value, "7")
+ },
+ "The default step scale factor is 1, unless min attribute has non-integer value"
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('default_step_scale_factor_2').value, "6.3")
+ },
+ "Step scale factor behavior when min attribute has integer value but max attribute is non-integer "
+ );
+
+ test(
+ function() {
+ assert_equals(document.getElementById('float_step_scale_factor').value, "6.8")
+ },
+ "Solving the step mismatch"
+ );
+
+ // Firefox Nightly (24.0a1) would result in the possible maximum value in this range... (i.e. 12)
+ test(
+ function() {
+ var e = document.getElementById('stepup');
+ e.stepUp();
+ assert_equals(e.value, "9")
+ },
+ "Performing stepUp()"
+ );
+
+ // Firefox Nightly (24.0a1) would result in the possible minimum value in this range... (i.e. 3)
+ test(
+ function() {
+ var e = document.getElementById('stepdown');
+ e.stepDown();
+ assert_equals(e.value, "6")
+ },
+ "Performing stepDown()"
+ );
+
+ // Chrome and Opera would throw DOM Exception 11 (InvalidStateError)
+ // Firefox Nightly gives the correct result
+ test(
+ function() {
+ var e = document.getElementById('stepup_beyond_max');
+ e.stepUp(2);
+ assert_equals(e.value, "12")
+ },
+ "Performing stepUp() beyond the value of the max attribute"
+ );
+
+ // Chrome and Opera would throw DOM Exception 11 (InvalidStateError)
+ // Firefox Nightly gives the correct result
+ test(
+ function() {
+ var e = document.getElementById('stepdown_beyond_min');
+ e.stepDown(2);
+ assert_equals(e.value, "3")
+ }, "Performing stepDown() beyond the value of the min attribute"
+ );
+
+ test(
+ function() {
+ var e = document.getElementById('should_skip_whitespace');
+ assert_equals(e.value, "50")
+ }, "Input should be reset to the default value when value attribute has whitespace"
+ );
+
+ test(
+ function() {
+ var e = document.getElementById('exponent_value1');
+ e.value = 1e2;
+ assert_equals(e.value, "100")
+ }, "Multiply value by ten raised to the exponentth power with `e`"
+ );
+
+ test(
+ function() {
+ var e = document.getElementById('exponent_value2');
+ e.value = 1E2;
+ assert_equals(e.value, "100")
+ }, "Multiply value by ten raised to the exponentth power with `E`"
+ );
+
+ </script>
+
+ </body>
+
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/required_attribute.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/required_attribute.html
new file mode 100644
index 0000000000..63488e9f4c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/required_attribute.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Required Attribute</title>
+ <meta name=viewport content="width=device-width, maximum-scale=1.0, user-scalable=no" />
+ <link rel="author" title="Fabrice Clari" href="mailto:f.clari@inno-group.com">
+ <link rel="author" title="Dimitri Bocquet" href="mailto:Dimitri.Bocquet@mosquito-fp7.eu">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-input-required">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+
+ <body>
+
+
+ <h1>Required Attribute</h1>
+ <div style="display: none">
+ <input type="text" required="required" />
+ </div>
+
+ <div id="log">
+ </div>
+
+ <script type="text/javascript">
+
+
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].getAttribute("required"), "required")}, "required attribute support on input element");
+
+ </script>
+
+ </body>
+
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/reset.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/reset.html
new file mode 100644
index 0000000000..9a97995426
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/reset.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>input type reset</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#reset-button-state-(type=reset)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form>
+ <input type=text id=input1 value="foobar">
+ <input type=text id=input2>
+ <input type=reset id=r1>
+</form>
+
+<input type=text id=input3 value="barfoo">
+
+<table>
+ <form>
+ <tr>
+ <td>
+ <input type=text id=input4 value="foobar">
+ <input type=reset id=r2>
+ </td>
+ </tr>
+ </form>
+</table>
+
+<div>
+ <form>
+ <input type=text id=input5 value="foobar">
+ </div>
+ <input type=reset id=r3>
+</form>
+
+<div>
+ <form>
+ <input type=reset id=r4>
+ </div>
+ <input type=text id=input6 value="foobar">
+</form>
+
+<form id=form5>
+ <input type=reset id=r5>
+</form>
+<input form=form5 type=text id=input7 value="foobar">
+
+<form id=form6>
+ <input type=text id=input8 value="foobar">
+</form>
+<input type=reset form=form6 id=r6>
+
+<script>
+ var input1 = document.getElementById('input1'),
+ input2 = document.getElementById('input2'),
+ input3 = document.getElementById('input3'),
+ input7 = document.getElementById('input7'),
+ input8 = document.getElementById('input8'),
+ r1 = document.getElementById('r1');
+
+ test(function(){
+ assert_equals(input1.value, "foobar");
+ assert_equals(input2.value, "");
+ assert_equals(input3.value, "barfoo");
+ input1.value = "foobar1";
+ input2.value = "notempty";
+ input3.value = "barfoo1";
+ assert_equals(input1.value, "foobar1");
+ assert_equals(input2.value, "notempty");
+ assert_equals(input3.value, "barfoo1");
+ r1.click();
+ assert_equals(input1.value, "foobar");
+ assert_equals(input2.value, "");
+ assert_equals(input3.value, "barfoo1");
+ }, "reset button only resets the form owner");
+
+ test(function(){
+ assert_false(r1.willValidate);
+ }, "the element is barred from constraint validation");
+
+ test(function(){
+ assert_equals(input1.value, "foobar");
+ assert_equals(input2.value, "");
+ assert_equals(input3.value, "barfoo1");
+ r1.disabled = true;
+ r1.click();
+ assert_equals(input1.value, "foobar");
+ assert_equals(input2.value, "");
+ assert_equals(input3.value, "barfoo1");
+ }, "clicking on a disabled reset does nothing");
+
+ function testReset(inputId, buttonId) {
+ var inp = document.getElementById(inputId);
+ assert_equals(inp.value, "foobar");
+ inp.value = "barfoo";
+ assert_equals(inp.value, "barfoo");
+ document.getElementById(buttonId).click();
+ assert_equals(inp.value, "foobar");
+ }
+
+ test(function(){
+ testReset("input4", "r2");
+ testReset("input5", "r3");
+ testReset("input6", "r4");
+ }, "reset button resets controls associated with their form using the form element pointer");
+
+ test(function(){
+ testReset("input7", "r5");
+ }, "reset button resets controls associated with a form using the form attribute");
+
+ test(function(){
+ testReset("input8", "r6");
+ }, "reset button associated with a form using the form attribute resets all the form's controls");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/image-submit-click.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/image-submit-click.html
new file mode 100644
index 0000000000..8461a03d7a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/image-submit-click.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<form>
+ <input type="image" name="name" value="value">
+</form>
+
+<script>
+"use strict";
+if (window.location.search.startsWith("?name.x")) {
+ // The action pointed to ourself, so the form submitted something
+ window.parent.success(window.location.href);
+} else {
+ const input = document.querySelector("input");
+ input.click();
+}
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/loadresolver.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/loadresolver.html
new file mode 100644
index 0000000000..14379dd922
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/loadresolver.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+ window.onload = () => {
+ window.opener.loadResolver();
+ };
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/range-restore-events.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/range-restore-events.html
new file mode 100644
index 0000000000..27044c3537
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/range-restore-events.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ window.onload = () => {
+ const loadResolver = window.opener.loadResolver;
+ if (loadResolver)
+ loadResolver();
+ };
+</script>
+<input type=range min=0 max=10 value=5>
+<script>
+ const input = document.querySelector('input');
+ input.onchange = () => window.seenchange = true;
+ input.oninput = () => window.seeninput = true;
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/range-restore-events.html.headers b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/range-restore-events.html.headers
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/range-restore-events.html.headers
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/show-picker-child-iframe.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/show-picker-child-iframe.html
new file mode 100644
index 0000000000..07b72f02cb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/show-picker-child-iframe.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Test showPicker() in an iframe</title>
+<script type=module>
+import inputTypes from "./../input-types.js";
+
+const urlParams = new URLSearchParams(location.search);
+const documentDomain = urlParams.get('documentDomain');
+if (documentDomain) {
+ document.domain = documentDomain;
+}
+
+let securityErrors = [];
+for (const inputType of inputTypes) {
+ const input = document.createElement("input");
+ input.setAttribute("type", inputType);
+
+ try {
+ input.showPicker();
+ } catch (error) {
+ if (error instanceof DOMException && error.name == 'SecurityError') {
+ securityErrors.push(inputType);
+ }
+ }
+}
+parent.postMessage(securityErrors.join(','), "*");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/text-restore-events.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/text-restore-events.html
new file mode 100644
index 0000000000..aa57bdebcc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/text-restore-events.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ window.onload = () => {
+ const loadResolver = window.opener.loadResolver;
+ if (loadResolver)
+ loadResolver();
+ };
+</script>
+<input type=text value=initialValue>
+<script>
+ const input = document.querySelector('input');
+ input.onchange = () => window.seenchange = true;
+ input.oninput = () => window.seeninput = true;
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/text-restore-events.html.headers b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/text-restore-events.html.headers
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/resources/text-restore-events.html.headers
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/search_input.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/search_input.html
new file mode 100644
index 0000000000..7b63cd43e1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/search_input.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Search Input</title>
+ <meta name=viewport content="width=device-width, maximum-scale=1.0, user-scalable=no" />
+ <link rel="author" title="Fabrice Clari" href="mailto:f.clari@inno-group.com">
+ <link rel="author" title="Dimitri Bocquet" href="mailto:Dimitri.Bocquet@mosquito-fp7.eu">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-input-element">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-type">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-input-placeholder">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+
+ <body>
+
+
+ <h1>Search Input</h1>
+ <input type="search" style="display:none" placeholder="Search..." />
+
+ <div id="log">
+ </div>
+
+ <script type="text/javascript">
+
+
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].type, "search")}, "search type support on input element");
+ test(function() {assert_equals(document.getElementsByTagName("input")[0].placeholder, "Search...")}, "placeholder attribute support on input element");
+
+ </script>
+
+ </body>
+
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/selection-pointer.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/selection-pointer.html
new file mode 100644
index 0000000000..8cb0ead441
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/selection-pointer.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Selecting texts across input element</title>
+<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>
+<link rel="stylesheet" href="/fonts/ahem.css" />
+
+<style>
+ .test {
+ font: 16px/1 Ahem;
+ padding-bottom: 16px;
+ }
+</style>
+<div class="test">
+ <span id="foo">foo</span><br>
+ <input id="input"><br>
+ <span id="bar">bar</span>
+</div>
+<script type="module">
+import inputTypes from "./input-types.js";
+
+const selection = getSelection();
+const inputVisibleTypes = inputTypes.filter(t => t !== "hidden");
+
+for (const inputType of inputVisibleTypes) {
+ promise_test(async () => {
+ input.type = inputType;
+ selection.collapse(foo);
+ await new test_driver.Actions()
+ .pointerMove(0, 0, {origin: foo})
+ .pointerDown()
+ .pointerMove(0, 0, {origin: input})
+ .pointerMove(0, 0, {origin: bar})
+ .pointerUp()
+ .send();
+ assert_equals(selection.anchorNode, foo.childNodes[0], "anchorNode");
+ assert_equals(selection.focusNode, bar.childNodes[0], "focusNode");
+ }, `Selecting texts across <input type=${inputType}> should not cancel selection`);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/selection-weekmonth.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/selection-weekmonth.html
new file mode 100644
index 0000000000..c0d36dded9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/selection-weekmonth.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<title>Input element programmatic selection support</title>
+<link rel="author" title="yaycmyk" href="mailto:evan@yaycmyk.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-select">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+
+/* all textual, non-hidden inputs support .select() */
+test(function() {
+ var valid = [
+ "month",
+ "week",
+ ];
+
+ valid.forEach(function(type) {
+ test(function() {
+ var input = document.createElement("input");
+ var a;
+
+ input.type = type;
+ assert_equals(input.type, type, "the given input type is not supported");
+
+ input.select();
+
+ }, "input type " + type + " should support the select() method");
+ });
+});
+
+/* only certain input types are allowed to have a variable-length selection */
+test(function() {
+ var invalid = [
+ "month",
+ "week",
+ ];
+
+ invalid.forEach(function(type) {
+ test(function() {
+ var input = document.createElement("input");
+
+ input.type = type;
+ assert_equals(input.type, type, "the given input type is not supported");
+
+ assert_equals(input.selectionStart, null, 'getting input.selectionStart');
+ assert_throws_dom("INVALID_STATE_ERR", function() { input.selectionStart = 0; });
+ assert_equals(input.selectionEnd, null, 'getting input.selectionEnd');
+ assert_throws_dom("INVALID_STATE_ERR", function() { input.selectionEnd = 0; });
+ assert_equals(input.selectionDirection, null, 'getting input.selectionDirection');
+ assert_throws_dom("INVALID_STATE_ERR", function() { input.selectionDirection = "none"; });
+ assert_throws_dom("INVALID_STATE_ERR", function() { input.setSelectionRange(0, 0); });
+ assert_throws_dom("INVALID_STATE_ERR", function() { input.setRangeText('', 0, 0); });
+
+ }, "input type " + type + " should not support variable-length selections");
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/selection.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/selection.html
new file mode 100644
index 0000000000..1f5c8378ff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/selection.html
@@ -0,0 +1,140 @@
+<!DOCTYPE HTML>
+<title>Input element programmatic selection support</title>
+<link rel="author" title="yaycmyk" href="mailto:evan@yaycmyk.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-select">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+
+/* all textual, non-hidden inputs support .select() */
+test(function() {
+ var valid = [
+ "text",
+ "search",
+ "url",
+ "tel",
+ "email",
+ "password",
+ "date",
+ "time",
+ "datetime-local",
+ "number",
+ "color",
+ "file",
+ ];
+
+ var invalid = [
+ "hidden",
+ "range",
+ "checkbox",
+ "radio",
+ "submit",
+ "image",
+ "reset",
+ "button"
+ ];
+
+ valid.forEach(function(type) {
+ test(function() {
+ var input = document.createElement("input");
+ var a;
+
+ input.type = type;
+ assert_equals(input.type, type, "the given input type is not supported");
+
+ input.select();
+
+ }, "input type " + type + " should support the select() method");
+ });
+
+ invalid.forEach(function(type) {
+ test(function() {
+ var input = document.createElement("input");
+
+ input.type = type;
+ assert_equals(input.type, type, "the given input type is not supported");
+
+ var selectionStartBefore = input.selectionStart;
+ var selectionEndBefore = input.selectionEnd;
+ var selectionDirectionBefore = input.selectionDirection;
+
+ // Does not throw; see https://github.com/whatwg/html/issues/2275
+ input.select();
+
+ assert_equals(input.selectionStart, selectionStartBefore, "selectionStart must not change");
+ assert_equals(input.selectionEnd, selectionEndBefore, "selectionEnd must not change");
+ assert_equals(input.selectionDirection, selectionDirectionBefore, "selectionDirection must not change");
+
+ }, "input type " + type + " should do nothing when the select() method is called (but, not throw)");
+ });
+});
+
+/* only certain input types are allowed to have a variable-length selection */
+test(function() {
+ var valid = [
+ "text",
+ "search",
+ "url",
+ "tel",
+ "password"
+ ];
+
+ var invalid = [
+ "hidden",
+ "email",
+ "date",
+ "time",
+ "datetime-local",
+ "number",
+ "range",
+ "color",
+ "checkbox",
+ "radio",
+ "file",
+ "submit",
+ "image",
+ "reset",
+ "button"
+ ];
+
+ valid.forEach(function(type) {
+ test(function() {
+ var input = document.createElement("input");
+ var a;
+
+ input.type = type;
+ assert_equals(input.type, type, "the given input type is not supported");
+
+ a = input.selectionStart;
+ input.selectionStart = 0;
+ a = input.selectionEnd;
+ input.selectionEnd = 0;
+ a = input.selectionDirection;
+ input.selectionDirection = "none";
+ input.setSelectionRange(0, 0);
+ input.setRangeText('', 0, 0);
+
+ }, "input type " + type + " should support all selection attributes and methods");
+ });
+
+ invalid.forEach(function(type) {
+ test(function() {
+ var input = document.createElement("input");
+
+ input.type = type;
+ assert_equals(input.type, type, "the given input type is not supported");
+
+ assert_equals(input.selectionStart, null, 'getting input.selectionStart');
+ assert_throws_dom("INVALID_STATE_ERR", function() { input.selectionStart = 0; });
+ assert_equals(input.selectionEnd, null, 'getting input.selectionEnd');
+ assert_throws_dom("INVALID_STATE_ERR", function() { input.selectionEnd = 0; });
+ assert_equals(input.selectionDirection, null, 'getting input.selectionDirection');
+ assert_throws_dom("INVALID_STATE_ERR", function() { input.selectionDirection = "none"; });
+ assert_throws_dom("INVALID_STATE_ERR", function() { input.setSelectionRange(0, 0); });
+ assert_throws_dom("INVALID_STATE_ERR", function() { input.setRangeText('', 0, 0); });
+
+ }, "input type " + type + " should not support variable-length selections");
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-cross-origin-iframe.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-cross-origin-iframe.html
new file mode 100644
index 0000000000..c8197cc180
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-cross-origin-iframe.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<title>Test showPicker() called from cross-origin iframe</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<body>
+<iframe id="iframe1"></iframe>
+<iframe id="iframe2"></iframe>
+<iframe id="iframe3"></iframe>
+<iframe id="iframe4"></iframe>
+</body>
+<script>
+function waitForSecurityErrors() {
+ return new Promise((resolve) => {
+ window.addEventListener("message", (event) => resolve(event.data), {
+ once: true,
+ });
+ });
+}
+
+promise_test(async (t) => {
+ iframe1.src =
+ new URL("resources/", self.location).pathname +
+ "show-picker-child-iframe.html";
+
+ // Wait for the iframe to report security errors when calling showPicker().
+ const securityErrors = await waitForSecurityErrors();
+ assert_equals(
+ securityErrors,
+ "",
+ "In same-origin iframes, showPicker() does not throw a SecurityError."
+ );
+});
+
+promise_test(async (t) => {
+ iframe2.src =
+ get_host_info().HTTP_NOTSAMESITE_ORIGIN +
+ new URL("resources/", self.location).pathname +
+ "show-picker-child-iframe.html";
+
+ // Wait for the iframe to report security errors when calling showPicker().
+ const securityErrors = await waitForSecurityErrors();
+ assert_equals(
+ securityErrors,
+ "button,checkbox,date,datetime-local,email,hidden,image,month,number,password,radio,range,reset,search,submit,tel,text,time,url,week",
+ "In cross-origin iframes, showPicker() throws a SecurityError except on file and color."
+ );
+});
+
+promise_test(async (t) => {
+ iframe3.src =
+ new URL("resources/", self.location).pathname +
+ "show-picker-child-iframe.html?documentDomain=" + get_host_info().ORIGINAL_HOST;
+
+ // Wait for the iframe to report security errors when calling showPicker().
+ const securityErrors = await waitForSecurityErrors();
+ assert_equals(
+ securityErrors,
+ "",
+ "In same-origin but cross-origin-domain iframes, showPicker() does not throw a SecurityError."
+ );
+});
+
+promise_test(async (t) => {
+ document.domain = get_host_info().ORIGINAL_HOST;
+ iframe4.src =
+ get_host_info().HTTP_REMOTE_ORIGIN +
+ new URL("resources/", self.location).pathname +
+ "show-picker-child-iframe.html?documentDomain=" + get_host_info().ORIGINAL_HOST;
+
+ // Wait for the iframe to report security errors when calling showPicker().
+ const securityErrors = await waitForSecurityErrors();
+ assert_equals(
+ securityErrors,
+ "button,checkbox,date,datetime-local,email,hidden,image,month,number,password,radio,range,reset,search,submit,tel,text,time,url,week",
+ "In cross-origin but same-origin-domain iframes, showPicker() throws a SecurityError except on file and color."
+ );
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-disabled-readonly.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-disabled-readonly.html
new file mode 100644
index 0000000000..8fdffc158f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-disabled-readonly.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>Test showPicker() disabled/readonly requirement</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<body></body>
+<script type=module>
+import inputTypes from "./input-types.js";
+
+for (const inputType of inputTypes) {
+ test(() => {
+ const input = document.createElement("input");
+ input.setAttribute("type", inputType);
+ input.setAttribute("disabled", "");
+
+ assert_throws_dom('InvalidStateError', () => { input.showPicker(); });
+ }, `input[type=${inputType}] showPicker() throws when disabled`);
+}
+
+const noReadonlySupport = ['button', 'checkbox', 'color', 'file',
+'hidden', 'image', 'radio', 'range', 'reset', 'submit'];
+for (const inputType of inputTypes) {
+ if (!noReadonlySupport.includes(inputType)) {
+ test(() => {
+ const input = document.createElement("input");
+ input.setAttribute("type", inputType);
+ input.setAttribute("readonly", "");
+
+ assert_throws_dom('InvalidStateError', () => { input.showPicker(); });
+ }, `input[type=${inputType}] showPicker() throws when readonly`);
+ } else {
+ test(() => {
+ const input = document.createElement("input");
+ input.setAttribute("type", inputType);
+ input.setAttribute("readonly", "");
+
+ // Missing user gesture activation throws.
+ assert_throws_dom('NotAllowedError', () => { input.showPicker(); });
+ }, `input[type=${inputType}] showPicker() doesn't throw when readonly`);
+ }
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-user-gesture.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-user-gesture.html
new file mode 100644
index 0000000000..6b94b4f0f0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/show-picker-user-gesture.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Test showPicker() user gesture requirement</title>
+<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-vendor.js"></script>
+<body></body>
+<script type=module>
+import inputTypes from "./input-types.js";
+
+for (const inputType of inputTypes) {
+ test(() => {
+ const input = document.createElement("input");
+ input.setAttribute("type", inputType);
+
+ assert_throws_dom('NotAllowedError', () => { input.showPicker(); });
+ }, `input[type=${inputType}] showPicker() requires a user gesture`);
+}
+
+for (const inputType of inputTypes) {
+ promise_test(async t => {
+ const input = document.createElement("input");
+ input.setAttribute("type", inputType);
+
+ await test_driver.bless('show picker');
+ input.showPicker();
+ input.blur();
+ }, `input[type=${inputType}] showPicker() does not throw when user activation is active`);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/telephone.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/telephone.html
new file mode 100644
index 0000000000..974cbaf88b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/telephone.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Input tel</title>
+ <link rel="author" title="Kazuki Kanamori" href="mailto:yogurito@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#telephone-state-(type=tel)">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <h1>Input tel</h1>
+ <input type="tel" id="novalue" />
+ <input type="tel" id="value_with_LF" value="0&#x000A;1" />
+ <input type="tel" id="value_with_CR" value="0&#x000D;1" />
+ <input type="tel" id="value_with_CRLF" value="0&#x000A;&#x000D;1" />
+ <div id="log">
+ </div>
+
+ <script type="text/javascript">
+ var element = document.getElementById('novalue');
+ test(function(){
+ assert_equals(element.type, 'tel');
+ }, 'tel type supported on input element');
+ test(function(){
+ element.value = '0\u000A1';
+ assert_equals(element.value, '01');
+ }, 'User agents must not allow users to insert "LF" (U+000A)');
+ test(function(){
+ element.value = '0\u000D1';
+ assert_equals(element.value, '01');
+ }, 'User agents must not allow users to insert "CR" (U+000D)');
+
+ element = document.getElementById('value_with_LF');
+ test(function(){
+ assert_equals(element.value, '01');
+ }, 'The value attribute, if specified, must have a value that contains no "LF" (U+000A)');
+
+ element = document.getElementById('value_with_CR');
+ test(function(){
+ assert_equals(element.value, '01');
+ }, 'The value attribute, if specified, must have a value that contains no "CR" (U+000D)');
+
+ test(function(){
+ element = document.getElementById('novalue');
+ element.value = '0\u000D\u000A1';
+ assert_equals(element.value, '01');
+
+ element = document.getElementById('value_with_CRLF');
+ assert_equals(element.value, '01');
+ }, 'The value sanitization algorithm is as follows: Strip line breaks from the value');
+
+ element = document.getElementById('novalue');
+ test(function(){
+ element.value = '+811234';
+ assert_equals(element.value, '+811234');
+ }, 'Element can accept the phone number with plus sign(country code)');
+ test(function(){
+ element.value = '1234#5678';
+ assert_equals(element.value, '1234#5678');
+ }, 'Element can accept the phone number with hash mark(extension number)');
+ test(function(){
+ element.value = '123-456-789';
+ assert_equals(element.value, '123-456-789');
+ }, 'Element can accept the phone number with hyphen');
+ test(function(){
+ element.value = '123.456.789';
+ assert_equals(element.value, '123.456.789');
+ }, 'Element can accept the phone number with dots');
+ test(function(){
+ element.value = '1 23 4';
+ assert_equals(element.value, '1 23 4');
+ }, 'Element can accept the phone number with whitespace');
+ test(function(){
+ element.value = ' 1234 ';
+ assert_equals(element.value, ' 1234 ');
+ }, 'Element can accept the phone number with leading & following whitespaces');
+ test(function(){
+ element.value = '(03)12345678';
+ assert_equals(element.value, '(03)12345678');
+ }, 'Element can accept the phone number with parentheses(area code)');
+ </script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/text.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/text.html
new file mode 100644
index 0000000000..f30f9d39fc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/text.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Text input element</title>
+ <link rel="author" title="Kinuko Yasuda" href="mailto:kinuko@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#text-(type=text)-state-and-search-state-(type=search)">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+
+ <body>
+ <h1>Text input element</h1>
+ <div style="display: none">
+
+ <input id="text" type="text" />
+ <input id="text_with_value" type="text" value="foo" />
+
+ <input id="search" type="search" />
+ <input id="search_with_value" type="search" value="foo" />
+
+ </div>
+ <div id="log"></div>
+ <script type="text/javascript">
+ var types = [ 'text', 'search' ];
+
+ for (var i = 0; i < types.length; ++i) {
+ test(
+ function() {
+ assert_equals(document.getElementById(types[i]).value, "");
+ assert_equals(document.getElementById(types[i] + "_with_value").value, "foo");
+ }, "Value returns the current value for " + types[i]);
+
+ test(
+ function() {
+ document.getElementById(types[i]).value = "A";
+ assert_equals(document.getElementById(types[i]).value, "A");
+ document.getElementById(types[i]).value = "B";
+ }, "Setting value changes the current value for " + types[i]);
+
+ test(
+ function() {
+ // Any LF (\n) must be stripped.
+ document.getElementById(types[i]).value = "\nAB";
+ assert_equals(document.getElementById(types[i]).value, "AB");
+ document.getElementById(types[i]).value = "A\nB";
+ assert_equals(document.getElementById(types[i]).value, "AB");
+ document.getElementById(types[i]).value = "AB\n";
+ assert_equals(document.getElementById(types[i]).value, "AB");
+
+ // Any CR (\r) must be stripped.
+ document.getElementById(types[i]).value = "\rAB";
+ assert_equals(document.getElementById(types[i]).value, "AB");
+ document.getElementById(types[i]).value = "A\rB";
+ assert_equals(document.getElementById(types[i]).value, "AB");
+ document.getElementById(types[i]).value = "AB\r";
+ assert_equals(document.getElementById(types[i]).value, "AB");
+
+ // Any combinations of LF CR must be stripped.
+ document.getElementById(types[i]).value = "\r\nAB";
+ assert_equals(document.getElementById(types[i]).value, "AB");
+ document.getElementById(types[i]).value = "A\r\nB";
+ assert_equals(document.getElementById(types[i]).value, "AB");
+ document.getElementById(types[i]).value = "AB\r\n";
+ assert_equals(document.getElementById(types[i]).value, "AB");
+ document.getElementById(types[i]).value = "\r\nA\n\rB\r\n";
+ assert_equals(document.getElementById(types[i]).value, "AB");
+ }, "Value sanitization algorithm should strip line breaks for " + types[i]);
+
+ test(
+ function() {
+ assert_equals(document.getElementById(types[i]).files, null);
+ }, "files attribute must return null for " + types[i]);
+
+ test(
+ function() {
+ assert_equals(document.getElementById(types[i]).valueAsDate, null);
+ }, "valueAsDate attribute must return null for " + types[i]);
+
+ test(
+ function() {
+ assert_equals(document.getElementById(types[i]).valueAsNumber, NaN);
+ }, "valueAsNumber attribute must return NaN for " + types[i]);
+
+ test(
+ function() {
+ assert_equals(document.getElementById("text").list, null);
+ }, "list attribute must return null for " + types[i]);
+
+ test(
+ function() {
+ var el = document.getElementById(types[i]);
+ assert_throws_dom("InvalidStateError", function() { el.stepDown(); }, "");
+ }, "stepDown does not apply for " + types[i]);
+
+ test(
+ function() {
+ var el = document.getElementById(types[i]);
+ assert_throws_dom("InvalidStateError", function() { el.stepUp(); }, "");
+ }, "stepUp does not apply for " + types[i]);
+ }
+
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/time-2.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/time-2.html
new file mode 100644
index 0000000000..0ffec33bf5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/time-2.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Form input type=time</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#times">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#time-state-(type=time)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ var times = [
+ {value: "", expected: "", testname: "empty value"},
+ {value: "00:00", expected: "00:00", testname: "Valid value: value should be 00:00"},
+ {value: "00:00:00", expected: "00:00:00", testname: "Valid value: value should be 00:00:00"},
+ {value: "00:00:00.0", expected: "00:00:00.0", testname: "Valid value: value should be 00:00:00.0"},
+ {value: "00:00:00.00", expected: "00:00:00.00", testname: "Valid value: value should be 00:00:00.00"},
+ {value: "00:00:00.000", expected: "00:00:00.000", testname: "Valid value: value should be 00:00:00.000"},
+ {value: "00:00:00.0000", expected: "", testname: "Invalid value: fraction should have one, two or three ASCII digits. Value should be empty"},
+ {value: "0:00:00.000", expected: "", testname: "Invalid value: hour should have two ASCII digits. Value should be empty"},
+ {value: "00:0:00.000", expected: "", testname: "Invalid value: minutes should have two ASCII digits. Value should be empty"},
+ {value: "00:00:0.000", expected: "", testname: "Invalid value: seconds should have two ASCII digits. Value should be empty"},
+ {value: "24:00:00.000", expected: "", testname: "Invalid value: hour > 23. Value should be empty"},
+ {value: "00:60:00.000", expected: "", testname: "Invalid value: minute > 59. Value should be empty"},
+ {value: "00:00:60.000", expected: "", testname: "Invalid value: second > 59. Value should be empty"},
+ {value: "12:00:00.001", attributes: { min: "12:00:00.000" }, expected: "12:00:00.001", testname: "Value >= min attribute"},
+ {value: "12:00:00.000", attributes: { min: "12:00:00.001" }, expected: "12:00:00.000", testname: "Value < min attribute"},
+ {value: "12:00:00.000", attributes: { max: "12:00:00.001" }, expected: "12:00:00.000", testname: "Value <= max attribute"},
+ {value: "12:00:00.001", attributes: { max: "12:00:00.000" }, expected: "12:00:00.001", testname: "Value > max attribute"}
+ ];
+ for (var i = 0; i < times.length; i++) {
+ var w = times[i];
+ test(function() {
+ var input = document.createElement("input");
+ input.type = "time";
+ input.value = w.value;
+ for(var attr in w.attributes) {
+ input[attr] = w.attributes[attr];
+ }
+ assert_equals(input.value, w.expected);
+ }, w.testname);
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/time-datalist-crash.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/time-datalist-crash.html
new file mode 100644
index 0000000000..2964032e35
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/time-datalist-crash.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-input-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="newParent"></div>
+<datalist id="suggestions">
+ <option>12:00</option>
+ <input type="time" list="suggestions">
+</datalist>
+<script>
+ test(() => {
+ document.body.offsetTop;
+ newParent.appendChild(suggestions);
+ }, "Moving a datalist enclosing an input type=time using that list should not crash.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/time-focus-dynamic-value-change.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/time-focus-dynamic-value-change.html
new file mode 100644
index 0000000000..95ccb1ff69
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/time-focus-dynamic-value-change.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>input type=date and input type=datetime handle focus state correctly</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time)">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1450219">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<input type="time">
+<input type="text">
+<script>
+let t = async_test("Time input handles focus correctly when value changes");
+window.onload = t.step_func_done(function() {
+ let time = document.querySelector("input[type=time]");
+ let text = document.querySelector("input[type=text]");
+ time.focus();
+ assert_true(time.matches(":focus"));
+ assert_equals(document.activeElement, time);
+ time.value = "08:10:10";
+ assert_true(time.matches(":focus"));
+ assert_equals(document.activeElement, time);
+ time.value = "08:10";
+ assert_true(time.matches(":focus"));
+ assert_equals(document.activeElement, time);
+ text.focus();
+ assert_true(text.matches(":focus"));
+ assert_false(time.matches(":focus"));
+ assert_equals(document.activeElement, text);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/time.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/time.html
new file mode 100644
index 0000000000..ec815d4cb3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/time.html
@@ -0,0 +1,357 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Input Time</title>
+ <meta name=viewport content="width=device-width, maximum-scale=1.0, user-scalable=no" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-input-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+
+ <body>
+ <h1>Input Time</h1>
+ <div style="display:none;">
+ <input type="time "id="chkDefaultValue" />
+ <input type="time" id="chkStep" />
+ <input type="time" id="chkSetValueTest" />
+ <input type="time" id="chkSupportAttribute" min="01:01:01.001" max="12:12:12.012" step="600" />
+ </div>
+ <div id="log">
+ </div>
+
+ <script type="text/javascript">
+
+/* check default value */
+test(function(){ assert_equals(document.getElementById("chkDefaultValue").value, "");
+}, "time element of default time value");
+test(function(){assert_equals(document.getElementById('chkStep').step, "");
+}, "step attribute on default value check");
+test(function(){assert_equals(document.getElementById('chkDefaultValue').max, "");
+}, "max attribute on default value check")
+test(function(){assert_equals(document.getElementById('chkDefaultValue').max, "");
+}, "min attribute on default value check")
+
+/* simple attribute test*/
+test(function(){assert_equals(document.getElementById("chkSupportAttribute").type,"time");}
+ , "type attribute support on input element");
+test(function(){assert_equals(document.getElementById('chkSupportAttribute').min, "01:01:01.001")}
+ , "max attribute support on input element");
+test(function(){assert_equals(document.getElementById('chkSupportAttribute').max, "12:12:12.012")}
+ , "min attribute support on input element");
+test(function(){assert_equals(document.getElementById("chkSupportAttribute").step, "600")}
+ , "step attribute support on input element");
+
+/* check step up and down */
+var _StepTest = document.getElementById("chkStep");
+test(function(){ assert_true(typeof(_StepTest.stepUp) ==="function" ) } , "stepUp function support on input Element");
+test(function(){ assert_true(typeof(_StepTest.stepDown) ==="function" ) } , "stepDown function support on input Element");
+
+test(function(){
+ _StepTest.value = "12:00";
+ _StepTest.step = "";
+ _StepTest.stepUp();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "12:01",
+ "12:01:00",
+ "12:01:00.0",
+ "12:01:00.00",
+ "12:01:00.000"],
+ "a valid time string representing 1 minute after noon");
+} , "stepUp step value empty on default step value ");
+
+test(function(){
+ _StepTest.value = "12:00";
+ _StepTest.step = "";
+ _StepTest.stepDown();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "11:59",
+ "11:59:00",
+ "11:59:00.0",
+ "11:59:00.00",
+ "11:59:00.000"],
+ "a valid time string representing 1 minute before noon");
+}, "stepDown step value empty default step value");
+
+test(function(){
+ _StepTest.value = "12:00";
+ _StepTest.step = "-600";
+ _StepTest.stepUp();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "12:01",
+ "12:01:00",
+ "12:01:00.0",
+ "12:01:00.00",
+ "12:01:00.000"],
+ "a valid time string representing 1 minute after noon");
+},"stepUp on step value minus");
+test(function(){
+ _StepTest.value = "12:00";
+ _StepTest.step = "-600";
+ _StepTest.stepDown();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "11:59",
+ "11:59:00",
+ "11:59:00.0",
+ "11:59:00.00",
+ "11:59:00.000"],
+ "a valid time string representing 1 minute before noon");
+},"stepDown on step value minus");
+
+test(function(){
+ _StepTest.value = "12:00";
+ _StepTest.step = "0";
+ _StepTest.stepUp();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "12:01",
+ "12:01:00",
+ "12:01:00.0",
+ "12:01:00.00",
+ "12:01:00.000"],
+ "a valid time string representing 1 minute after noon");
+} , "stepUp on step value zero ");
+test(function(){
+ _StepTest.value = "12:00";
+ _StepTest.step = "0";
+ _StepTest.stepDown();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "11:59",
+ "11:59:00",
+ "11:59:00.0",
+ "11:59:00.00",
+ "11:59:00.000"],
+ "a valid time string representing 1 minute before noon");
+} , "stepDown on step value zero ");
+
+test(function(){
+ _StepTest.value = "00:00";
+ _StepTest.step = "86399";
+ _StepTest.stepUp();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "23:59:59",
+ "23:59:59.0",
+ "23:59:59.00",
+ "23:59:59.000"],
+ "a valid time string representing 1 second before midnight");
+} , "stepUp on step value 24 hour");
+test(function(){
+ _StepTest.value = "23:59:59";
+ _StepTest.step = "86399";
+ _StepTest.stepDown();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "00:00",
+ "00:00:00",
+ "00:00:00.0",
+ "00:00:00.00",
+ "00:00:00.000"],
+ "a valid time string representing midnight");
+} , "stepDown on step value 24 hour ");
+
+test(function(){
+ _StepTest.value = "12:00";
+ _StepTest.step = "3600";
+ _StepTest.stepUp();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "13:00",
+ "13:00:00",
+ "13:00:00.0",
+ "13:00:00.00",
+ "13:00:00.000"],
+ "a valid time string representing 1pm");
+} , "stepUp on step value hour ");
+test(function(){
+ _StepTest.value = "12:00";
+ _StepTest.step = "3600";
+ _StepTest.stepDown();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "11:00",
+ "11:00:00",
+ "11:00:00.0",
+ "11:00:00.00",
+ "11:00:00.000"],
+ "a valid time string representing 11am");
+} , "stepDown on step value hour ");
+
+test(function(){
+ _StepTest.value = "12:00";
+ _StepTest.step = "1";
+ _StepTest.stepUp();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "12:00:01",
+ "12:00:01.0",
+ "12:00:01.00",
+ "12:00:01.000"],
+ "a valid time string representing 1 second after noon");
+} , "stepUp on step value second ");
+test(function(){
+ _StepTest.value = "12:00";
+ _StepTest.step = "1";
+ _StepTest.stepDown();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "11:59:59",
+ "11:59:59.0",
+ "11:59:59.00",
+ "11:59:59.000"],
+ "a valid time string representing 1 second before noon");
+} , "stepDown on step value second ");
+
+test(function(){
+ _StepTest.value = "12:00";
+ _StepTest.step = "0.001";
+ _StepTest.stepUp();
+ assert_equals(_StepTest.value, "12:00:00.001");
+} , "stepUp on step value with fractional seconds");
+test(function(){
+ _StepTest.value = "12:00";
+ _StepTest.step = "0.001";
+ _StepTest.stepDown();
+ assert_equals(_StepTest.value, "11:59:59.999");
+} , "stepDown on step value with fractional seconds");
+
+test(function(){
+ _StepTest.value = "13:00:00";
+ _StepTest.step = "1";
+ _StepTest.stepUp(2);
+ assert_in_array(
+ _StepTest.value,
+ [
+ "13:00:02",
+ "13:00:02.0",
+ "13:00:02.00",
+ "13:00:02.000"],
+ "a valid time string representing 2 seconds after 1pm");
+}, "stepUp argument 2 times");
+test(function(){
+ _StepTest.value = "13:00:00";
+ _StepTest.step = "1";
+ _StepTest.stepDown(2);
+ assert_in_array(
+ _StepTest.value,
+ [
+ "12:59:58",
+ "12:59:58.0",
+ "12:59:58.00",
+ "12:59:58.000"],
+ "a valid time string representing 2 seconds before 1pm");
+}, "stepDown argument 2 times");
+
+test(function(){
+ _StepTest.max = "15:00";
+ this.add_cleanup(function() { _StepTest.max = ""; });
+ _StepTest.value = "15:00";
+ _StepTest.stepUp();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "15:00",
+ "15:00:00",
+ "15:00:00.0",
+ "15:00:00.00",
+ "15:00:00.000"],
+ "a valid time string representing 3pm");
+} , "stepUp stop because it exceeds the maximum value");
+test(function(){
+ _StepTest.min = "13:00";
+ this.add_cleanup(function() { _StepTest.min = ""; });
+ _StepTest.value = "13:00";
+ _StepTest.stepDown();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "13:00",
+ "13:00:00",
+ "13:00:00.0",
+ "13:00:00.00",
+ "13:00:00.000"],
+ "a valid time string representing 1pm");
+} , "stepDown stop so lower than the minimum value");
+
+test(function(){
+ // Set min value to ensure that 15:01 - base is a multiple of 2 min (i.e., a
+ // valid value).
+ _StepTest.min = "14:01";
+ _StepTest.max = "15:01";
+ this.add_cleanup(function() { _StepTest.min = _StepTest.max = ""; });
+ _StepTest.value = "15:00";
+ _StepTest.step = "120";
+ _StepTest.stepUp();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "15:01",
+ "15:01:00",
+ "15:01:00.0",
+ "15:01:00.00",
+ "15:01:00.000"],
+ "a valid time string representing 1 minute after 3pm");
+} , "stop at border on stepUp");
+test(function(){
+ _StepTest.min = "12:59";
+ this.add_cleanup(function() { _StepTest.min = ""; });
+ _StepTest.value = "13:00";
+ _StepTest.step = "120";
+ _StepTest.stepDown();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "12:59",
+ "12:59:00",
+ "12:59:00.0",
+ "12:59:00.00",
+ "12:59:00.000"],
+ "a valid time string representing 1 minute before 2pm");
+} , "stop at border on stepDown");
+
+test(function(){
+ _StepTest.value = "";
+ _StepTest.step = "60";
+ _StepTest.stepUp();
+ assert_in_array(
+ _StepTest.value,
+ [
+ "00:01",
+ "00:01:00",
+ "00:01:00.0",
+ "00:01:00.00",
+ "00:01:00.000"],
+ "a valid time string representing 1 minute after midnight");
+} , " empty value of stepUp");
+
+
+/* set value test */
+test(function(){
+ var _time = document.getElementById("chkSetValueTest");
+ _time.value = "12:00:00.000";
+ assert_equals(_time.value, "12:00:00.000");
+ _time.value = "hh:mi:ss.sss";
+ assert_equals(_time.value, "");
+}, "set value on not time format value");
+
+
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/type-change-file-to-text-crash.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/type-change-file-to-text-crash.html
new file mode 100644
index 0000000000..5fb5000a26
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/type-change-file-to-text-crash.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#input-type-change">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<input id="myInput" type="file">
+<script>
+ test(() => {
+ myInput.offsetTop;
+ myInput.type = "text";
+ }, "Changing type from file to text should not crash.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/type-change-state-weekmonth.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/type-change-state-weekmonth.html
new file mode 100644
index 0000000000..cab8e3a625
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/type-change-state-weekmonth.html
@@ -0,0 +1,169 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Input element's type attribute changes state</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#the-input-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+
+ const INITIAL_VALUE = " foo\rbar ";
+
+ // Sanitize algorithm implementations only for values used in this test.
+ function sanitizeText(value) {
+ switch (value) {
+ case INITIAL_VALUE: return " foobar ";
+ case " foobar ": return value;
+ case "foobar": return value;
+ case "50": return value;
+ case "#000000": return value;
+ case "": return value;
+ default: throw new DOMException(`Internal Error: Should add support of "${value}"`, "NotSupportedError");
+ }
+ }
+ function sanitizeEmailOrUrl(value) {
+ switch (value) {
+ case INITIAL_VALUE: return "foobar";
+ case " foobar ": return "foobar";
+ case "foobar": return value;
+ case "50": return value;
+ case "#000000": return value;
+ case "": return value;
+ default: throw new DOMException(`Internal Error: Should add support of "${value}"`, "NotSupportedError");
+ }
+ }
+ function sanitizeTemporal(value) {
+ // We have no test cases using valid temporal values.
+ return "";
+ }
+ function sanitizeNumber(value) {
+ switch (value) {
+ case "50": return value;
+ default:
+ // We have no test cases using valid numbers other than "50".
+ return "";
+ }
+ }
+ function sanitizeRange(value) {
+ // We have no test cases using valid numbers other than "50".
+ return "50";
+ }
+ function sanitizeColor(value) {
+ // We have no test cases using valid colors other than "#000000".
+ return "#000000";
+ }
+ function browserSupportsInputTypeOf(inputType) {
+ var inputTest = document.createElement("input");
+ inputTest.type = inputType;
+ return (inputTest.type === inputType);
+ }
+
+
+ var types = [
+ { type: "hidden" },
+ { type: "text", sanitizer: sanitizeText },
+ { type: "search", sanitizer: sanitizeText },
+ { type: "tel", sanitizer: sanitizeText },
+ { type: "url", sanitizer: sanitizeEmailOrUrl },
+ { type: "email", sanitizer: sanitizeEmailOrUrl },
+ { type: "password", sanitizer: sanitizeText },
+ { type: "datetime-local", sanitizer: sanitizeTemporal },
+ { type: "date", sanitizer: sanitizeTemporal },
+ { type: "month", sanitizer: sanitizeTemporal },
+ { type: "week", sanitizer: sanitizeTemporal },
+ { type: "time", sanitizer: sanitizeTemporal },
+ { type: "number", sanitizer: sanitizeNumber },
+ { type: "range", sanitizer: sanitizeRange },
+ { type: "color", sanitizer: sanitizeColor },
+ { type: "checkbox", defaultValue: "on" },
+ { type: "radio", defaultValue: "on" },
+ { type: "file" },
+ { type: "submit" },
+ { type: "image" },
+ { type: "reset" },
+ { type: "button" }
+ ];
+
+ const selectionStart = 2;
+ const selectionEnd = 5;
+ const selectionDirection = "backward";
+
+ // Obtain selectionDirection after setting it to "none".
+ // Some platforms don't support "none" direction, and "forward" is returned
+ // in such platforms.
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#set-the-selection-direction
+ function testNoneDirection() {
+ const input = document.createElement("input");
+ input.selectionDirection = "none";
+ return input.selectionDirection;
+ }
+ const noneDirectionResult = testNoneDirection();
+
+ for (var i = 0; i < types.length; i++) {
+ for (var j = 0; j < types.length; j++) {
+ const monthOrWeek = types[i].type === 'month' || types[i].type === 'week' || types[j].type === 'month' || types[j].type === 'week';
+ if ((types[i] != types[j]) && monthOrWeek) {
+ test(function() {
+ assert_implements(browserSupportsInputTypeOf(types[i].type), "Support for input type " + types[i].type + " is required for this test.");
+ assert_implements(browserSupportsInputTypeOf(types[j].type), "Support for input type " + types[j].type + " is required for this test.");
+ var input = document.createElement("input");
+ var expected = INITIAL_VALUE;
+ input.type = types[i].type;
+ if (types[i].type === "file") {
+ assert_throws_dom("INVALID_STATE_ERR", function() {
+ input.value = expected;
+ });
+ assert_equals(input.value, "");
+ } else if (types[j].type === "file") {
+ input.value = expected;
+ input.type = types[j].type; // change state
+ assert_equals(input.value, "");
+ } else {
+ input.value = expected;
+ expected = input.value;
+
+ const previouslySelectable = (input.selectionStart !== null);
+
+ if (previouslySelectable) {
+ input.setSelectionRange(selectionStart, selectionEnd, selectionDirection);
+ }
+
+ input.type = types[j].type; // change state
+
+ var preSanitizeValue = expected;
+ // type[j] sanitization
+ if (types[j].sanitizer) {
+ expected = types[j].sanitizer(expected);
+ }
+
+ // type[j] defaultValue
+ if (expected === "" && types[j].defaultValue) {
+ expected = types[j].defaultValue;
+ }
+
+ assert_equals(input.value, expected, "input.value should be '" + expected + "' after change of state");
+
+ const nowSelectable = (input.selectionStart !== null);
+
+ if (nowSelectable) {
+ if (previouslySelectable) {
+ // Value might change after sanitization. The following checks are only valid when the value stays the same.
+ if (preSanitizeValue === expected) {
+ assert_equals(input.selectionStart, selectionStart, "selectionStart should be unchanged");
+ assert_equals(input.selectionEnd, selectionEnd, "selectionEnd should be unchanged");
+ assert_equals(input.selectionDirection, selectionDirection, "selectionDirection should be unchanged");
+ }
+ } else {
+ assert_equals(input.selectionStart, 0, "selectionStart should be 0");
+ assert_equals(input.selectionEnd, 0, "selectionEnd should be 0");
+ assert_equals(input.selectionDirection, noneDirectionResult,
+ `selectionDirection should be '{noneDirectionResult}'`);
+ }
+ }
+ }
+ }, "change state from " + types[i].type + " to " + types[j].type);
+ }
+ }
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/type-change-state.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/type-change-state.html
new file mode 100644
index 0000000000..5fb4cc9b56
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/type-change-state.html
@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Input element's type attribute changes state</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#the-input-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+
+ const INITIAL_VALUE = " foo\rbar ";
+
+ // Sanitize algorithm implementations only for values used in this test.
+ function sanitizeText(value) {
+ switch (value) {
+ case INITIAL_VALUE: return " foobar ";
+ case " foobar ": return value;
+ case "foobar": return value;
+ case "50": return value;
+ case "#000000": return value;
+ case "": return value;
+ default: throw new DOMException(`Internal Error: Should add support of "${value}"`, "NotSupportedError");
+ }
+ }
+ function sanitizeEmailOrUrl(value) {
+ switch (value) {
+ case INITIAL_VALUE: return "foobar";
+ case " foobar ": return "foobar";
+ case "foobar": return value;
+ case "50": return value;
+ case "#000000": return value;
+ case "": return value;
+ default: throw new DOMException(`Internal Error: Should add support of "${value}"`, "NotSupportedError");
+ }
+ }
+ function sanitizeTemporal(value) {
+ // We have no test cases using valid temporal values.
+ return "";
+ }
+ function sanitizeNumber(value) {
+ switch (value) {
+ case "50": return value;
+ default:
+ // We have no test cases using valid numbers other than "50".
+ return "";
+ }
+ }
+ function sanitizeRange(value) {
+ // We have no test cases using valid numbers other than "50".
+ return "50";
+ }
+ function sanitizeColor(value) {
+ // We have no test cases using valid colors other than "#000000".
+ return "#000000";
+ }
+ function browserSupportsInputTypeOf(inputType) {
+ var inputTest = document.createElement("input");
+ inputTest.type = inputType;
+ return (inputTest.type === inputType);
+ }
+
+
+ var types = [
+ { type: "hidden" },
+ { type: "text", sanitizer: sanitizeText },
+ { type: "search", sanitizer: sanitizeText },
+ { type: "tel", sanitizer: sanitizeText },
+ { type: "url", sanitizer: sanitizeEmailOrUrl },
+ { type: "email", sanitizer: sanitizeEmailOrUrl },
+ { type: "password", sanitizer: sanitizeText },
+ { type: "datetime-local", sanitizer: sanitizeTemporal },
+ { type: "date", sanitizer: sanitizeTemporal },
+ { type: "time", sanitizer: sanitizeTemporal },
+ { type: "number", sanitizer: sanitizeNumber },
+ { type: "range", sanitizer: sanitizeRange },
+ { type: "color", sanitizer: sanitizeColor },
+ { type: "checkbox", defaultValue: "on" },
+ { type: "radio", defaultValue: "on" },
+ { type: "file" },
+ { type: "submit" },
+ { type: "image" },
+ { type: "reset" },
+ { type: "button" }
+ ];
+
+ const selectionStart = 2;
+ const selectionEnd = 5;
+ const selectionDirection = "backward";
+
+ // Obtain selectionDirection after setting it to "none".
+ // Some platforms don't support "none" direction, and "forward" is returned
+ // in such platforms.
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#set-the-selection-direction
+ function testNoneDirection() {
+ const input = document.createElement("input");
+ input.selectionDirection = "none";
+ return input.selectionDirection;
+ }
+ const noneDirectionResult = testNoneDirection();
+
+ for (var i = 0; i < types.length; i++) {
+ for (var j = 0; j < types.length; j++) {
+ if (types[i] != types[j]) {
+ test(function() {
+ assert_implements(browserSupportsInputTypeOf(types[i].type), "Support for input type " + types[i].type + " is required for this test.");
+ assert_implements(browserSupportsInputTypeOf(types[j].type), "Support for input type " + types[j].type + " is required for this test.");
+ var input = document.createElement("input");
+ var expected = INITIAL_VALUE;
+ input.type = types[i].type;
+ if (types[i].type === "file") {
+ assert_throws_dom("INVALID_STATE_ERR", function() {
+ input.value = expected;
+ });
+ assert_equals(input.value, "");
+ } else if (types[j].type === "file") {
+ input.value = expected;
+ input.type = types[j].type; // change state
+ assert_equals(input.value, "");
+ } else {
+ input.value = expected;
+ expected = input.value;
+
+ const previouslySelectable = (input.selectionStart !== null);
+
+ if (previouslySelectable) {
+ input.setSelectionRange(selectionStart, selectionEnd, selectionDirection);
+ }
+
+ input.type = types[j].type; // change state
+
+ var preSanitizeValue = expected;
+ // type[j] sanitization
+ if (types[j].sanitizer) {
+ expected = types[j].sanitizer(expected);
+ }
+
+ // type[j] defaultValue
+ if (expected === "" && types[j].defaultValue) {
+ expected = types[j].defaultValue;
+ }
+
+ assert_equals(input.value, expected, "input.value should be '" + expected + "' after change of state");
+
+ const nowSelectable = (input.selectionStart !== null);
+
+ if (nowSelectable) {
+ if (previouslySelectable) {
+ // Value might change after sanitization. The following checks are only valid when the value stays the same.
+ if (preSanitizeValue === expected) {
+ assert_equals(input.selectionStart, selectionStart, "selectionStart should be unchanged");
+ assert_equals(input.selectionEnd, selectionEnd, "selectionEnd should be unchanged");
+ assert_equals(input.selectionDirection, selectionDirection, "selectionDirection should be unchanged");
+ }
+ } else {
+ assert_equals(input.selectionStart, 0, "selectionStart should be 0");
+ assert_equals(input.selectionEnd, 0, "selectionEnd should be 0");
+ assert_equals(input.selectionDirection, noneDirectionResult,
+ `selectionDirection should be '{noneDirectionResult}'`);
+ }
+ }
+ }
+ }, "change state from " + types[i].type + " to " + types[j].type);
+ }
+ }
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/url.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/url.html
new file mode 100644
index 0000000000..aafa0ced9d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/url.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Input url</title>
+ <link rel="author" title="Hyeonseok Shin" href="mailto:hyeonseok@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#url-state-%28type=url%29">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <h1>Input url</h1>
+ <div style="display: none">
+ <input type="url" id="type_support" />
+ <input type="url" id="set_value_LF" />
+ <input type="url" id="set_value_CR" />
+ <input type="url" id="set_value_CRLF" />
+ <input type="url" id="value_with_CRLF" value="a&#x000D;&#x000A;a" />
+ <input type="url" id="value_with_leading_trailing_white_space" value=" aa " />
+ <input type="url" id="value_with_leading_trailing_inner_white_space" value=" a a " />
+ </div>
+ <div id="log">
+ </div>
+
+ <script type="text/javascript">
+ test(function(){
+ var element = document.getElementById('type_support');
+ assert_equals(element.type, 'url');
+ }, 'url type supported on input element');
+
+ test(function(){
+ var element = document.getElementById('set_value_LF');
+ element.value = 'a\u000Aa';
+ assert_equals(element.value, 'aa');
+
+ element = document.getElementById('set_value_CR');
+ element.value = 'a\u000Da';
+ assert_equals(element.value, 'aa');
+
+ element = document.getElementById('set_value_CRLF');
+ element.value = 'a\u000D\u000Aa';
+ assert_equals(element.value, 'aa');
+ }, 'The value must not be set with "LF" (U+000A) or "CR" (U+000D)');
+
+ test(function(){
+ var element = document.getElementById('value_with_CRLF');
+ assert_equals(element.value, 'aa');
+ }, 'The value sanitization algorithm is as follows: Strip line breaks from the value');
+
+ test(function(){
+ var element = document.getElementById('value_with_leading_trailing_white_space');
+ assert_equals(element.value, 'aa');
+
+ element = document.getElementById('value_with_leading_trailing_inner_white_space');
+ assert_equals(element.value, 'a a');
+ }, 'The value sanitization algorithm is as follows: Strip leading and trailing whitespace from the value.');
+ </script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/valueMode-weekmonth.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/valueMode-weekmonth.html
new file mode 100644
index 0000000000..c4a241016b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/valueMode-weekmonth.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Input element value mode</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+// MODE DEFAULT
+test(function () {
+ var input = document.createElement("input");
+ input.type = "month";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "");
+}, "value IDL attribute of input type month without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "month";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "");
+}, "value IDL attribute of input type month with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "week";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "");
+}, "value IDL attribute of input type week without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "week";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "");
+}, "value IDL attribute of input type week with value attribute");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/valueMode.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/valueMode.html
new file mode 100644
index 0000000000..37f3a7bce9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/valueMode.html
@@ -0,0 +1,304 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Input element value mode</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+// MODE DEFAULT
+test(function () {
+ var input = document.createElement("input");
+ input.type = "hidden";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type hidden without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "hidden";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type hidden with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "submit";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type submit without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "submit";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type submit with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "image";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type image without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "image";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type image with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "reset";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type reset without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "reset";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type reset with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "button";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type button without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "button";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type button with value attribute");
+
+// MODE DEFAULT/ON
+test(function () {
+ var input = document.createElement("input");
+ input.type = "checkbox";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type checkbox without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "checkbox";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type checkbox with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "radio";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type radio without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "radio";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\r\r\n\n\0");
+}, "value IDL attribute of input type radio with value attribute");
+
+// MODE VALUE
+test(function () {
+ var input = document.createElement("input");
+ input.type = "text";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\0");
+}, "value IDL attribute of input type text without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "text";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\0");
+}, "value IDL attribute of input type text with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "search";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\0");
+}, "value IDL attribute of input type search without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "search";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\0");
+}, "value IDL attribute of input type search with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "tel";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\0");
+}, "value IDL attribute of input type tel without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "tel";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\0");
+}, "value IDL attribute of input type tel with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "url";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\0");
+}, "value IDL attribute of input type url without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "url";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\0");
+}, "value IDL attribute of input type url with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "email";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\0");
+}, "value IDL attribute of input type email without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "email";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\0");
+}, "value IDL attribute of input type email with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "password";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\0");
+}, "value IDL attribute of input type password without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "password";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "foo\0");
+}, "value IDL attribute of input type password with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "datetime-local";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "");
+}, "value IDL attribute of input type datetime-local without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "datetime-local";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "");
+}, "value IDL attribute of input type datetime-local with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "date";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "");
+}, "value IDL attribute of input type date without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "date";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "");
+}, "value IDL attribute of input type date with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "time";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "");
+}, "value IDL attribute of input type time without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "time";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "");
+}, "value IDL attribute of input type time with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "number";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "");
+}, "value IDL attribute of input type number without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "number";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "");
+}, "value IDL attribute of input type number with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "range";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "50");
+}, "value IDL attribute of input type range without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "range";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "50");
+}, "value IDL attribute of input type range with value attribute");
+
+test(function () {
+ var input = document.createElement("input");
+ input.type = "color";
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "#000000");
+}, "value IDL attribute of input type color without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "color";
+ input.setAttribute("value", "bar");
+ input.value = "foo\r\r\n\n\0";
+ assert_equals(input.value, "#000000");
+}, "value IDL attribute of input type color with value attribute");
+
+// MODE FILENAME
+test(function () {
+ var input = document.createElement("input");
+ input.type = "file";
+
+ for (const emptyValue of ["", null]) {
+ input.value = emptyValue;
+ assert_equals(input.value, "", `input.value is empty after assigning ${emptyValue}`);
+ }
+
+ for (const invalidValue of ["foo", 10, undefined]) {
+ assert_throws_dom("InvalidStateError", () => {
+ input.value = invalidValue;
+ });
+ assert_equals(input.value, "", `input.value is empty after assigning ${invalidValue}`);
+ }
+}, "value IDL attribute of input type file without value attribute");
+test(function() {
+ var input = document.createElement("input");
+ input.type = "file";
+ input.setAttribute("value", "bar");
+ assert_equals(input.value, "", "input.value is empty even with a value attribute");
+
+ input.value = "";
+ assert_equals(input.getAttribute("value"), "bar", "Setting input.value does not change the value attribute");
+}, "value IDL attribute of input type file with value attribute");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/week.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/week.html
new file mode 100644
index 0000000000..925acfdaf8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/week.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Form input type=week</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#weeks">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#week-state-(type=week)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ var weeks = [
+ {value: "", expected: "", testname: "empty value"},
+ {value: "2014-W52", expected: "2014-W52", testname: "Valid value: Value should be 2014-W52"},
+ {value: "2014-W53", expected: "", testname: "2014 has 52 weeks: Value should be empty"},
+ {value: "2015-W53", expected: "2015-W53", testname: "2015 has 53 weeks: Value should be 2015-W53"},
+ {value: "2014", expected: "", testname: "Invalid value: year only"},
+ {value: "2014W", expected: "", testname: "Invalid value: no week number"},
+ {value: "2014W52", expected: "", testname: "Invalid value: no '-' (U+002D)"},
+ {value: "-W52", expected: "", testname: "Invalid value: yearless week"},
+ {value: "2017-w52", expected: "", testname: "Invalid value: should be capital letter 'W'"},
+ {value: "2017-W52-", expected: "", testname: "Invalid value: incorrect with '-' at the end"},
+ {value: "2017-W52-12", expected: "", testname: "Invalid value: value should be two parts"},
+ {value: "W52", expected: "", testname: "Invalid value: yearless week and no '-' (U+002D)"},
+ {value: "2014-W03", attributes: { min: "2014-W02" }, expected: "2014-W03", testname: "Value >= min attribute"},
+ {value: "2014-W01", attributes: { min: "2014-W02" }, expected: "2014-W01", testname: "Value < min attribute"},
+ {value: "2014-W10", attributes: { max: "2014-W11" }, expected: "2014-W10", testname: "Value <= max attribute"},
+ {value: "2014-W12", attributes: { max: "2014-W11" }, expected: "2014-W12", testname: "Value > max attribute"}
+ ];
+ for (var i = 0; i < weeks.length; i++) {
+ var w = weeks[i];
+ test(function() {
+ var input = document.createElement("input");
+ input.type = "week";
+ input.value = w.value;
+ for(var attr in w.attributes) {
+ input[attr] = w.attributes[attr];
+ }
+ assert_equals(input.value, w.expected);
+ }, w.testname);
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-label-element/clicking-interactive-content.html b/testing/web-platform/tests/html/semantics/forms/the-label-element/clicking-interactive-content.html
new file mode 100644
index 0000000000..300d09cdda
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-label-element/clicking-interactive-content.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Label event handling when a descendant interactive content is clicked</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<label id=label></label>
+<template id=interactive-content>
+ <a href="about:blank" onclick="event.preventDefault()"></a>
+ <audio controls></audio>
+ <button></button>
+ <details></details>
+ <embed>
+ <iframe></iframe>
+ <img usemap="">
+ <input>
+ <label>label</label>
+ <select></select>
+ <textarea></textarea>
+ <video controls></video>
+</template>
+
+<script>
+"use strict";
+
+const interactiveContent = document.getElementById("interactive-content");
+const interactiveElements = Array.from(interactiveContent.content.children);
+const label = document.getElementById("label");
+
+for (const srcInteractiveElement of interactiveElements) {
+ test(t => {
+ t.add_cleanup(() => {
+ label.innerHTML = "";
+ });
+
+ const interactiveElement = srcInteractiveElement.cloneNode();
+ label.appendChild(interactiveElement);
+
+ let clicked = 0;
+ interactiveElement.addEventListener("click", () => {
+ clicked++;
+ });
+ interactiveElement.click();
+ assert_equals(clicked, 1, "clicking interactive content");
+
+ clicked = 0;
+ const span = document.createElement("span");
+ interactiveElement.appendChild(span);
+ span.click();
+ assert_equals(clicked, 1, "clicking descendant of interactive content");
+ }, `interactive content ${srcInteractiveElement.outerHTML} as first child of <label>`);
+
+ test(t => {
+ t.add_cleanup(() => {
+ label.innerHTML = "";
+ });
+
+ const interactiveElement = srcInteractiveElement.cloneNode();
+ const div = document.createElement("div");
+ div.appendChild(interactiveElement);
+ label.appendChild(div);
+
+ let clicked = 0;
+ interactiveElement.addEventListener("click", () => {
+ clicked++;
+ });
+ interactiveElement.click();
+ assert_equals(clicked, 1, "clicking nested interactive content");
+
+ clicked = 0;
+ const span = document.createElement("span");
+ interactiveElement.appendChild(span);
+ span.click();
+ assert_equals(clicked, 1, "clicking descendant of nested interactive content");
+ }, `interactive content ${srcInteractiveElement.outerHTML} deeply nested under <label>`);
+
+ test(t => {
+ t.add_cleanup(() => {
+ label.innerHTML = "";
+ });
+
+ const button = document.createElement("button");
+ label.appendChild(button);
+
+ const interactiveElement = srcInteractiveElement.cloneNode();
+ label.appendChild(interactiveElement);
+
+ let buttonClicked = 0;
+ button.addEventListener("click", () => {
+ buttonClicked++;
+ });
+
+ let clicked = 0;
+ interactiveElement.addEventListener("click", () => {
+ clicked++;
+ });
+ interactiveElement.click();
+ assert_equals(clicked, 1, "clicking nested interactive content");
+ assert_equals(buttonClicked, 0, "clicking nested interactive content should not click button");
+
+ clicked = 0;
+ const span = document.createElement("span");
+ interactiveElement.appendChild(span);
+ span.click();
+ assert_equals(clicked, 1, "clicking descendant of nested interactive content");
+ assert_equals(buttonClicked, 0, "clicking descendant of nested interactive content should not click button");
+ }, `interactive content ${srcInteractiveElement.outerHTML} as second child under <label>`);
+}
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-label-element/clicking-noninteractive-labelable-content.html b/testing/web-platform/tests/html/semantics/forms/the-label-element/clicking-noninteractive-labelable-content.html
new file mode 100644
index 0000000000..5563ef1e3c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-label-element/clicking-noninteractive-labelable-content.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Label event handling when a descendant labelable but not interactive element is clicked</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<label id=label></label>
+<template id=labelable-not-interactive-content>
+ <meter></meter>
+ <output></output>
+ <progress></progress>
+</template>
+
+<script>
+"use strict";
+
+const template = document.getElementById("labelable-not-interactive-content");
+const labelableNotInteractiveElements = Array.from(template.content.children);
+const label = document.getElementById("label");
+
+// This part may be subject to platform-dependent operations in the spec, so we
+// only check for obvious errors. (Clicking once should register at least one
+// click, but less than 30 clicks.) See
+// https://github.com/whatwg/html/issues/5415 for possibly tightening this up.
+function checkClickCount(clicked, description) {
+ assert_greater_than(clicked, 0, description);
+ assert_less_than(clicked, 30, description);
+}
+
+for (const srcElement of labelableNotInteractiveElements) {
+ test(t => {
+ t.add_cleanup(() => {
+ label.innerHTML = "";
+ });
+
+ const element = srcElement.cloneNode();
+ label.appendChild(element);
+
+ let clicked = 0;
+ element.addEventListener("click", () => {
+ clicked++;
+ });
+ element.click();
+ checkClickCount(clicked, "clicking labelable content");
+
+ clicked = 0;
+ const span = document.createElement("span");
+ element.appendChild(span);
+ span.click();
+ checkClickCount(clicked, "clicking descendant of labelable content");
+ }, `labelable element ${srcElement.outerHTML} as first child of <label>`);
+
+ test(t => {
+ t.add_cleanup(() => {
+ label.innerHTML = "";
+ });
+
+ const element = srcElement.cloneNode();
+ const div = document.createElement("div");
+ div.appendChild(element);
+ label.appendChild(div);
+
+ let clicked = 0;
+ element.addEventListener("click", () => {
+ clicked++;
+ });
+ element.click();
+ checkClickCount(clicked, "clicking nested labelable content");
+
+ clicked = 0;
+ const span = document.createElement("span");
+ element.appendChild(span);
+ span.click();
+ checkClickCount(clicked, "clicking descendant of nested labelable content");
+ }, `labelable element ${srcElement.outerHTML} deeply nested under <label>`);
+
+ test(t => {
+ t.add_cleanup(() => {
+ label.innerHTML = "";
+ });
+
+ const button = document.createElement("button");
+ label.appendChild(button);
+
+ const element = srcElement.cloneNode();
+ label.appendChild(element);
+
+ let buttonClicked = 0;
+ button.addEventListener("click", () => {
+ buttonClicked++;
+ });
+
+ let clicked = 0;
+ element.addEventListener("click", () => {
+ clicked++;
+ });
+ element.click();
+ assert_equals(clicked, 1, "clicking nested labelable content");
+ assert_equals(buttonClicked, 1, "clicking nested labelable content should click button");
+
+ buttonClicked = 0;
+ clicked = 0;
+ const span = document.createElement("span");
+ element.appendChild(span);
+ span.click();
+ assert_equals(clicked, 1, "clicking descendant of nested labelable content");
+ assert_equals(buttonClicked, 1, "clicking descendant of nested labelable content should not click button");
+ }, `labelable element ${srcElement.outerHTML} as second child under <label>`);
+}
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-label-element/clicking-noninteractive-unlabelable-content.html b/testing/web-platform/tests/html/semantics/forms/the-label-element/clicking-noninteractive-unlabelable-content.html
new file mode 100644
index 0000000000..285cd8c041
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-label-element/clicking-noninteractive-unlabelable-content.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Label event handling when a descendant noninteractive and unlabelable content is clicked</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<label id=label></label>
+<template id=noninteractive-unlabelable-content>
+ <div></div>
+ <svg></svg>
+
+ <!-- These are "almost interactive": they could become interactive with the
+ addition/removal of a non-tabindex attribute. -->
+ <a></a>
+ <audio></audio>
+ <img>
+ <input type=hidden>
+ <video></video>
+
+ <!-- These are considered interactive content for the purpose of <label> in a
+ previous version of the HTML Standard, but no longer. -->
+ <a tabindex=""></a>
+ <audio tabindex=""></audio>
+ <div tabindex=""></div>
+ <img tabindex="">
+ <input type=hidden tabindex="">
+ <object></object>
+ <object tabindex=""></object>
+ <object usemap=""></object>
+ <video tabindex=""></video>
+</template>
+
+<script>
+"use strict";
+
+const template = document.getElementById("noninteractive-unlabelable-content");
+{
+ const details = document.createElementNS("http://www.w3.org/2000/svg", "details");
+ template.content.appendChild(details);
+}
+
+const elements = Array.from(template.content.children);
+const label = document.getElementById("label");
+
+for (const srcElement of elements) {
+ test(t => {
+ t.add_cleanup(() => {
+ label.innerHTML = "";
+ });
+
+ const element = srcElement.cloneNode();
+ label.appendChild(element);
+
+ let clicked = 0;
+ element.addEventListener("click", () => {
+ clicked++;
+ });
+ element.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+ assert_equals(clicked, 1, "clicking interactive content");
+
+ clicked = 0;
+ const span = document.createElement("span");
+ element.appendChild(span);
+ span.click();
+ assert_equals(clicked, 1, "clicking descendant of interactive content");
+ }, `noninteractive unlabelable content ${srcElement.outerHTML} as first child of <label>`);
+
+ test(t => {
+ t.add_cleanup(() => {
+ label.innerHTML = "";
+ });
+
+ const element = srcElement.cloneNode();
+ const div = document.createElement("div");
+ div.appendChild(element);
+ label.appendChild(div);
+
+ let clicked = 0;
+ element.addEventListener("click", () => {
+ clicked++;
+ });
+ element.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+ assert_equals(clicked, 1, "clicking nested interactive content");
+
+ clicked = 0;
+ const span = document.createElement("span");
+ element.appendChild(span);
+ span.click();
+ assert_equals(clicked, 1, "clicking descendant of nested interactive content");
+ }, `noninteractive unlabelable content ${srcElement.outerHTML} deeply nested under <label>`);
+
+ test(t => {
+ t.add_cleanup(() => {
+ label.innerHTML = "";
+ });
+
+ const button = document.createElement("button");
+ label.appendChild(button);
+
+ const element = srcElement.cloneNode();
+ label.appendChild(element);
+
+ let buttonClicked = 0;
+ button.addEventListener("click", () => {
+ buttonClicked++;
+ });
+
+ let clicked = 0;
+ element.addEventListener("click", () => {
+ clicked++;
+ });
+ element.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+ assert_equals(clicked, 1, "clicking noninteractive unlabelable content");
+ assert_equals(buttonClicked, 1, "clicking noninteractive unlabelable content should click button");
+
+ buttonClicked = 0;
+ clicked = 0;
+ const span = document.createElement("span");
+ element.appendChild(span);
+ span.click();
+ assert_equals(clicked, 1, "clicking descendant of nested noninteractive unlabelable content");
+ assert_equals(
+ buttonClicked, 1,
+ "clicking descendant of nested noninteractive unlabelable content should click button"
+ );
+ }, `noninteractive unlabelable content ${srcElement.outerHTML} as second child under <label>`);
+}
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-label-element/forward-focus-to-associated-element.html b/testing/web-platform/tests/html/semantics/forms/the-label-element/forward-focus-to-associated-element.html
new file mode 100644
index 0000000000..86e3f652af
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-label-element/forward-focus-to-associated-element.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<title>label element focus forwarding via "for" attribute or nested labelable element</title>
+<link rel="author" title="yaycmyk" href="mailto:evan@yaycmyk.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#the-label-element:the-label-element-10">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form id="test">
+ <input id="input-a" type="checkbox">
+ <label id="label-a" for="input-a">a</label>
+
+ <label id="label-b">
+ <input id="input-b" type="checkbox" /> b
+ </label>
+
+ <label id="label-c" tabindex="0">
+ <input id="input-c" type="checkbox" /> c
+ </label>
+
+ <label id="label-d" tabindex="-1">
+ <input id="input-d" type="checkbox" /> d
+ </label>
+
+ <label id="label-e" tabindex="">
+ <input id="input-e" type="checkbox" /> e
+ </label>
+
+ <input id="input-f" type="checkbox">
+ <label id="label-f" for="input-f" tabindex="0" style="display:none">f</label>
+</form>
+<script>
+ "use strict";
+
+ async_test(t => {
+ const label = document.getElementById("label-a");
+ const input = document.getElementById("input-a");
+
+ input.addEventListener("focus", t.step_func_done());
+ label.addEventListener("focus", t.unreached_func("Label should not receive focus"));
+
+ label.focus();
+
+ }, "focusing a label with for attribute should forward focus to the associated element");
+
+ async_test(t => {
+ const label = document.getElementById("label-b");
+ const input = document.getElementById("input-b");
+
+ input.addEventListener("focus", t.step_func_done());
+ label.addEventListener("focus", t.unreached_func("Label should not receive focus"));
+
+ label.focus();
+
+ }, "focusing a label without for attribute should fowrad focus to the first labelable child");
+
+ async_test(t => {
+ const label = document.getElementById("label-c");
+ const input = document.getElementById("input-c");
+
+ input.addEventListener("focus", t.unreached_func("Input should not receive focus"));
+ label.addEventListener("focus", t.step_func_done());
+
+ label.focus();
+
+ }, "focusing a label with tabindex should not forward focus to the labelable element");
+
+ async_test(t => {
+ const label = document.getElementById("label-d");
+ const input = document.getElementById("input-d");
+
+ input.addEventListener("focus", t.unreached_func("Input should not receive focus"));
+ label.addEventListener("focus", t.step_func_done());
+
+ label.focus();
+
+ }, "focusing a label with negative tabindex should not forward focus to the labelable element");
+
+ async_test(t => {
+ const label = document.getElementById("label-e");
+ const input = document.getElementById("input-e");
+
+ label.addEventListener("focus", t.unreached_func("Label should not receive focus"));
+ input.addEventListener("focus", t.step_func_done());
+
+ label.focus();
+
+ }, "focusing a label with empty tabindex should forward focus to the labelable element");
+
+ async_test(t => {
+ const label = document.getElementById("label-f");
+ const input = document.getElementById("input-f");
+
+ label.addEventListener("focus", t.unreached_func("Label should not receive focus"));
+ input.addEventListener("focus", t.step_func_done());
+
+ label.focus();
+
+ }, "focusing a hidden label with tabindex should forward focus to the labelable element");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-label-element/iframe-label-attributes.html b/testing/web-platform/tests/html/semantics/forms/the-label-element/iframe-label-attributes.html
new file mode 100644
index 0000000000..3f08a29094
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-label-element/iframe-label-attributes.html
@@ -0,0 +1,8 @@
+<html>
+ <body>
+ <label>
+ <div id="div1"></div>
+ </label>
+ <label for="test13"></label>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-label-element/label-attributes.sub.html b/testing/web-platform/tests/html/semantics/forms/the-label-element/label-attributes.sub.html
new file mode 100644
index 0000000000..3c8591c7ee
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-label-element/label-attributes.sub.html
@@ -0,0 +1,339 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: The label element</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form id="fm" style="display:none">
+ <label id="lbl0" for="test0"></label>
+ <b id="test0"></b>
+
+ <input id="test1"></input>
+
+ <label id="lbl1">
+ <a id="test2"></a>
+ <div><input id="test3"></div>
+ <input id="test4">
+ </label>
+
+ <label id="lbl2" for="testx">
+ <input id="test5">
+ </label>
+
+ <label id="lbl3" for="test6">
+ <b id="test6"></b>
+ <input id="test6" class="class1">
+ </label>
+
+ <label id="lbl4" for="">
+ <input id="" class="class2">
+ </label>
+
+ <label id="lbl5" for="test7"></label>
+ <input id="test7">
+
+ <label id="lbl7">
+ <label id="lbl8">
+ <div id="div1">
+ <input id="test8">
+ </div>
+ </label>
+ </label>
+ <div id="div2"></div>
+
+ <label id="lbl9">
+ <label id="lbl10" for="test10">
+ <div id="div3">
+ <input id="test9">
+ </div>
+ </label>
+ </label>
+ <div id="div4"><input id="test10"></div>
+
+ <label id="lbl11">
+ <object id="obj">
+ <input id="test11">
+ <input id="test12">
+ </object>
+ </label>
+ <label id="lbl12" for="test12"><div id="div5"></div></label>
+
+ <label id="lbl13">
+ <p id="p1">
+ <input id="test13">
+ </p>
+ </label>
+
+ <div id="div6">
+ <div id="div7">
+ <label id="lbl14">
+ <label id="lbl15" for="test15">
+ <input id="test14">
+ </label>
+ </label>
+ </div>
+ </div>
+ <input id="test15">
+</form>
+
+<label id="lbl6" for="test7"></label>
+<div id="content" style="display: none">
+<script>
+
+ //control attribute
+ test(function () {
+ assert_not_equals(document.getElementById("lbl0").control, document.getElementById("test0"),
+ "An element that's not a labelable element can't be a label element's labeled control.");
+ assert_equals(document.getElementById("lbl0").control, null,
+ "A label element whose 'for' attribute doesn't reference any labelable element shouldn't have any labeled control.");
+ }, "A label element with a 'for' attribute should only be associated with a labelable element.");
+
+ test(function () {
+ var label = document.createElement("label");
+ label.htmlFor = "test1";
+ assert_not_equals(label.control, document.getElementById("test1"),
+ "A label element not in a document should not label an element in a document.");
+ document.body.appendChild(label);
+ assert_equals(label.control, document.getElementById("test1"));
+ label.remove();
+ }, "A label element not in a document can not label any element in the document.");
+
+ test(function () {
+ var labels = document.getElementById("test3").labels;
+ assert_equals(document.getElementById("lbl1").control, document.getElementById("test3"),
+ "The first labelable descendant of a label element should be its labeled control.");
+
+ var input = document.createElement("input");
+ document.getElementById("lbl1").insertBefore(input, document.getElementById("test2"));
+ assert_equals(document.getElementById("lbl1").control, input,
+ "The first labelable descendant of a label element in tree order should be its labeled control.");
+ assert_equals(input.labels.length, 1,
+ "The form control has an ancestor with no explicit associated label, and is the first labelable descendant.");
+ assert_equals(labels.length, 0,
+ "The number of labels should be 0 if it's not the first labelable descendant of a label element.");
+ input.remove();
+ }, "The labeled control for a label element that has no 'for' attribute is the first labelable element which is a descendant of that label element.");
+
+ test(function () {
+ assert_equals(document.getElementById("lbl2").control, null,
+ "The label's 'control' property should return null if its 'for' attribute points to an inexistent element.");
+ }, "The 'for' attribute points to an inexistent id.");
+
+ test(function () {
+ assert_equals(document.getElementById("lbl3").control, null, "The label should have no control associated.");
+ assert_equals(document.querySelector(".class1").labels.length, 0);
+ }, "A non-control follows by a control with same ID.");
+
+ test(function () {
+ assert_equals(document.getElementById("lbl4").control, null,
+ "A label element with an empty 'for' attribute should not associate with anything.");
+ }, "The 'for' attribute is an empty string.");
+
+ //labels attribute
+ test(function () {
+ var labels = document.getElementById("test7").labels;
+ assert_true(labels instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(labels.length, 2,
+ "The number of labels associated with a form control should be the number of label elements for which it is a labeled control.");
+ assert_array_equals(labels, [document.getElementById("lbl5"), document.getElementById("lbl6")],
+ "The labels for a form control should be returned in tree order.");
+
+ var newLabel = document.createElement("label");
+ newLabel.htmlFor = "test7";
+ document.getElementById("fm").insertBefore(newLabel, document.getElementById("lbl0"));
+ assert_array_equals(document.getElementById("test7").labels, [newLabel, document.getElementById("lbl5"), document.getElementById("lbl6")],
+ "The labels for a form control should be returned in tree order.");
+ newLabel.remove();
+ }, "A form control has multiple labels.");
+
+ test(function () {
+ var labels = document.getElementById("test8").labels;
+ assert_true(labels instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(labels.length, 2,
+ "The form control has two ancestors with no explicit associated label, and is the first labelable descendant.");
+ assert_array_equals(labels, [document.getElementById("lbl7"), document.getElementById("lbl8")],
+ "The labels for a form control should be returned in tree order.");
+
+ document.getElementById('div2').insertBefore(document.getElementById('div1'), document.getElementById('div2').firstChild);
+ assert_equals(labels.length, 0,
+ "The number of labels should be 0 after the labelable element is moved to outside of nested associated labels.");
+ }, "A labelable element is moved to outside of nested associated labels.");
+
+ test(function () {
+ var labels1 = document.getElementById("test9").labels;
+ var labels2 = document.getElementById("test10").labels;
+ assert_true(labels1 instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_true(labels2 instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(labels1.length, 1,
+ "The form control has an ancestor with no explicit associated label, and is the first labelable descendant.");
+ assert_equals(labels2.length, 1,
+ "The number of labels associated with a form control should be the number of label elements for which it is a labeled control.");
+ assert_array_equals(labels1, [document.getElementById("lbl9")],
+ "The labels for a form control should be returned in tree order.");
+ assert_array_equals(labels2, [document.getElementById("lbl10")],
+ "The labels for a form control should be returned in tree order.");
+ document.getElementById('div3').insertBefore(document.getElementById('div4'), document.getElementById('div3').firstChild);
+ assert_equals(labels1.length, 0,
+ "The number of labels should be 0 if it's not the first labelable descendant of a label element.");
+ assert_equals(labels2.length, 2,
+ "The form control has an ancestor with an explicit associated label, and is the first labelable descendant.");
+ }, "A labelable element is moved to inside of nested associated labels.");
+
+ test(function () {
+ var labels1 = document.getElementById("test11").labels;
+ var labels2 = document.getElementById("test12").labels;
+ assert_true(labels1 instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_true(labels2 instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(labels1.length, 1,
+ "The form control has an ancestor with no explicit associated label, and it is the first labelable descendant.");
+ assert_equals(labels2.length, 1,
+ "The number of labels should be 1 since there is a label with a 'for' attribute associated with this labelable element.");
+ assert_array_equals(labels1, [document.getElementById("lbl11")],
+ "The labels for a form control should be returned in tree order.");
+ assert_array_equals(labels2, [document.getElementById("lbl12")],
+ "The labels for a form control should be returned in tree order.");
+ document.getElementById('div5').appendChild(document.getElementById('obj'));
+ assert_equals(labels1.length, 0,
+ "The number of labels should be 0 after the labelable element is moved to outside of associated label.");
+ assert_equals(labels2.length, 1,
+ "The number of labels should be 1 after the labelable element is moved to outside of associated label.");
+ }, "A labelable element which is a descendant of non-labelable element is moved to outside of associated label.");
+
+ async_test(function () {
+ var labels = document.getElementById("test13").labels;
+ assert_true(labels instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(labels.length, 1,
+ "The form control has an ancestor with no explicit associated label, and is the first labelable descendant.");
+ assert_array_equals(labels, [document.getElementById("lbl13")],
+ "The labels for a form control should be returned in tree order.");
+ let iframe = document.createElement('iframe');
+
+ iframe.onload = this.step_func_done(() => {
+ iframe.contentWindow.document.getElementById("div1").appendChild(document.getElementById("p1"));
+ assert_equals(labels.length, 2,
+ "The number of labels should be 2 after the labelable element is moved to iframe.");
+ });
+
+ iframe.setAttribute('src', 'http://{{domains[]}}:{{ports[http][0]}}/html/semantics/forms/the-label-element/iframe-label-attributes.html');
+ document.body.appendChild(iframe);
+ }, "A labelable element is moved to iframe.");
+
+ test(function () {
+ var test14 = document.getElementById("test14");
+ var labels1 = test14.labels;
+ var labels2 = document.getElementById("test15").labels;
+ assert_true(labels1 instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(labels1.length, 1,
+ "The form control has an ancestor with no explicit associated label, and is the first labelable descendant.");
+ assert_equals(labels2.length, 1,
+ "The number of labels associated with a form control should be the number of label elements for which it is a labeled control.");
+ assert_array_equals(labels1, [document.getElementById("lbl14")],
+ "The labels for a form control should be returned in tree order.");
+ assert_array_equals(labels2, [document.getElementById("lbl15")],
+ "The labels for a form control should be returned in tree order.");
+
+ document.getElementById('div6').removeChild(document.getElementById('div7'));
+ assert_equals(labels1.length, 1,
+ "The number of labels should be 1 after the labelable element is removed but label element is still in the same tree.");
+ assert_equals(labels2.length, 0,
+ "The number of labels should be 0 since there is no label with a 'for' attribute associated with this labelable element.");
+ test14.remove();
+ assert_equals(labels1.length, 0,
+ "The number of labels should be 0 after the labelable element is removed.");
+ }, "A div element which contains labelable element is removed.");
+
+ test(function () {
+ // <label><input id="test16"><label for="test16"></label></label>
+ var label1 = document.createElement('label');
+ label1.innerHTML = "<input id='test16'>";
+ var label2 = document.createElement('label');
+ label2.htmlFor = "test16";
+ label1.appendChild(label2);
+
+ var input = label1.firstChild;
+ var labels = input.labels;
+
+ assert_equals(labels.length, 2,
+ "The number of labels associated with a form control should be the number of label elements for which it is a labeled control.");
+ assert_true(labels instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(label1.control, input, "The first labelable descendant of a label element should be its labeled control.");
+ assert_equals(label2.control, input, "The labeled cotrol should be associated with the control whose ID is equal to the value of the 'for' attribute.");
+ }, "A labelable element not in a document can label element in the same tree.");
+
+ test(function () {
+ var root1 = document.getElementById('content').attachShadow({mode: 'open'});
+ assert_true(root1 instanceof DocumentFragment,
+ "ShadowRoot should be an instance of DocumentFragment.");
+ // <label><input id="shadow1"/></label><div id="div1"></div>
+ var label1 = document.createElement('label');
+ var input1 = document.createElement('input');
+ input1.setAttribute("id", "shadow1");
+ label1.appendChild(input1);
+ root1.appendChild(label1);
+
+ var div1 = document.createElement('div');
+ label1.appendChild(div1);
+ // <label for="shadow2"></label><input id="shadow2"/>
+ var root2 = div1.attachShadow({mode: 'open'});
+
+ assert_true(root2 instanceof DocumentFragment,
+ "ShadowRoot should be an instance of DocumentFragment.");
+ var label2 = document.createElement('label');
+ label2.setAttribute("for", "shadow2");
+
+ var input2 = document.createElement('input');
+ input2.setAttribute("id", "shadow2");
+ root2.appendChild(label2);
+ root2.appendChild(input2);
+
+ assert_equals(root1.getElementById("shadow1").labels.length, 1,
+ "The form control has an ancestor with no explicit associated label, and it is the first labelable descendant.");
+ assert_equals(root2.getElementById("shadow2").labels.length, 1,
+ "The number of labels should be 1 since there is a label with a 'for' attribute associated with this labelable element.");
+ }, "A labelable element inside the shadow DOM.");
+
+ test(function () {
+ var labels = document.getElementById("test3").labels;
+ assert_true(labels instanceof NodeList, "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(labels.length, 1, "The form control has an ancestor with no explicit associated label, and is the first labelable descendant.");
+ }, "A form control has an implicit label.");
+
+ test(function () {
+ var labels = document.getElementById("test4").labels;
+ assert_true(labels instanceof NodeList, "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(labels.length, 0, "The form control has an ancestor with no explicit associated label, but is *not* the first labelable descendant");
+ }, "A form control has no label 1.");
+
+ test(function () {
+ assert_equals(document.getElementById("test5").labels.length, 0,
+ "The number of labels should be 0 if the form control has an ancestor label element that the for attribute points to another control.");
+ assert_equals(document.getElementById("lbl2").control, null,
+ "The labeled cotrol should be associated with the control whose ID is equal to the value of the 'for' attribute.");
+ }, "A form control has no label 2.");
+
+ // form attribute
+ test(function () {
+ assert_equals(document.getElementById("lbl0").form, null,
+ "The 'form' property for a label should return null if label.control is null.");
+ }, "A label in a form without a control");
+
+ test(function () {
+ assert_equals(document.getElementById("lbl6").form, document.getElementById("fm"),
+ "The 'form' property for a label should return label.control.form.");
+ }, "A label outside a form with a control inside the form");
+
+ // htmlFor attribute
+ test(function () {
+ assert_equals(document.getElementById("lbl2").htmlFor, "testx");
+ }, "A label's htmlFor attribute must reflect the for content attribute");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-label-element/label-inside-anchor.html b/testing/web-platform/tests/html/semantics/forms/the-label-element/label-inside-anchor.html
new file mode 100644
index 0000000000..316441c5f2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-label-element/label-inside-anchor.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>label element: clicking on label containing inline element placed inside &lt;a&gt; </title>
+<link rel="author" title="Yu Han" href="mailto:yuzhehan@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#the-label-element">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-a-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<a href="javascript:void(0)" target="_blank">
+ <label for="peas"><span id="text">peas?</span></label>
+ <input type="checkbox" name="peas" id="peas">
+</a>
+<script>
+ const text = document.getElementById('text'),
+ peas_cb = document.getElementById('peas');
+
+ t1 = async_test("click on inline element inside a label that's placed inside a anchor should trigger default label behavior");
+
+ peas_cb.onchange = t1.step_func_done(function(e) {
+ assert_true(peas_cb.checked, "checkbox is checked");
+ });
+
+ t1.step(function() {
+ text.click();
+ });
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-label-element/labelable-elements.html b/testing/web-platform/tests/html/semantics/forms/the-label-element/labelable-elements.html
new file mode 100644
index 0000000000..7943aa2be3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-label-element/labelable-elements.html
@@ -0,0 +1,174 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: labelable elements</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form style="display:none">
+ <output id="testoutput"></output>
+ <label id="lbl0" for="testoutput"></label>
+ <progress id="testprogress"></progress>
+ <label id="lbl1" for="testprogress"></label>
+ <select id="testselect"></select>
+ <label id="lbl2" for="testselect"></label>
+ <textarea id="testarea"></textarea>
+ <label id="lbl3" for="testarea"></label>
+ <button id="testButton"></button>
+ <label id="lbl4" for="testButton"></label>
+ <input type="hidden" id="testHidden">
+ <label id="lbl5" for="testHidden"></label>
+ <input type="radio" id="testRadio">
+ <label id="lbl6" for="testRadio"></label>
+ <keygen id="testkeygen">
+ <label id="lbl7" for="testkeygen"></label>
+ <meter id="testmeter"></meter>
+ <label id="lbl8" for="testmeter"></label>
+
+ <fieldset id="testfieldset"></fieldset>
+ <label id="lbl9" for="testfieldset"></label>
+ <label id="testlabel"></label>
+ <label id="lbl10" for="testlabel"></label>
+ <object id="testobject"></object>
+ <label id="lbl11" for="testobject"></label>
+ <img id="testimg">
+ <label id="lbl12" for="testimg"></label>
+</form>
+
+<script>
+function testLabelsAttr(formElementId, labelElementId) {
+ var elem = document.getElementById(formElementId);
+ if (labelElementId) {
+ assert_equals(elem.labels.length, 1);
+ assert_equals(elem.labels[0].id, labelElementId);
+ } else {
+ assert_equals(elem.labels.length, 0);
+ }
+}
+
+test(function() {
+ assert_equals(document.getElementById("lbl0").control.id, "testoutput", "An output element should be labelable.");
+}, "Check if the output element is a labelable element");
+
+test(function() {
+ testLabelsAttr("testoutput", "lbl0");
+}, "Check if the output element can access 'labels'");
+
+test(function() {
+ assert_equals(document.getElementById("lbl1").control.id, "testprogress", "A progress element should be labelable.");
+}, "Check if the progress element is a labelable element");
+
+test(function() {
+ testLabelsAttr("testprogress", "lbl1");
+}, "Check if the progress element can access 'labels'");
+
+test(function() {
+ assert_equals(document.getElementById("lbl2").control.id, "testselect", "A select element should be labelable.");
+}, "Check if the select element is a labelable element");
+
+test(function() {
+ testLabelsAttr("testselect", "lbl2");
+}, "Check if the select element can access 'labels'");
+
+test(function() {
+ assert_equals(document.getElementById("lbl3").control.id, "testarea", "A textarea element should be labelable.");
+}, "Check if the textarea element is a labelable form-element");
+
+test(function() {
+ testLabelsAttr("testarea", "lbl3");
+}, "Check if the textarea element can access 'labels'");
+
+test(function() {
+ assert_equals(document.getElementById("lbl4").control.id, "testButton", "A button element should be labelable.");
+}, "Check if the button element is a labelable element");
+
+test(function() {
+ testLabelsAttr("testButton", "lbl4");
+}, "Check if the button element can access 'labels'");
+
+test(function() {
+ assert_equals(document.getElementById("lbl5").control, null, "An input element in hidden state should not be labelable.");
+}, "Check if the hidden input element is not a labelable element.");
+
+test(function() {
+ var hiddenInput = document.getElementById("testHidden");
+ assert_equals(hiddenInput.labels, null, "input[type=hidden] must have null .labels");
+
+ this.add_cleanup(function () {
+ hiddenInput.type = "hidden";
+ });
+
+ hiddenInput.type = "text";
+ testLabelsAttr("testHidden", "lbl5");
+ var labels = hiddenInput.labels;
+
+ hiddenInput.type = "hidden";
+ assert_equals(labels.length, 0, "Retained .labels NodeList should be empty after input type changed to hidden");
+ assert_equals(hiddenInput.labels, null, ".labels NodeList should be null after input type changed to hidden");
+
+ hiddenInput.type = "checkbox";
+ assert_equals(labels, hiddenInput.labels, ".labels property must return the [SameObject] after input type is toggled back from 'hidden'");
+ assert_equals(hiddenInput.labels.length, 1, ".labels NodeList should contain the input after the input type is changed from 'hidden' to 'checkbox'");
+}, "Check if the hidden input element has null 'labels'");
+
+test(function() {
+ assert_equals(document.getElementById("lbl6").control.id, "testRadio", "An input element in radio state should be labelable.");
+}, "Check if the input element in radio state is a labelable element");
+
+test(function() {
+ testLabelsAttr("testRadio", "lbl6");
+}, "Check if the input element in radio state can access 'labels'");
+
+test(function() {
+ assert_not_equals(document.getElementById("lbl7").control, document.getElementById("testkeygen"));
+ assert_equals(document.getElementById("lbl7").control, null, "A keygen element should not be labelable.");
+}, "Check if the keygen element is not a labelable element");
+
+test(function() {
+ assert_equals(document.getElementById("testkeygen").labels, undefined);
+}, "Check if the keygen element can access 'labels'");
+
+test(function() {
+ assert_equals(document.getElementById("lbl8").control.id, "testmeter", "A meter element should be labelable.");
+}, "Check if the meter element is a labelable element");
+
+test(function() {
+ testLabelsAttr("testmeter", "lbl8");
+}, "Check if the meter element can access 'labels'");
+
+test(function() {
+ assert_not_equals(document.getElementById("lbl9").control, document.getElementById("testfieldset"));
+ assert_equals(document.getElementById("lbl9").control, null, "A fieldset element should not be labelable.");
+}, "Check if the fieldset element is not a labelable element");
+
+test(function() {
+ assert_equals(document.getElementById("testfieldset").labels, undefined);
+}, "Check if the fieldset element can access 'labels'");
+
+test(function() {
+ assert_not_equals(document.getElementById("lbl9").control, document.getElementById("testlabel"));
+ assert_equals(document.getElementById("lbl10").control, null, "A label element should not be labelable.");
+}, "Check if the label element is not a labelable element");
+
+test(function() {
+ assert_equals(document.getElementById("testlabel").labels, undefined);
+}, "Check if the label element can access 'labels'");
+
+test(function() {
+ assert_not_equals(document.getElementById("lbl9").control, document.getElementById("testobject"));
+ assert_equals(document.getElementById("lbl11").control, null, "An object element should not be labelable.");
+}, "Check if the object element is not a labelable element");
+
+test(function() {
+ assert_equals(document.getElementById("testobject").labels, undefined);
+}, "Check if the object element can access 'labels'");
+
+test(function() {
+ assert_not_equals(document.getElementById("lbl9").control, document.getElementById("testimg"));
+ assert_equals(document.getElementById("lbl12").control, null, "An img element should not be labelable.");
+}, "Check if the img element is not a labelable element");
+
+test(function() {
+ assert_equals(document.getElementById("lbl9").labels, undefined);
+}, "Check if the img element can access 'labels'");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-label-element/proxy-click-to-associated-element.html b/testing/web-platform/tests/html/semantics/forms/the-label-element/proxy-click-to-associated-element.html
new file mode 100644
index 0000000000..fbfeda8a51
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-label-element/proxy-click-to-associated-element.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<title>label element click proxying via "for" attribute or nested labelable element</title>
+<link rel="author" title="yaycmyk" href="mailto:evan@yaycmyk.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#the-label-element:the-label-element-10">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form id="test">
+ <input id="foo" type="checkbox" />
+ <label id="foo-label" for="foo">foo</label>
+
+ <label id="bar-label">
+ <input id="bar" type="checkbox" /> bar
+ <input id="baz" type="checkbox" /> baz
+ </label>
+
+ <input id="baz" type="checkbox" />
+ <label id="baz-label" for="baz">baz</label>
+</form>
+<script>
+ "use strict";
+
+ async_test(t => {
+ const label = document.getElementById("foo-label");
+ const input = document.getElementById("foo");
+
+ input.addEventListener("click", t.step_func_done());
+
+ label.click();
+
+ }, "label with for attribute should proxy click events to the associated element");
+
+ async_test(t => {
+ const label = document.getElementById("bar-label");
+ const input = document.getElementById("bar");
+
+ input.addEventListener("click", t.step_func_done());
+
+ label.click();
+
+ }, "label without for attribute should proxy click events to the first labelable child");
+
+ async_test(t => {
+
+ const label = document.getElementById("baz-label");
+ const input = document.getElementById("baz");
+
+ input.addEventListener("click", t.unreached_func("Input should not receive click"));
+ label.addEventListener("click", t.step_func(ev => {
+ ev.preventDefault();
+ t.step_timeout(() => t.done(), 500);
+ }));
+
+ label.click();
+
+ }, "clicking a label that prevents the event's default should not proxy click events");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-label-element/proxy-modifier-click-to-associated-element.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-label-element/proxy-modifier-click-to-associated-element.tentative.html
new file mode 100644
index 0000000000..fa50c08025
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-label-element/proxy-modifier-click-to-associated-element.tentative.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<title>clicks on label element with modifier keys should be proxied to its associated control</title>
+<link rel="author" title="Mu-An Chiou" href="mailto:hi@muan.co">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#the-label-element:the-label-element-10">
+<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>
+<div id="log"></div>
+<div style="user-select: none;">
+ <label id="click-label" for="click">foo</label><input id="click" type="checkbox" />
+ <label id="shift-label" for="shift">foo</label><input id="shift" type="checkbox" />
+ <label id="alt-label" for="alt">foo</label><input id="alt" type="checkbox" />
+ <label id="meta-label" for="meta">foo</label><input id="meta" type="checkbox" />
+</div>
+<script>
+ "use strict";
+
+ function clickWithModifier(label, key) {
+ new test_driver.Actions()
+ .keyDown(key)
+ .pointerMove(0, 0, { origin: label })
+ .pointerDown()
+ .pointerUp()
+ .addTick()
+ .keyUp(key)
+ .send()
+ }
+
+ async_test(t => {
+ const label = document.getElementById("click-label");
+ const input = document.getElementById("click");
+
+ input.addEventListener("click", t.step_func_done());
+ new test_driver.click(label)
+
+ }, "label with for attribute should proxy click events to the associated element on click");
+
+ async_test(t => {
+ const label = document.getElementById("shift-label");
+ const input = document.getElementById("shift");
+
+ input.addEventListener("click", t.step_func_done());
+ clickWithModifier(label, "\uE008"); // ShiftLeft
+
+ }, "label with for attribute should proxy click events to the associated element on shift click");
+
+ async_test(t => {
+ const label = document.getElementById("alt-label");
+ const input = document.getElementById("alt");
+
+ input.addEventListener("click", t.step_func_done());
+ clickWithModifier(label, "\uE00A"); // AltLeft
+
+ }, "label with for attribute should proxy click events to the associated element on alt click");
+
+ async_test(t => {
+ const label = document.getElementById("meta-label");
+ const input = document.getElementById("meta");
+
+ input.addEventListener("click", t.step_func_done());
+ clickWithModifier(label, "\uE03D"); // OSLeft
+
+ }, "label with for attribute should proxy click events to the associated element on meta click");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-legend-element/HTMLLegendElement.html b/testing/web-platform/tests/html/semantics/forms/the-legend-element/HTMLLegendElement.html
new file mode 100644
index 0000000000..8600e5437a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-legend-element/HTMLLegendElement.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: HTMLLegendElement</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" title="4.10.17 The legend element" href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-button-element.html#the-legend-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div style="display:none">
+ <form>
+ <legend id="lgd1">test</legend>
+ </form>
+ <form id="fm">
+ <fieldset id="fs">
+ <legend id="lgd2">test</legend>
+ </fieldset>
+ </form>
+</div>
+<script>
+ test(function() {
+ assert_equals(document.getElementById("lgd1").form, null,
+ "The legend.form return null if it has no fieldset parent.");
+ }, "The legend.form return null when it has no fieldset parent");
+
+ test(function() {
+ assert_equals(document.getElementById("lgd2").form, document.getElementById("fs").form,
+ "The legend.form should be same as fieldset.form.");
+ assert_equals(document.getElementById("lgd2").form, document.getElementById("fm"),
+ "The legend.form should be the correct form.");
+ }, "The legend.form must be same value as fieldset.form");
+
+ test(function() {
+ assert_true(document.getElementById("lgd1") instanceof HTMLLegendElement, "legend should be a HTMLLegendElement");
+ assert_readonly(document.getElementById("lgd1"), "form", "The form is not readonly");
+ }, "Interface HTMLLegendElement");
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/forms/the-legend-element/legend-form.html b/testing/web-platform/tests/html/semantics/forms/the-legend-element/legend-form.html
new file mode 100644
index 0000000000..b127164aed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-legend-element/legend-form.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLLegendElement Test: form</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<div style="display:none">
+ <form id="testform">
+ <legend id="testlegend">radio</legend>
+ </form>
+</div>
+
+<div style="display:none">
+ <form id="testformWithFieldSet">
+ <fieldset>
+ <legend id="legendWithFieldSet">radio</legend>
+ </fieldset>
+ </form>
+</div>
+<script>
+test(function () {
+ var legendEle = document.getElementById("legendWithFieldSet");
+ assert_not_equals(legendEle.form, null);
+ assert_equals(legendEle.form, document.getElementById("testformWithFieldSet"));
+}, "Check if legend.form returns its parent when it's inside a fieldset");
+test(function () {
+ var legendEle = document.getElementById("testlegend");
+ assert_equals(legendEle.form, null);
+}, "Check if legend.form return null when legend has no fieldset element as its parent");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-meter-element/meter-min-rendering-ref.html b/testing/web-platform/tests/html/semantics/forms/the-meter-element/meter-min-rendering-ref.html
new file mode 100644
index 0000000000..f253945968
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-meter-element/meter-min-rendering-ref.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<title>Test Reference</title>
+<meter max="1.0" value="0.5">
diff --git a/testing/web-platform/tests/html/semantics/forms/the-meter-element/meter-min-rendering.html b/testing/web-platform/tests/html/semantics/forms/the-meter-element/meter-min-rendering.html
new file mode 100644
index 0000000000..ca83fc9565
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-meter-element/meter-min-rendering.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>min is accounted for when rendering a &lt;meter&gt; element</title>
+<link rel=author href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel=author href="https://mozilla.org" title="Mozilla">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1746758">
+<link rel=match href="meter-min-rendering-ref.html">
+
+<meter min="1.0" max="2.0" value="1.5">
diff --git a/testing/web-platform/tests/html/semantics/forms/the-meter-element/meter.html b/testing/web-platform/tests/html/semantics/forms/the-meter-element/meter.html
new file mode 100644
index 0000000000..c7c260c957
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-meter-element/meter.html
@@ -0,0 +1,250 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>The meter element</title>
+ <link rel="author" title="Tomoyuki SHIMIZU" href="mailto:tomoyuki.labs@gmail.com">
+ <link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-meter-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <h1>Meter Element</h1>
+ <div id="log"></div>
+ <div style="display: none;">
+ <meter id="meter_illegal_value" value="abc"></meter>
+ <meter id="meter_without_min" value="-10"></meter>
+ <meter id="meter_without_max" value="10"></meter>
+ <meter id="meter_min_without_max_1" value="10" min="-3.1"></meter>
+ <meter id="meter_min_without_max_2" value="210" min="12.1"></meter>
+ <meter id="meter_max_without_min_1" value="-10" max="-5342.55"></meter>
+ <meter id="meter_max_without_min_2" value="210" max="-9.9"></meter>
+ <meter id="meter_illegal_min" value="-2" min="hugfe"></meter>
+ <meter id="meter_illegal_max" value="2.4" max="min"></meter>
+ <meter id="meter_illegal_low_with_min" value="-20" min="-10.3" low="ahuge"></meter>
+ <meter id="meter_illegal_high_with_max" value="2.4" high="old" max="1.5"></meter>
+ <meter id="meter_smaller_than_min" value="-10" min="4.5"></meter>
+ <meter id="meter_larger_than_max" value="2345.53" max="52.02"></meter>
+ <meter id="meter_default_low_and_high_1" value="40" min="-12.3" max="3.4"></meter>
+ <meter id="meter_default_low_and_high_2" value="23"></meter>
+ <meter id="meter_low_smaller_than_min" value="-4" min="12.3" low="34"></meter>
+ <meter id="meter_low_larger_than_max" value="-1" min="-50" low="-5" max="-34.5"></meter>
+ <meter id="meter_high_smaller_than_min" value="-4" min="12.3" high="34"></meter>
+ <meter id="meter_high_larger_than_max" value="-1" min="-50" high="-5" max="-34.5"></meter>
+ <meter id="meter_high_smaller_than_low" value="-9" min="-20" low="-10.3" high="-15.2" max="-2"></meter>
+ <meter id="meter_low_without_min" value="-1" low="-5"></meter>
+ <meter id="meter_high_without_max" value="50000" high="4"></meter>
+ <meter id="meter_optimum_smaller_than_min" value="-8" optimum="-4"></meter>
+ <meter id="meter_optimum_larger_than_max" value="324" optimum="4.6"></meter>
+ <meter id="meter_default_optimum" value="10" min="-132.35" max="33.423"></meter>
+ </div>
+ <script>
+ var meters = [
+ {value: 0, expectedValue: 0, expectedMin: 0, expectedMax: 1.0, expectedLow: 0, expectedHigh: 1.0, expectedOptimum: 0.5, testname: "Default values"},
+ {value: 3, expectedValue: 3, min: -10.1, expectedMin: -10.1, max: 10.1, expectedMax: 10.1, low: -9.1, expectedLow: -9.1, high: 9.1, expectedHigh: 9.1, optimum: 3, expectedOptimum: 3, testname: "Setting values to min, max, low, high and optimum"},
+ {value: 0, expectedValue: 0, min: 0, expectedMin: 0, max: -1.0, expectedMax: 0, expectedLow: 0, expectedHigh: 0, expectedOptimum: 0, testname: "max < min"},
+ {value: 0, expectedValue: 10, min: 10, expectedMin: 10, max: 20, expectedMax: 20, expectedLow: 10, expectedHigh: 20, expectedOptimum: 15, testname: "value < min"},
+ {value: 30, expectedValue: 20, min: 10, expectedMin: 10, max: 20, expectedMax: 20, expectedLow: 10, expectedHigh: 20, expectedOptimum: 15, testname: "value > max"},
+ {value: 15, expectedValue: 15, min: 10, expectedMin: 10, max: 20, expectedMax: 20, low: 5, expectedLow: 10, expectedHigh: 20, expectedOptimum: 15, testname: "low < min"},
+ {value: 15, expectedValue: 15, min: 10, expectedMin: 10, max: 20, expectedMax: 20, low: 25, expectedLow: 20, expectedHigh: 20, expectedOptimum: 15, testname: "low > max"},
+ {value: 15, expectedValue: 15, min: 10, expectedMin: 10, max: 20, expectedMax: 20, low: 12, expectedLow: 12, high: 10, expectedHigh: 12, expectedOptimum: 15, testname: "high < low"},
+ {value: 15, expectedValue: 15, min: 10, expectedMin: 10, max: 20, expectedMax: 20, low: 10, expectedLow: 10, high: 22, expectedHigh: 20, expectedOptimum: 15, testname: "high > max"},
+ {value: 15, expectedValue: 15, min: 10, expectedMin: 10, max: 20, expectedMax: 20, expectedLow: 10, expectedHigh: 20, optimum: 9, expectedOptimum: 10, testname: "optimum < min"},
+ {value: 15, expectedValue: 15, min: 10, expectedMin: 10, max: 20, expectedMax: 20, expectedLow: 10, expectedHigh: 20, optimum: 21, expectedOptimum: 20, testname: "optimum > max"}
+ ];
+ for (var i = 0; i < meters.length; i++) {
+ var m = meters[i];
+ test(function() {
+ var meter = document.createElement("meter");
+ meter.value = m.value;
+ if (m.min) meter.min= m.min;
+ if (m.max) meter.max = m.max;
+ if (m.low) meter.low = m.low;
+ if (m.high) meter.high = m.high;
+ if (m.optimum) meter.optimum = m.optimum;
+ assert_equals(meter.value, m.expectedValue, "meter value");
+ assert_equals(meter.min, m.expectedMin, "min value");
+ assert_equals(meter.max, m.expectedMax, "max value");
+ assert_equals(meter.low, m.expectedLow, "low value");
+ assert_equals(meter.high, m.expectedHigh, "high value");
+ assert_equals(meter.optimum, m.expectedOptimum, "optimum value");
+ }, m.testname);
+ }
+ test(function() {
+ var meter = document.createElement("meter");
+ assert_throws_js(TypeError, function() { meter.value = "foobar"; }, "value attribute");
+ assert_throws_js(TypeError, function() { meter.min = "foobar"; }, "min attribute");
+ assert_throws_js(TypeError, function() { meter.max = "foobar"; }, "max attribute");
+ assert_throws_js(TypeError, function() { meter.low = "foobar"; }, "low attribute");
+ assert_throws_js(TypeError, function() { meter.high = "foobar"; }, "high attribute");
+ assert_throws_js(TypeError, function() { meter.optimum = "foobar"; }, "optimum attribute");
+ }, "Invalid floating-point number values");
+
+ </script>
+ <script type="text/javascript">
+ test(function() {
+ assert_equals(document.getElementById('meter_illegal_value').value, 0);
+ }, "value must be 0 when a string is given");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_without_min').min, 0);
+ }, "default value of min is 0");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_without_min').value, 0);
+ }, "If min is not specified and value is smaller than the default value of min (i.e. 0), the actual value must be 0");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_without_max').max, 1.0);
+ }, "default value of max is 1.0");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_without_max').value, 1.0);
+ }, "If max is not specified and value is larger than the default value of max (i.e. 1.0), the actual value must be 1.0");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_min_without_max_1').max, 1.0);
+ }, "If a value smaller than 1.0 is given to min and max is not specified, max must be the same value as its default value (i.e. 1.0)");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_min_without_max_1').value, 1.0);
+ }, "If a value smaller than 1.0 is given to min, max is not specified, and value is larger than the default value of max (i.e. 1.0), the actual value must be 1.0");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_min_without_max_2').max, 12.1);
+ }, "If a value larger than or equal to 1.0 is given to min and max is not specified, max must be the same value as min");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_min_without_max_2').value, 12.1);
+ }, "If a value larger than or equal to 1.0 is given to min and max is not specified, the actual value must be the same value as min");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_max_without_min_1').min, 0);
+ }, "If a value smaller than 0 is given to max and min is not specified, min must be be the same value as its default value (i.e. 0)");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_max_without_min_1').max, 0);
+ }, "If a value smaller than 0 is given to max and min is not specified, max must be be the same value as the default value of min (i.e. 0)");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_max_without_min_1').value, 0);
+ }, "If a value smaller than 0 is given to max and min is not specified, the actual value must be be the same value as the default value of min (i.e. 0)");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_max_without_min_2').max, 0);
+ }, "If a value larger than or equal to 0 is given to max and min is not specified, max must be the same value as the default value of min (i.e. 0)");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_max_without_min_2').min, 0);
+ }, "If a value larger than or equal to 0 is given to max and min is not specified, min must be the same value as its default value (i.e. 0)");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_max_without_min_2').value, 0);
+ }, "If a value larger than or equal to 0 is given to max and min is not specified, the actual value must be the same value as the default value of min (i.e. 0)");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_illegal_min').min, 0);
+ }, "min must be 0 when a string is given");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_illegal_min').value, 0);
+ }, "If a string is given to min and value is smaller than the default value of min (i.e. 0), the actual value must be 0");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_illegal_max').max, 1.0);
+ }, "max must be 1.0 when a string is given");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_illegal_max').value, 1.0);
+ }, "If a string is given to max and value is larger than the default value of min (i.e. 1.0), the actual value must be 1.0");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_illegal_low_with_min').low, -10.3);
+ }, "giving a string to low must not affect the actual value");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_illegal_high_with_max').high, 1.5);
+ }, "high must equal max when a string is given to high");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_illegal_high_with_max').value, 1.5);
+ }, "giving a string to high must not affect the actual value");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_smaller_than_min').value, 4.5);
+ }, "value must not be smaller than min");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_larger_than_max').value, 52.02);
+ }, "value must not be larger than max");
+
+ test(function() {
+ var e = document.getElementById('meter_default_low_and_high_1');
+ assert_array_equals([e.low,e.high], [-12.3,3.4]);
+ }, "default low and high values equal min and max, respectively");
+
+ test(function() {
+ var e = document.getElementById('meter_default_low_and_high_2');
+ assert_array_equals([e.low,e.high], [0,1.0]);
+ }, "default low and high values equal 0 and 1.0 respectively, if both low and high are not specified");
+
+ test(function() {
+ var e = document.getElementById('meter_low_smaller_than_min');
+ assert_array_equals([e.low,e.min,e.value], [12.3,12.3,12.3]);
+ }, "low must not be smaller than min");
+
+ test(function() {
+ var e = document.getElementById('meter_low_larger_than_max');
+ assert_array_equals([e.low,e.max,e.value], [-34.5,-34.5,-34.5]);
+ }, "low must not be larger than max");
+
+ test(function() {
+ var e = document.getElementById('meter_high_smaller_than_min');
+ assert_array_equals([e.high,e.min,e.value], [12.3,12.3,12.3]);
+ }, "high must not be smaller than min");
+
+ test(function() {
+ var e = document.getElementById('meter_high_larger_than_max');
+ assert_array_equals([e.high,e.max,e.value], [-34.5,-34.5,-34.5]);
+ }, "high must not be larger than max");
+
+ test(function() {
+ var e = document.getElementById('meter_low_without_min');
+ assert_array_equals([e.low,e.min,e.value], [0,0,0]);
+ }, "If min is not specified, low must not be smaller than default value of min (i.e. 0)");
+
+ test(function() {
+ var e = document.getElementById('meter_high_smaller_than_low');
+ assert_array_equals([e.low,e.high,e.value], [-10.3,-10.3,-9]);
+ }, "If a value smaller than low is given to high, it must be set to the same value as low");
+
+ test(function() {
+ var e = document.getElementById('meter_high_without_max');
+ assert_array_equals([e.high,e.value], [1.0,1.0]);
+ }, "If max is not specified, high must not be larger than default value of max (i.e. 1.0)");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_optimum_smaller_than_min').optimum, 0);
+ }, "optimum smaller than min");
+
+ test(function() {
+ var e = document.getElementById('meter_optimum_smaller_than_min');
+ assert_array_equals([e.min,e.value], [0,0]);
+ }, "optimum (smaller than min) must not affect min and the actual value");
+
+ test(function() {
+ assert_equals(document.getElementById('meter_optimum_larger_than_max').optimum, 1.0);
+ }, "optimum smaller than max");
+
+ test(function() {
+ var e = document.getElementById('meter_optimum_larger_than_max');
+ assert_array_equals([e.max,e.value], [1.0,1.0]);
+ }, "optimum (larger than max) must not affect max and the actual value");
+
+ test(function() {
+ var e = document.getElementById('meter_default_optimum');
+ assert_equals(e.optimum, (e.max + e.min) / 2);
+ }, "default optimum value is the midpoint between min and max");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-optgroup-element/optgroup-disabled-manual.html b/testing/web-platform/tests/html/semantics/forms/the-optgroup-element/optgroup-disabled-manual.html
new file mode 100644
index 0000000000..ca8c6cda80
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-optgroup-element/optgroup-disabled-manual.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLOptGroupElement Test: disabled</title>
+<meta name="flags" content="interact">
+<link rel="author" title="Intel" href="http://www.intel.com/">
+
+<form>
+ <select>
+ <optgroup label="8.01" disabled>
+ <option value="8.01.1">Lecture 01: Powers of Ten</option>
+ <option value="8.01.2">Lecture 02: 1D Kinematics</option>
+ <option value="8.01.3">Lecture 03: Vectors</option>
+ </optgroup>
+ <optgroup label="8.02">
+ <option value="8.02.1">Lecture 01: What holds our world together?</option>
+ <option value="8.02.2">Lecture 02: Electric Field</option>
+ <option value="8.02.3">Lecture 03: Electric Flux</option>
+ </optgroup>
+ </select>
+</form>
+
+<h2>Description</h2>
+<p>
+ This test validates that an optgroup element is disabled if its disabled attribute is present.
+</p>
+
+<h2>Test steps:</h2>
+<ol>
+ <li>
+ Click the select flag to select section '8.01'
+ </li>
+</ol>
+
+<h2>Result:</h2>
+<p>Click the select flag and try to select section 8.01, test passes if the section 8.01 is disable to be selected</p>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/dynamic-content-change-rendering-ref.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/dynamic-content-change-rendering-ref.html
new file mode 100644
index 0000000000..453bb70822
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/dynamic-content-change-rendering-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<select>
+<option>foo</option>
+</select>
+
+<select multiple>
+<option>bar</option>
+</select>
+
+</bod>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/dynamic-content-change-rendering.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/dynamic-content-change-rendering.html
new file mode 100644
index 0000000000..1108c45e11
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/dynamic-content-change-rendering.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Invalidation test on resetting &lt;select></title>
+<link rel="help" href="https://html.spec.whatwg.org/C/#concept-option-label">
+<link rel="help" href="http://crbug.com/1090806">
+<link rel="match" href="dynamic-content-change-rendering-ref.html">
+<meta name=fuzzy content="maxDifference=0-3;totalPixels=20">
+<body>
+
+<select id="dropdown">
+<option></option>
+</select>
+
+<select id="listbox" multiple>
+<option></option>
+</select>
+
+<script>
+const selects = document.querySelectorAll('select');
+
+const span0 = document.createElement('span');
+selects[0].options[0].appendChild(span0);
+
+const span1 = document.createElement('span');
+selects[1].options[0].appendChild(span1);
+
+document.documentElement.addEventListener('TestRendered', e => {
+ span0.textContent = 'foo';
+ span1.textContent = 'bar';
+ e.target.removeAttribute('class');
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/option-disabled-manual.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-disabled-manual.html
new file mode 100644
index 0000000000..25dfcc87a5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-disabled-manual.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLOptionElement Test: disabled</title>
+<meta name="flags" content="interact">
+<link rel="author" title="Intel" href="http://www.intel.com/">
+
+<div>
+ <select>
+ <option id="testOption1" text="Option1" >Option1</option>
+ <option id="testOption2" disabled >Option2</option>
+ <option id="testOption3" >Option3</option>
+ </select>
+</div>
+
+<h2>Description</h2>
+<p>
+ This test validates that an option element is disabled if its disabled attribute is present.
+</p>
+
+<h2>Test steps:</h2>
+<ol>
+ <li>
+ Click the select flag to select 'Option2'
+ </li>
+</ol>
+
+<h2>Result:</h2>
+<p>Test passes if not able to select 'Option2'</p>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/option-element-constructor.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-element-constructor.html
new file mode 100644
index 0000000000..05bcb3024a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-element-constructor.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Option element constructor</title>
+<link rel="author" title="Alex Pearson" href="mailto:alex@alexpear.com">
+<link rel="help" href="https://html.spec.whatwg.org/#the-option-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="parent">
+ <div id="child" tabindex="0"></div>
+</div>
+
+<body>
+<script>
+ "use strict";
+
+ test(() => {
+ const option = new Option();
+
+ assert_true(option instanceof HTMLOptionElement);
+
+ assert_false(option.hasChildNodes());
+ assert_false(option.hasAttribute("value"));
+ assert_false(option.hasAttribute("selected"));
+ assert_false(option.selected);
+
+ assert_equals(option.textContent, "");
+ assert_equals(option.value, "");
+ }, "Option constructor with no arguments");
+
+ test(() => {
+ const option = new Option(false, false);
+
+ assert_true(option instanceof HTMLOptionElement);
+
+ assert_true(option.hasChildNodes());
+ assert_equals(option.childNodes.length, 1);
+ assert_equals(option.childNodes[0].nodeType, Node.TEXT_NODE);
+ assert_equals(option.childNodes[0].data, "false");
+ assert_equals(option.getAttribute("value"), "false");
+ assert_false(option.hasAttribute("selected"));
+ assert_false(option.selected);
+
+ assert_equals(option.textContent, "false");
+ assert_equals(option.value, "false");
+ }, "Option constructor with falsy arguments");
+
+ test(() => {
+ const option = new Option("text", "value");
+
+ assert_true(option.hasChildNodes());
+ assert_equals(option.childNodes.length, 1);
+ assert_equals(option.childNodes[0].nodeType, Node.TEXT_NODE);
+ assert_equals(option.childNodes[0].data, "text");
+ assert_equals(option.getAttribute("value"), "value");
+ assert_false(option.hasAttribute("selected"));
+ assert_false(option.selected);
+
+ assert_equals(option.textContent, "text");
+ assert_equals(option.value, "value");
+ }, "Option constructor creates HTMLOptionElement with specified text and value");
+
+ test(() => {
+ const notSelected = new Option("text", "value", false);
+ const selected = new Option("text", "value", true);
+
+ assert_false(notSelected.hasAttribute("selected"));
+ assert_equals(notSelected.getAttribute("selected"), null);
+ assert_false(notSelected.selected);
+
+ assert_equals(selected.getAttribute("selected"), "");
+ assert_false(selected.selected);
+ }, "Option constructor handles selectedness correctly when specified with defaultSelected only");
+
+ test(() => {
+ const notSelected = new Option("text", "value", true, false);
+ const selected = new Option("text", "value", false, true);
+
+ assert_equals(notSelected.selected, false);
+ assert_equals(selected.selected, true);
+ }, "Option constructor handles selectedness correctly, even when incongruous with defaultSelected");
+
+ test(() => {
+ const option = new Option(undefined, undefined);
+
+ assert_false(option.hasChildNodes());
+ assert_false(option.hasAttribute("value"));
+
+ assert_equals(option.textContent, "");
+ assert_equals(option.value, "");
+ }, "Option constructor treats undefined text and value correctly");
+
+ test(() => {
+ const option = new Option("", "");
+
+ assert_false(option.hasChildNodes());
+ assert_true(option.hasAttribute("value"));
+
+ assert_equals(option.textContent, "");
+ assert_equals(option.value, "");
+ }, "Option constructor treats empty text and value correctly");
+
+ test(() => {
+ const option = new Option("text", "value", 0, "");
+
+ assert_false(option.hasAttribute("selected"));
+ assert_false(option.selected);
+ }, "Option constructor treats falsy selected and defaultSelected correctly");
+
+ test(() => {
+ const option = new Option("text", "value", {}, 1);
+
+ assert_true(option.hasAttribute("selected"));
+ assert_true(option.selected);
+ }, "Option constructor treats truthy selected and defaultSelected correctly");
+
+ test(() => {
+ const option = new Option("text", "value", false, true);
+
+ assert_false(option.hasAttribute("selected"));
+ assert_true(option.selected);
+
+ option.setAttribute("selected", "");
+ assert_true(option.selected);
+
+ option.removeAttribute("selected");
+ assert_false(option.selected);
+ }, "Option constructor does not set dirtiness (so, manipulating the selected content attribute still updates the " +
+ "selected IDL attribute)");
+
+ test(function() {
+ var option = new Option();
+ assert_equals(Object.getPrototypeOf(option), HTMLOptionElement.prototype);
+ }, "Prototype of object created with named constructor");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/option-form.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-form.html
new file mode 100644
index 0000000000..1a68b5c1ca
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-form.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLOptionElement.form</title>
+<link rel=author title="Sergey Alexandrov" href="mailto:splavgm@gmail.com">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#dom-option-form">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<form id="form">
+ <select id="select">
+ <optgroup id="optgroup"></optgroup>
+ </select>
+</form>
+<div id=log></div>
+
+<script>
+test(function () {
+ var form = document.getElementById("form");
+ var select = document.getElementById("select");
+ var optgroup = document.getElementById("optgroup");
+
+ var o1 = document.createElement("option");
+ assert_equals(o1.form, null);
+
+ select.appendChild(o1);
+ assert_equals(o1.form, select.form);
+
+ var o2 = document.createElement("option");
+ select.appendChild(o2);
+ assert_equals(o2.form, select.form);
+
+}, "form");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/option-index.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-index.html
new file mode 100644
index 0000000000..719c608a63
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-index.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<title>HTMLOptionElement.prototype.index</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-option-index">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<select>
+<option id="option0">hello</option>
+<option id="option1">hello</option>
+</select>
+
+
+<datalist>
+<option id="dl-option0">hello</option>
+<option id="dl-option1">hello</option>
+</datalist>
+
+<option id="doc-option0">hello</option>
+<option id="doc-option1">hello</option>
+
+<script>
+"use strict";
+
+test(() => {
+
+ assert_equals(document.querySelector("#option0").index, 0);
+ assert_equals(document.querySelector("#option1").index, 1);
+
+}, "option index should work inside the document");
+
+test(() => {
+
+ assert_equals(document.querySelector("#dl-option0").index, 0);
+ assert_equals(document.querySelector("#dl-option1").index, 0);
+
+}, "option index should always be 0 for options in datalists");
+
+test(() => {
+
+ assert_equals(document.querySelector("#doc-option0").index, 0);
+ assert_equals(document.querySelector("#doc-option1").index, 0);
+
+}, "option index should always be 0 for options with no container");
+
+test(() => {
+
+ assert_equals(document.createElement("option").index, 0);
+ assert_equals(document.createElement("option").index, 0);
+
+}, "option index should always be 0 for options not even in the document");
+
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/option-label-value.js b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-label-value.js
new file mode 100644
index 0000000000..5c453f1733
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-label-value.js
@@ -0,0 +1,82 @@
+function test_option(member) {
+ test(function() {
+ var option = document.createElement("option");
+ assert_equals(option[member], "");
+ }, "No children, no " + member);
+
+ test(function() {
+ var option = document.createElement("option");
+ option.setAttribute(member, "")
+ assert_equals(option[member], "");
+ }, "No children, empty " + member);
+
+ test(function() {
+ var option = document.createElement("option");
+ option.setAttribute(member, member)
+ assert_equals(option[member], member);
+ }, "No children, " + member);
+
+ test(function() {
+ var option = document.createElement("option");
+ option.setAttributeNS("http://www.example.com/", member, member)
+ assert_equals(option[member], "");
+ }, "No children, namespaced " + member);
+
+ test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" child "));
+ assert_equals(option[member], "child");
+ }, "Single child, no " + member);
+
+ test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" child "));
+ option.setAttribute(member, "")
+ assert_equals(option[member], "");
+ }, "Single child, empty " + member);
+
+ test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" child "));
+ option.setAttribute(member, member)
+ assert_equals(option[member], member);
+ }, "Single child, " + member);
+
+ test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" child "));
+ option.setAttributeNS("http://www.example.com/", member, member)
+ assert_equals(option[member], "child");
+ }, "Single child, namespaced " + member);
+
+ test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" child "));
+ option.appendChild(document.createTextNode(" node "));
+ assert_equals(option[member], "child node");
+ }, "Two children, no " + member);
+
+ test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" child "));
+ option.appendChild(document.createTextNode(" node "));
+ option.setAttribute(member, "")
+ assert_equals(option[member], "");
+ }, "Two children, empty " + member);
+
+ test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" child "));
+ option.appendChild(document.createTextNode(" node "));
+ option.setAttribute(member, member)
+ assert_equals(option[member], member);
+ }, "Two children, " + member);
+
+ test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" child "));
+ option.appendChild(document.createTextNode(" node "));
+ option.setAttributeNS("http://www.example.com/", member, member)
+ assert_equals(option[member], "child node");
+ }, "Two children, namespaced " + member);
+}
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/option-label.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-label.html
new file mode 100644
index 0000000000..f931b96220
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-label.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLOptionElement.label</title>
+<link rel=author title=Ms2ger href="mailto:Ms2ger@gmail.com">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#dom-option-label">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=option-label-value.js></script>
+<div id=log></div>
+<script>
+test_option("label")
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/option-selected.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-selected.html
new file mode 100644
index 0000000000..e18e90b853
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-selected.html
@@ -0,0 +1,61 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLOptionElement.selected</title>
+<link rel=author title="Corey Farwell" href="mailto:coreyf@rwell.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#dom-option-selected">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+
+<script>
+test(function () {
+ var elem = document.createElement("option");
+ assert_equals(elem.selected, false);
+
+ elem.setAttribute("selected", "");
+ assert_equals(elem.selected, true);
+
+ elem.removeAttribute("selected");
+ assert_equals(elem.selected, false);
+
+ elem.defaultSelected = true
+ assert_equals(elem.selected, true);
+
+ elem.defaultSelected = false;
+ assert_equals(elem.selected, false);
+}, "not dirty");
+
+test(function () {
+ testDirty(true);
+}, "dirty, selected");
+
+test(function () {
+ testDirty(false);
+}, "dirty, not selected");
+
+function testDirty(isSelected) {
+ var elem = document.createElement("option");
+
+ elem.selected = isSelected; // After this assignment, dirtiness=true
+ assertDirty(elem, isSelected);
+
+ elem.selected = !isSelected; // Change the value, still dirty
+ assertDirty(elem, !isSelected);
+};
+
+function assertDirty(elem, expect) {
+ assert_equals(elem.selected, expect);
+
+ elem.setAttribute("selected", "");
+ assert_equals(elem.selected, expect);
+
+ elem.removeAttribute("selected");
+ assert_equals(elem.selected, expect);
+
+ elem.defaultSelected = true;
+ assert_equals(elem.selected, expect);
+
+ elem.defaultSelected = false;
+ assert_equals(elem.selected, expect);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-backslash.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-backslash.html
new file mode 100644
index 0000000000..34bd0d368b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-backslash.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset=EUC-JP>
+<title>Test for the backslash sign in option.text</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<select id=test><option>\</option></select>
+<script>
+test(function() {
+ var select = document.getElementById("test");
+ var option = select.firstChild;
+ assert_equals(option.text, "\\");
+ assert_equals(option.textContent, "\\");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-label.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-label.html
new file mode 100644
index 0000000000..9259aecf30
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-label.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLOptionElement.text</title>
+<link rel=author title=Ms2ger href="mailto:Ms2ger@gmail.com">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#dom-option-text">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var option = document.createElement("option");
+ option.setAttribute("label", "label");
+ option.textContent = "text";
+ assert_equals(option.text, "text");
+}, "Option with non-empty label.");
+
+test(function() {
+ var option = document.createElement("option");
+ option.setAttribute("label", "");
+ option.textContent = "text";
+ assert_equals(option.text, "text");
+}, "Option with empty label.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-recurse.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-recurse.html
new file mode 100644
index 0000000000..cf854f5260
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-recurse.html
@@ -0,0 +1,92 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLOptionElement.text</title>
+<link rel=author title=Ms2ger href="mailto:Ms2ger@gmail.com">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#dom-option-text">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createElement("font"))
+ .appendChild(document.createTextNode(" font "));
+ assert_equals(option.text, "font");
+}, "option.text should recurse");
+
+test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" before "));
+ option.appendChild(document.createElement("script"))
+ .appendChild(document.createTextNode(" script "));
+ option.appendChild(document.createTextNode(" after "));
+ assert_equals(option.text, "before after");
+}, "option.text should not recurse into HTML script elements");
+test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" before "));
+ option.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "script"))
+ .appendChild(document.createTextNode(" script "));
+ option.appendChild(document.createTextNode(" after "));
+ assert_equals(option.text, "before after");
+}, "option.text should not recurse into SVG script elements");
+test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" before "));
+ option.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "script"))
+ .appendChild(document.createTextNode(" script "));
+ option.appendChild(document.createTextNode(" after "));
+ assert_equals(option.text, "before script after");
+}, "option.text should recurse into MathML script elements");
+test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" before "));
+ option.appendChild(document.createElementNS(null, "script"))
+ .appendChild(document.createTextNode(" script "));
+ option.appendChild(document.createTextNode(" after "));
+ assert_equals(option.text, "before script after");
+}, "option.text should recurse into null script elements");
+test(function() {
+ var option = document.createElement("option");
+ var span = option.appendChild(document.createElement("span"));
+ span.appendChild(document.createTextNode(" Some "));
+ span.appendChild(document.createElement("script"))
+ .appendChild(document.createTextNode(" script "));
+ option.appendChild(document.createTextNode(" Text "));
+ assert_equals(option.text, "Some Text");
+}, "option.text should work if a child of the option ends with a script");
+
+test(function() {
+ var script = document.createElement("script");
+ var option = script.appendChild(document.createElement("option"));
+ option.appendChild(document.createTextNode("text"));
+ assert_equals(option.text, "text");
+}, "option.text should work if the option is in an HTML script element");
+test(function() {
+ var script = document.createElementNS("http://www.w3.org/2000/svg", "script");
+ var option = script.appendChild(document.createElement("option"));
+ option.appendChild(document.createTextNode("text"));
+ assert_equals(option.text, "text");
+}, "option.text should work if the option is in an SVG script element");
+test(function() {
+ var script = document.createElementNS("http://www.w3.org/1998/Math/MathML", "script");
+ var option = script.appendChild(document.createElement("option"));
+ option.appendChild(document.createTextNode("text"));
+ assert_equals(option.text, "text");
+}, "option.text should work if the option is in a MathML script element");
+
+test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode("te"));
+ option.appendChild(document.createComment("comment"));
+ option.appendChild(document.createTextNode("xt"));
+ assert_equals(option.text, "text");
+}, "option.text should ignore comment children");
+test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode("te"));
+ option.appendChild(document.createProcessingInstruction("target", "data"));
+ option.appendChild(document.createTextNode("xt"));
+ assert_equals(option.text, "text");
+}, "option.text should ignore PI children");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-setter.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-setter.html
new file mode 100644
index 0000000000..1aa44ed7f4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-setter.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<form>
+ <select>
+ <option value='foo'>bar</option>
+ </select>
+</form>
+<script>
+test(() => {
+ var option = document.querySelector('option');
+ var textChild = option.firstChild;
+ assert_equals(textChild.nodeValue, "bar", "Verify that text child node's value equals the option value.");
+ assert_true(textChild.isConnected, 'Verify that text child node is in the document.');
+ option.text = "baz";
+ assert_equals(textChild.nodeValue, "bar", 'Verify that the text child node does not have an updated value.');
+ assert_false(textChild.isConnected, 'Verify that the text child node is not in the document.');
+ assert_not_equals(textChild, option.firstChild, 'Verify that text child node was replaced by a different text child node.');
+ assert_equals(option.firstChild.nodeValue, "baz", 'Verify that the new text child node does equal the updated value.');
+ assert_equals(option.text, "baz", 'Verify that option text getter returns the updated value.');
+ option.text = "";
+ assert_equals(option.firstChild, null, 'Verify that after setting to empty string there are no child text nodes.');
+}, 'Verify that using HTMLOptionElement.text setter does not update the existing text child node.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-spaces.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-spaces.html
new file mode 100644
index 0000000000..2c712655a9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-text-spaces.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLOptionElement.text</title>
+<link rel=author title=Ms2ger href="mailto:Ms2ger@gmail.com">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#dom-option-text">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var spaces = ["\u0020", "\u0009", "\u000A", "\u000C", "\u000D"];
+ spaces.forEach(function(space) {
+ test(function() {
+ var option = document.createElement("option");
+ option.textContent = space + "text";
+ assert_equals(option.text, "text");
+ }, "option.text should strip leading space characters (" +
+ format_value(space) + ")");
+ });
+ spaces.forEach(function(space) {
+ test(function() {
+ var option = document.createElement("option");
+ option.textContent = "text" + space;
+ assert_equals(option.text, "text");
+ }, "option.text should strip trailing space characters (" +
+ format_value(space) + ")");
+ });
+ spaces.forEach(function(space) {
+ test(function() {
+ var option = document.createElement("option");
+ option.textContent = space + "text" + space;
+ assert_equals(option.text, "text");
+ }, "option.text should strip leading and trailing space characters (" +
+ format_value(space) + ")");
+ });
+ spaces.forEach(function(space) {
+ test(function() {
+ var option = document.createElement("option");
+ option.textContent = "before" + space + "after";
+ assert_equals(option.text, "before after");
+ }, "option.text should replace single internal space characters (" +
+ format_value(space) + ")");
+ });
+ spaces.forEach(function(space1) {
+ spaces.forEach(function(space2) {
+ test(function() {
+ var option = document.createElement("option");
+ option.textContent = "before" + space1 + space2 + "after";
+ assert_equals(option.text, "before after");
+ }, "option.text should replace multiple internal space characters (" +
+ format_value(space1) + ", " + format_value(space2) + ")");
+ });
+ });
+ test(function() {
+ var option = document.createElement("option");
+ option.textContent = "\u00a0text";
+ assert_equals(option.text, "\u00a0text");
+ }, "option.text should leave leading NBSP alone.");
+ test(function() {
+ var option = document.createElement("option");
+ option.textContent = "text\u00a0";
+ assert_equals(option.text, "text\u00a0");
+ }, "option.text should leave trailing NBSP alone.");
+ test(function() {
+ var option = document.createElement("option");
+ option.textContent = "before\u00a0after";
+ assert_equals(option.text, "before\u00a0after");
+ }, "option.text should leave a single internal NBSP alone.");
+ test(function() {
+ var option = document.createElement("option");
+ option.textContent = "before\u00a0\u00a0after";
+ assert_equals(option.text, "before\u00a0\u00a0after");
+ }, "option.text should leave two internal NBSPs alone.");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-option-element/option-value.html b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-value.html
new file mode 100644
index 0000000000..cccdc37487
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-option-element/option-value.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLOptionElement.value</title>
+<link rel=author title=Ms2ger href="mailto:Ms2ger@gmail.com">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#dom-option-label">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=option-label-value.js></script>
+<div id=log></div>
+<script>
+test_option("value")
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-output-element/mutations.window.js b/testing/web-platform/tests/html/semantics/forms/the-output-element/mutations.window.js
new file mode 100644
index 0000000000..9b1c6bd70f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-output-element/mutations.window.js
@@ -0,0 +1,36 @@
+function assert_equal_values(output, value, message) {
+ assert_equals(output.value, value, `.value ${message}`);
+ assert_equals(output.defaultValue, value, `.defaultValue ${message}`);
+}
+function assert_values(output, value, defaultValue, message) {
+ assert_equals(output.value, value, `.value ${message}`);
+ assert_equals(output.defaultValue, defaultValue, `.defaultValue ${message}`);
+}
+
+test(() => {
+ const output = document.createElement("output"),
+ child = output.appendChild(document.createElement("span"));
+ assert_equal_values(output, "", "start");
+ child.textContent = "x";
+ assert_equal_values(output, "x", "after setting textContent");
+ output.value = "some";
+ assert_values(output, "some", "x", "after setting value");
+ output.textContent = "y";
+ assert_values(output, "y", "x", "after setting textContent again");
+}, "Descendant mutations and output.value and .defaultValue");
+
+test(() => {
+ const form = document.createElement("form"),
+ output = form.appendChild(document.createElement("output"));
+ output.textContent = "value";
+ assert_equal_values(output, "value", "after setting textContent");
+ output.value = "heya";
+ assert_values(output, "heya", "value", "after setting value");
+ form.reset();
+ assert_equal_values(output, "value", "after form.reset()");
+
+ output.innerHTML = "<div>something</div>";
+ assert_equal_values(output, "something", "after setting innerHTML");
+ form.reset();
+ assert_equal_values(output, "something", "after form.reset() again");
+}, "output and output.form.reset()");
diff --git a/testing/web-platform/tests/html/semantics/forms/the-output-element/output-setcustomvalidity.html b/testing/web-platform/tests/html/semantics/forms/the-output-element/output-setcustomvalidity.html
new file mode 100644
index 0000000000..1166eeb610
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-output-element/output-setcustomvalidity.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<title>output setCustomValidity</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<output id='output_test'></output>
+
+<script>
+
+test(() => {
+ let elem = document.getElementById("output_test");
+ assert_false(elem.validity.customError);
+ elem.setCustomValidity("custom error");
+ assert_true(elem.validity.customError);
+}, "output setCustomValidity is correct")
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-output-element/output-validity.html b/testing/web-platform/tests/html/semantics/forms/the-output-element/output-validity.html
new file mode 100644
index 0000000000..c59a055ecf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-output-element/output-validity.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<title>:valid and :invalid pseudo-class on output element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<output id='output_test'></output>
+
+<script>
+
+test(() => {
+ let output = document.getElementById("output_test");
+ assert_false(output.matches(":valid"), "should not match :valid pseudo-class");
+ assert_false(output.matches(":invalid"), "should not match :invalid pseudo-class");
+
+ output.setCustomValidity("custom error");
+ assert_equals(output.validationMessage, "", "should not have a validation message");
+ assert_true(output.validity.customError, "should have a custom error");
+ assert_false(output.validity.valid, "should not be valid with a custom error");
+ assert_false(output.matches(":valid"), "should still not match :valid pseudo-class");
+ assert_false(output.matches(":invalid"), "should still not match :invalid pseudo-class");
+}, ":valid and :invalid pseudo-class on output element")
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-output-element/output.html b/testing/web-platform/tests/html/semantics/forms/the-output-element/output.html
new file mode 100644
index 0000000000..7ae00ec7e0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-output-element/output.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>The output element</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#the-output-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<output id=output></output>
+<output id="defaultValueOutput">abc</output>
+<script>
+ var output = document.getElementById("output");
+ var defaultValueOutput = document.getElementById("defaultValueOutput");
+
+ test(function(){
+ assert_equals(output.type, "output", "type must return the string 'output'");
+ assert_equals(output.textContent, "", "textContent is empty");
+ assert_equals(output.value, "", "value should be empty");
+ assert_equals(output.defaultValue, "", "defaultValue should be empty");
+
+ output.textContent="5";
+ assert_equals(output.value, "5", "textContent is set to 5: value is updated");
+ assert_equals(output.textContent, "5", "textContent is set to 5");
+ assert_equals(output.defaultValue, "5", "textContent is set to 5: defaultValue is updated");
+
+ output.defaultValue="10"; // value mode flag is in "default" mode. Setting defaultValue should set textContent as well
+ assert_equals(output.value, "10", "defaultValue is set to 10: value is updated");
+ assert_equals(output.textContent, "10", "defaultValue is set to 10: textContent is updated");
+ assert_equals(output.defaultValue, "10", "defaultValue is set to 10");
+
+ output.value="20"; // set the value mode flag to "value": default value remains unchanged
+ assert_equals(output.value, "20", "value is set to 20");
+ assert_equals(output.textContent, "20", "value is set to 20: textContent is updated");
+ assert_equals(output.defaultValue, "10", "value is set to 20: defaultValue remains unchanged");
+
+ output.defaultValue="15"; // value mode flag is in "value" mode. textContent remains unchanged when setting defaultValue
+ assert_equals(output.value, "20", "defaultValue is set to 15: value remains unchanged");
+ assert_equals(output.textContent, "20", "defaultValue is set to 15: textContent remains unchanged");
+ assert_equals(output.defaultValue, "15", "defaultValue is set to 15");
+
+ assert_equals(defaultValueOutput.type, "output", "type must return the string 'output'");
+ assert_equals(defaultValueOutput.textContent, "abc", "textContent should be 'abc'");
+ assert_equals(defaultValueOutput.value, "abc", "value should be 'abc'");
+ assert_equals(defaultValueOutput.defaultValue, "abc", "defaultValue should be 'abc'");
+ }, "output value and defaultValue");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-progress-element/progress-2.html b/testing/web-platform/tests/html/semantics/forms/the-progress-element/progress-2.html
new file mode 100644
index 0000000000..ebc4750627
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-progress-element/progress-2.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Progress Element Tests</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-progress-element" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+ <progress id="p00" style="display: none"></progress>
+ <progress id="p01" max="5.5" value=".5" style="display: none"></progress>
+ <script>
+ test(function () {
+ assert_equals(document.getElementById('p00').position, -1);
+ }, "progress position equals -1");
+
+ test(function () {
+ assert_equals(document.getElementById('p00').value, 0);
+ }, "progress value equals 0");
+
+ test(function () {
+ assert_equals(document.getElementById('p01').value, .5);
+ }, "progress value equals .5");
+
+ test(function () {
+ document.getElementById('p00').value = document.getElementById('p00').value;
+ assert_equals(document.getElementById('p00').position, 0);
+ }, "progress position equals 0");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-progress-element/progress.html b/testing/web-platform/tests/html/semantics/forms/the-progress-element/progress.html
new file mode 100644
index 0000000000..00d63c372f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-progress-element/progress.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>The progress element</title>
+
+ <link rel="author" title="dan smith" href="mailto:XX1011@gmail.com">
+ <link rel="author" title="Tomoyuki SHIMIZU" href="mailto:tomoyuki.labs@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-progress-element">
+
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+
+ <progress id="indeterminate"></progress>
+ <progress id="removevalue" value="0.5"></progress>
+ <progress id="quarter" value="1" max="4"></progress>
+ <progress id="largerthanmax" value="2"></progress>
+ <progress id="invalidmax" value="1" max="a"></progress>
+ <progress id="negativemax" value="1" max="-1"></progress>
+ <progress id="invalidvalue" value="a"></progress>
+ <progress id="negativevalue" value="-1"></progress>
+
+ <script>
+
+ test(function() {
+ assert_equals(indeterminate.position, -1);
+ }, "Indeterminate progress bar should have position -1");
+
+ test(function() {
+ removevalue.removeAttribute('value');
+ assert_equals(removevalue.position, -1);
+ }, "Revoming the value attribute makes an intermediate progress bar, which should have position -1");
+
+ test(function() {
+ assert_equals(quarter.position, quarter.value / quarter.max);
+ }, "Determinate progress bar should have fractional position");
+
+ test(function() {
+ assert_equals(indeterminate.value, 0);
+ }, "Indeterminate progress bar should have value 0");
+
+ test(function() {
+ assert_equals(largerthanmax.value, 1);
+ }, "Value must equal max if the parsed value is larger than max");
+
+ test(function() {
+ assert_equals(indeterminate.max, 1);
+ }, "Max must be 1 by default");
+
+ test(function() {
+ assert_equals(largerthanmax.max, 1);
+ }, "Max must be 1 by default, even if value is specified");
+
+ test(function() {
+ assert_equals(invalidmax.max, 1);
+ }, "Max must be 1 if max value is invalid");
+
+ test(function() {
+ assert_equals(negativemax.max, 1);
+ }, "Max must be 1 if the parsed max value is less than or equal to zero");
+
+ test(function() {
+ assert_equals(invalidvalue.value, 0);
+ }, "Value must be 0 if value is invalid");
+
+ test(function() {
+ assert_equals(negativevalue.value, 0);
+ }, "Value must be 0 if the parsed value is less than or equal to zero");
+
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-progress-element/progress.window.js b/testing/web-platform/tests/html/semantics/forms/the-progress-element/progress.window.js
new file mode 100644
index 0000000000..37526053de
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-progress-element/progress.window.js
@@ -0,0 +1,18 @@
+test(() => {
+ const pr = document.createElement("progress");
+ assert_equals(pr.value, 0);
+ assert_equals(pr.position, -1);
+ pr.value = 2;
+ assert_equals(pr.value, 1);
+ assert_equals(pr.position, 1);
+}, "If value > max, then current value = max");
+
+test(() => {
+ const pr = document.createElement("progress");
+ pr.value = 2;
+ assert_equals(pr.value, 1);
+ assert_equals(pr.position, 1);
+ pr.max = 4;
+ assert_equals(pr.value, 2);
+ assert_equals(pr.position, 0.5);
+}, "If value < max, then current value = value");
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/common-HTMLOptionsCollection-add.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/common-HTMLOptionsCollection-add.html
new file mode 100644
index 0000000000..cf4306e271
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/common-HTMLOptionsCollection-add.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title id='title'>HTMLOptionsCollection</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<select id="selly">
+ <option id="id1" name="name1">1</option>
+ <option id="id2" name="name2">2</option>
+ <option id="id3" name="name3">3</option>
+ <option id="id4" name="name4">4</option>
+ <optgroup id="og1">
+ <option name="nameonly">n1</option>
+ <option id="id5">5</option>
+ </optgroup>
+ <optgroup id="og2">
+ <option name="nameonly">n2</option>
+ <option id="id6">6</option>
+ </optgroup>
+
+</select>
+
+<script>
+var selly;
+setup(function() {
+ selly = document.getElementById('selly');
+});
+
+test(function () {
+ var option = document.getElementById('id1');
+ var optgroup = document.getElementById('og1');
+ selly.options.add(option, option);
+ selly.options.add(optgroup, optgroup);
+ assert_equals(selly.children.length, 6);
+ assert_equals(selly.length, 8);
+}, "if before and node are the same element nothing should be done");
+
+test(function () {
+ var o1 = document.createElement("option");
+ o1.value = "a";
+ var o2 = document.createElement("option");
+ o2.value = "b";
+ var o3 = document.createElement("option");
+ o3.value = "c";
+ var optgroup = document.getElementById('og1');
+ selly.options.add(o1, null);
+ selly.options.add(o2, optgroup);
+ selly.options.add(o3, 0);
+
+ var elarray = [];
+ for (var i = 0; i < selly.length; i++) {
+ elarray.push(selly[i].value);
+ }
+ assert_array_equals(elarray, ["c", "1", "2", "3", "4", "b", "n1", "5", "n2", "6", "a"]);
+}, "add method should add option elements correctly");
+
+test(function () {
+ var og1 = document.createElement("optgroup");
+ var o1 = document.createElement("option");
+ o1.value = "a";
+ o1.appendChild(og1);
+ var og2 = document.createElement("optgroup");
+ var o2 = document.createElement("option");
+ o2.value = "b";
+ o2.appendChild(og2);
+ var og3 = document.createElement("optgroup");
+ var o3 = document.createElement("option");
+ o3.value = "c";
+ o3.appendChild(og3);
+
+ var optgroup = document.getElementById('og1');
+ selly.options.add(og1, null);
+ selly.options.add(og2, optgroup);
+ selly.options.add(og3, 0);
+
+ var elarray = [];
+ for (var i = 0; i < selly.length; i++) {
+ elarray.push(selly[i].value);
+ }
+ assert_array_equals(elarray, ["c", "1", "2", "3", "4", "b", "n1", "5", "n2", "6", "a"]);
+}, "add method should add option groups correctly");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/common-HTMLOptionsCollection-namedItem.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/common-HTMLOptionsCollection-namedItem.html
new file mode 100644
index 0000000000..c5c8510a47
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/common-HTMLOptionsCollection-namedItem.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title id='title'>HTMLOptionsCollection</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<select id="selly">
+ <option id="id1" name="name1">1</option>
+ <option id="id2" name="name2">2</option>
+ <option id="id3" name="name3">3</option>
+ <option id="id4" name="name4">4</option>
+ <option name="nameonly">nameonly</option>
+ <option id="id3">duplicate ID</option>
+ <option name="name4">duplicate name</option>
+ <option id="mixed1">mixed ID</option>
+ <option name="mixed1">mixed name</option>
+</select>
+
+<script>
+var selly;
+setup(function() {
+ selly = document.getElementById('selly');
+});
+
+test(function () {
+ assert_equals(selly.namedItem('nameonly')["value"], "nameonly");
+}, "if only one item has a *name* or id value matching the parameter, return that object and stop");
+
+test(function () {
+ assert_equals(selly.namedItem('id2')["value"], "2");
+}, "if only one item has a name or *id* value matching the parameter, return that object and stop");
+
+test(function () {
+ assert_equals(selly.namedItem('thisdoesnotexist'), null);
+}, "if no item has a name or id value matching the parameter, return null and stop");
+
+test(function () {
+ assert_equals(selly.namedItem('id3')["value"], "3");
+}, "if multiple items have a name or *id* value matching the parameter, return the first object and stop");
+
+test(function () {
+ assert_equals(selly.namedItem('name4')["value"], "4");
+}, "if multiple items have a *name* or id value matching the parameter, return the first object and stop");
+
+test(function () {
+ assert_equals(selly.namedItem('mixed1')["value"], "mixed ID");
+}, "if multiple items have a *name* or *id* value matching the parameter, return the first object and stop");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/common-HTMLOptionsCollection.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/common-HTMLOptionsCollection.html
new file mode 100644
index 0000000000..737e9be876
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/common-HTMLOptionsCollection.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title id='title'>HTMLOptionsCollection</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<select id="selly">
+ <option>1</option>
+ <option>2</option>
+ <option>3</option>
+ <option>4</option>
+</select>
+
+<script>
+var selly;
+setup(function() {
+ selly = document.getElementById('selly');
+});
+
+test(function () {
+ assert_equals(selly.length, 4);
+}, "On getting, the length attribute must return the number of nodes represented by the collection.");
+
+test(function () {
+ selly.length = 7;
+ assert_equals(selly.length, 7,
+ "Number of nodes in collection should have changed");
+ assert_equals(selly.children.length, 7,
+ "Number of children should have changed");
+ for (var i = 4; i < 7; ++i) {
+ var child = selly.children[i];
+ assert_equals(child.localName, "option",
+ "new child should be an option");
+ assert_equals(child.namespaceURI, "http://www.w3.org/1999/xhtml",
+ "new child should be an HTML element");
+ assert_equals(child.attributes.length, 0,
+ "new child should not have attributes");
+ assert_equals(child.childNodes.length, 0,
+ "new child should not have child nodes");
+ }
+}, "Changing the length adds new nodes; The number of new nodes = new length minus old length");
+
+test(function () {
+ var elarray = [];
+ for (var i = 0; i < selly.length; i++) {
+ elarray.push(selly[i].value);
+ }
+ assert_array_equals(elarray, ["1", "2", "3", "4", "", "", ""]);
+}, "New nodes have no value");
+
+test(function () {
+ selly.length = 7;
+ assert_equals(selly.length, 7,
+ "Number of nodes in collection should not have changed");
+ assert_equals(selly.children.length, 7,
+ "Number of children should not have changed");
+}, "Setting a length equal to existing length changes nothing");
+
+test(function () {
+ selly.length = 4;
+ assert_equals(selly[6], undefined,
+ "previously set node is now undefined");
+ assert_equals(selly.length, 4,
+ "Number of nodes in collection is correctly changed");
+ assert_equals(selly.children.length, 4,
+ "Number of children should have changed");
+}, "Setting a length lower than the old length trims nodes from the end");
+
+test(function () {
+ var opts = selly.options;
+ opts[3] = null;
+ assert_equals(selly[3], undefined,
+ "previously set node is now undefined");
+ assert_equals(selly.length, 3,
+ "Number of nodes in collection is correctly changed");
+ assert_equals(selly.children.length, 3,
+ "Number of children should have changed");
+}, "Setting element to null by index removed the element");
+
+test(function () {
+ var opts = selly.options;
+ var new_option = document.createElement("option");
+ var replace_option = new_option.cloneNode(true);
+ new_option.value = "-1";
+ replace_option.value = "a";
+ opts[5] = new_option;
+ opts[0] = replace_option;
+
+ var elarray = [];
+ for (var i = 0; i < selly.length; i++) {
+ elarray.push(selly[i].value);
+ }
+ assert_array_equals(elarray, ["a", "2", "3", "", "", "-1"]);
+
+}, "Setting element by index should correctly append and replace elements");
+
+test(function () {
+ var selection = document.createElementNS("http://www.w3.org/1999/xhtml", "foo:select");
+
+ selection.length = 5;
+ assert_equals(selection.length, 5,
+ "Number of nodes in collection should have changed");
+ for (var i = 0; i < 5; ++i) {
+ var child = selection.children[i];
+ assert_equals(child.localName, "option",
+ "new child should be an option");
+ assert_equals(child.namespaceURI, "http://www.w3.org/1999/xhtml",
+ "new child should be an HTML element");
+ assert_equals(child.prefix, null,
+ "new child should not copy select's prefix");
+ }
+
+}, "Changing the length adds new nodes; The new nodes will not copy select's prefix");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/inserted-or-removed.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/inserted-or-removed.html
new file mode 100644
index 0000000000..ee12316421
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/inserted-or-removed.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/C/#the-select-element:nodes-are-inserted">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+
+<select id="by-parser">
+<option selected>First</option>
+<option selected>Second</option>
+</select>
+
+<select id="by-parser-optgroup">
+<optgroup>
+<option selected>First</option>
+<option selected>Second</option>
+</optgroup>
+</select>
+
+<select id="by-dom"></select>
+
+<select id="by-innerHTML"></select>
+
+<script>
+test(() => {
+ const target = document.querySelector("#by-parser");
+ assert_equals(target.selectedOptions[0].textContent, 'Second');
+
+ const target2 = document.querySelector("#by-parser-optgroup");
+ assert_equals(target2.selectedOptions[0].textContent, 'Second');
+}, 'The last selected OPTION should win; Inserted by parser');
+
+test(() => {
+ const target = document.querySelector("#by-dom");
+ const option1 = document.createElement('option');
+ option1.defaultSelected = true;
+ option1.textContent = 'First';
+ const option2 = document.createElement('option');
+ option2.defaultSelected = true;
+ option2.textContent = 'Second';
+ target.appendChild(option1);
+ target.appendChild(option2);
+ assert_equals(target.selectedOptions[0].textContent, 'Second');
+
+ target.innerHTML = '';
+ const optgroup = document.createElement('optgroup');
+ const option3 = document.createElement('option');
+ option3.defaultSelected = true;
+ option3.textContent = 'First';
+ const option4 = document.createElement('option');
+ option4.defaultSelected = true;
+ option4.textContent = 'Second';
+ optgroup.appendChild(option3);
+ optgroup.appendChild(option4);
+ target.appendChild(optgroup);
+ assert_equals(target.selectedOptions[0].textContent, 'Second');
+}, 'The last selected OPTION should win; Inserted by DOM API');
+
+test(() => {
+ const target = document.querySelector("#by-dom");
+ target.innerHTML = '';
+ const inner = `<option value="one" selected>First</option>
+ <option value="two" selected>Second</option>`;
+
+ // Emulate what jQuery 1.x/2.x does in append(inner).
+ const fragment = document.createDocumentFragment();
+ const div = document.createElement('div');
+ div.innerHTML = '<select multiple>' + inner + '</select>';
+ while (div.firstChild.firstChild)
+ fragment.appendChild(div.firstChild.firstChild);
+ target.appendChild(fragment);
+ assert_equals(target.selectedOptions[0].textContent, 'Second');
+}, 'The last selected OPTION should win; Inserted by jQuery append()');
+
+test(() => {
+ const target = document.querySelector("#by-innerHTML");
+ target.innerHTML = '<option selected>First</option>' +
+ '<option selected>Second</option>';
+ assert_equals(target.selectedOptions[0].textContent, 'Second');
+
+ target.innerHTML = '<option selected>First</option>' +
+ '<optgroup><option selected>Second</option>' +
+ '<option selected>Third</option></optgroup>' +
+ '<option selected>Fourth</option>';
+ assert_equals(target.selectedOptions[0].textContent, 'Fourth');
+}, 'The last selected OPTION should win; Inserted by innerHTML');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/reset-algorithm-rendering-ref.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/reset-algorithm-rendering-ref.html
new file mode 100644
index 0000000000..acf192d1d5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/reset-algorithm-rendering-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<body>
+
+<form>
+<select>
+<option>Default</option>
+<option>Another</option>
+</select>
+
+<select>
+<option>Another</option>
+<option selected>Default</option>
+</select>
+
+<select multiple>
+<option>option 1</option>
+<option>option 2</option>
+</select>
+</form>
+
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/reset-algorithm-rendering.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/reset-algorithm-rendering.html
new file mode 100644
index 0000000000..67da173ff2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/reset-algorithm-rendering.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Invalidation test on resetting &lt;select></title>
+<link rel="help" href="https://html.spec.whatwg.org/C/#the-select-element:concept-form-reset-control">
+<link rel="help" href="http://crbug.com/1087031">
+<link rel="match" href="reset-algorithm-rendering-ref.html">
+<body>
+
+<form>
+<select>
+<option>Default</option>
+<option>Another</option>
+</select>
+
+<select>
+<option>Another</option>
+<option selected>Default</option>
+</select>
+
+<select multiple>
+<option>option 1</option>
+<option>option 2</option>
+</select>
+</form>
+
+<script>
+const selects = document.querySelectorAll('select');
+selects[0].selectedIndex = 1;
+selects[1].selectedIndex = 0;
+selects[2].options[1].selected = true;
+
+document.documentElement.addEventListener('TestRendered', e => {
+ document.querySelector('form').reset();
+ e.target.removeAttribute('class');
+});
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-add-option-crash.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-add-option-crash.html
new file mode 100644
index 0000000000..78e9e7de53
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-add-option-crash.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1178128">
+
+<script>
+function iframeloadhandler() {
+ selectid[5] = optionid;
+}
+</script>
+<option id="optionid" selected>
+ <select id="selectid">
+ <select>
+ <iframe onload="iframeloadhandler()">
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-add.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-add.html
new file mode 100644
index 0000000000..910be348ae
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-add.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLSelectElement Test: add()</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-add-dev">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<form style="display:none">
+ <option id="testoption">
+ <select id="testselect1">
+ </select>
+ <select id="testselect2">
+ <option>TEST</option>
+ </select>
+ </option>
+</form>
+
+<script>
+
+test(() => {
+ let testselect1 = document.getElementById("testselect1");
+ let opt1 = new Option("Marry","1");
+ testselect1.add(opt1);
+ assert_equals(testselect1.options[0].value, "1");
+}, "test that HTMLSelectElement.add method can add option element");
+
+test(() => {
+ let testselect2 = document.getElementById("testselect2");
+ let opt2 = document.getElementById("testoption");
+ assert_throws_dom("HierarchyRequestError", () => {
+ testselect2.add(opt2);
+ });
+}, "test that HierarchyRequestError exception must be thrown when element is an ancestor of the element into which it is to be inserted");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-ask-for-reset.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-ask-for-reset.html
new file mode 100644
index 0000000000..822114fb26
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-ask-for-reset.html
@@ -0,0 +1,97 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLSelectElement ask for reset</title>
+<link rel="author" title="Dongie Agnir" href="dongie.agnir@gmail.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var select = makeSelect(5);
+
+ select.children[4].selected = true;
+ unselectedExcept(select, 4);
+
+ select.children[4].remove();
+ unselectedExcept(select, 0); // remove selected node, should default to first
+
+ select.children[3].selected = true;
+
+ select.children[0].remove();
+ unselectedExcept(select, 2); // last node still selected
+
+ select.size = 2;
+ select.children[2].remove();
+
+ unselectedExcept(select, null);
+}, "ask for reset on node remove, non multiple.");
+
+test(function() {
+ var select = makeSelect(3);
+ select.children[1].selected = true;
+
+ // insert selected option, should remain selected
+ var opt4 = document.createElement("option");
+ opt4.selected = true;
+ select.appendChild(opt4);
+ unselectedExcept(select, 3);
+
+ // insert unselected, 3 should remain selected
+ var opt5 = document.createElement("option");
+ select.appendChild(opt5);
+ unselectedExcept(select, 3);
+}, "ask for reset on node insert, non multiple.");
+
+test(function() {
+ var select = makeSelect(3);
+
+ var options = select.children;
+
+ // select options from first to last
+ for (var i = 0; i < options.length; ++i) {
+ options[i].selected = true;
+ unselectedExcept(select, i);
+ }
+
+ // select options from last to first
+ for (var i = options.length - 1; i >= 0; --i) {
+ options[i].selected = true;
+ unselectedExcept(select, i);
+ }
+
+ options[2].selected = true;
+ options[2].selected = false; // none selected
+ unselectedExcept(select, 0);
+
+ // disable first so option at index 1 is first eligible
+ options[0].disabled = true;
+ options[2].selected = true;
+ options[2].selected = false; // none selected
+ unselectedExcept(select, 1);
+
+ select.size = 2;
+ options[1].selected = false;
+ unselectedExcept(select, null); // size > 1 so should not default to any
+}, "change selectedness of option, non multiple.");
+
+
+function unselectedExcept(sel, opt) {
+ for (var i = 0; i < sel.children.length; ++i) {
+ if (i != opt) {
+ assert_false(sel.children[i].selected, "option should not be selected.");
+ }
+ if (opt != null) {
+ assert_true(sel.children[opt].selected, "option should be selected.");
+ }
+ }
+}
+
+function makeSelect(n) {
+ var sel = document.createElement("select");
+ for (var i = 0; i < n; ++i) {
+ opt = document.createElement("option");
+ sel.appendChild(opt);
+ }
+ return sel;
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-multiple.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-multiple.html
new file mode 100644
index 0000000000..e348064151
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-multiple.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLSelectElement ask for reset</title>
+<link rel="author" title="Sebastian Mayr" href="wpt@smayr.name">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<select multiple id="initial-selected">
+ <option selected>Test 1</option>
+ <option selected>Test 2</option>
+</select>
+<select multiple id="scripted-select">
+ <option selected>Test 1</option>
+ <option>Test 2</option>
+</select>
+<div id=log></div>
+<script>
+"use strict";
+
+test(() => {
+
+ const select = document.getElementById("initial-selected");
+ assert_true(select.options[0].selected, "first option should be selected.");
+ assert_true(select.options[1].selected, "second option should be selected.");
+
+}, "multiple selected options exist, both set from markup");
+
+test(() => {
+
+ const select = document.getElementById("initial-selected");
+ select.options[1].selected = true;
+
+ assert_true(select.options[0].selected, "first option should be selected.");
+ assert_true(select.options[1].selected, "second option should be selected.");
+
+}, "multiple selected options exist, one set from script");
+
+// crbug.com/1245443
+test(() => {
+ let select = document.createElement("select");
+ select.length = 4;
+ let o1 = select.options.item(1);
+ select.multiple = true;
+ select.selectedIndex = 2;
+ o1.selected = true;
+ select.multiple = false;
+ assert_equals(select.selectedOptions.length, 1);
+}, "Removing multiple attribute reduces the number of selected OPTIONs to 1");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-named-getter.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-named-getter.html
new file mode 100644
index 0000000000..da43da9d92
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-named-getter.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Absence of a named getter on HTMLSelectElement</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<select id=select>
+ <option id=op1>A</option>
+ <option name=op2>B</option>
+ <option id=op3 name=op4>C</option>
+ <option id=>D</option>
+ <option name=>D</option>
+</select>
+<script>
+test(function() {
+ var select = document.getElementById("select");
+ assert_equals(select.op1, undefined);
+ assert_false("op1" in select);
+ assert_equals(select.namedItem("op1"), select.children[0]);
+}, "Option with id")
+
+test(function() {
+ var select = document.getElementById("select");
+ assert_equals(select.op2, undefined);
+ assert_false("op2" in select);
+ assert_equals(select.namedItem("op2"), select.children[1]);
+}, "Option with name")
+
+test(function() {
+ var select = document.getElementById("select");
+ assert_equals(select.op3, undefined);
+ assert_false("op3" in select);
+ assert_equals(select.namedItem("op3"), select.children[2]);
+
+ assert_equals(select.op4, undefined);
+ assert_false("op4" in select);
+ assert_equals(select.namedItem("op4"), select.children[2]);
+}, "Option with name and id")
+
+test(function() {
+ var select = document.getElementById("select");
+ assert_equals(select[""], undefined);
+ assert_false("" in select);
+ assert_equals(select.namedItem(""), null);
+}, "Empty string name");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-remove.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-remove.html
new file mode 100644
index 0000000000..cf2128bd15
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-remove.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLSelectElement.remove</title>
+<link rel="author" title="Ms2ger" href="Ms2ger@gmail.com">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-childnode-remove">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-select-remove">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-remove">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+function testRemove(getter, desc) {
+ test(function() {
+ var div = document.createElement("div");
+ var select = document.createElement("select");
+ div.appendChild(select);
+ assert_equals(select.parentNode, div);
+
+ var options = [];
+ for (var i = 0; i < 3; ++i) {
+ var option = document.createElement("option");
+ option.textContent = String(i);
+ select.appendChild(option);
+ options.push(option);
+ }
+
+ getter(select).remove(-1);
+ assert_array_equals(select.options, options, "After remove(-1)");
+ assert_equals(select.parentNode, div);
+
+ getter(select).remove(3);
+ assert_array_equals(select.options, options, "After remove(3)");
+ assert_equals(select.parentNode, div);
+
+ getter(select).remove(0);
+ assert_array_equals(select.options, [options[1], options[2]], "After remove(0)");
+ assert_equals(select.parentNode, div);
+ }, desc)
+}
+testRemove(function(select) { return select; }, "select.remove(n) should work");
+testRemove(function(select) { return select.options; }, "select.options.remove(n) should work");
+test(function() {
+ var div = document.createElement("div");
+ var select = document.createElement("select");
+ div.appendChild(select);
+ assert_equals(select.parentNode, div);
+ assert_equals(div.firstChild, select);
+
+ select.remove();
+ assert_equals(select.parentNode, null);
+ assert_equals(div.firstChild, null);
+}, "remove() should work on select elements.")
+test(function() {
+ var div = document.createElement("div");
+ var select = document.createElement("select");
+ div.appendChild(select);
+ assert_equals(select.parentNode, div);
+ assert_equals(div.firstChild, select);
+
+ Element.prototype.remove.call(select);
+ assert_equals(select.parentNode, null);
+ assert_equals(div.firstChild, null);
+}, "Element#remove() should work on select elements.")
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-selectedOptions.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-selectedOptions.html
new file mode 100644
index 0000000000..bd5984a6b2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-selectedOptions.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>HTMLSelectElement.selectedOptions</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#dom-select-selectedoptions">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+
+<select id="select-none-selected">
+ <option>One</option>
+ <option>Two</option>
+ <option>Three</option>
+</select>
+
+<select id="select-one-selected">
+ <option>One</option>
+ <option selected>Two</option>
+ <option>Three</option>
+</select>
+
+<select multiple id="multiple-select-none-selected">
+ <option>One</option>
+ <option>Two</option>
+ <option>Three</option>
+</select>
+
+<select multiple id="multiple-select-two-selected">
+ <option>One</option>
+ <option selected>Two</option>
+ <option selected>Three</option>
+</select>
+
+<select id="select-named-selected">
+ <option>One</option>
+ <option>Two</option>
+ <option id="named-option" selected>Three</option>
+</select>
+
+<select id="invalid-select">
+ <option selected>One</option>
+ <option selected>Two</option>
+ <option>Three</option>
+</select>
+
+<select id="select-same-object">
+ <option>One</option>
+ <option selected>Two</option>
+ <option>Three</option>
+</select>
+
+<select multiple id="select-same-object-change">
+ <option selected>One</option>
+ <option selected>Two</option>
+ <option selected>Three</option>
+</select>
+
+<script>
+"use strict";
+
+test(() => {
+ const select = document.getElementById("select-none-selected");
+
+ assert_array_equals(select.selectedOptions, [select.children[0]]);
+ assert_equals(select.selectedOptions.length, 1);
+
+}, ".selectedOptions with no selected option");
+
+test(() => {
+ const select = document.getElementById("select-one-selected");
+
+ assert_array_equals(select.selectedOptions, [select.children[1]]);
+ assert_equals(select.selectedOptions.length, 1);
+}, ".selectedOptions with one selected option");
+
+test(() => {
+ const select = document.getElementById("multiple-select-none-selected");
+
+ assert_equals(select.selectedOptions.item(0), null);
+ assert_equals(select.selectedOptions.length, 0);
+}, ".selectedOptions using the 'multiple' attribute with no selected options");
+
+test(() => {
+ const select = document.getElementById("multiple-select-two-selected");
+
+ assert_equals(select.selectedOptions.item(0), select.children[1]);
+ assert_equals(select.selectedOptions.item(1), select.children[2]);
+ assert_equals(select.selectedOptions.length, 2);
+}, ".selectedOptions using the 'multiple' attribute with two selected options");
+
+// "A select element whose multiple attribute is not specified must not have
+// more than one descendant option element with its selected attribute set."
+// - https://html.spec.whatwg.org/multipage/forms.html#the-option-element:the-select-element-6
+
+// "If two or more option elements in the select element's list of options
+// have their selectedness set to true, set the selectedness of all but
+// the last option element with its selectedness set to true in the list of
+// options in tree order to false."
+// - https://html.spec.whatwg.org/multipage/forms.html#the-select-element:the-option-element-21
+test(() => {
+ const select = document.getElementById("invalid-select");
+
+ assert_array_equals(select.selectedOptions, [select.children[1]]);
+ assert_equals(select.selectedOptions.length, 1);
+
+}, ".selectedOptions without the 'multiple' attribute but " +
+ "more than one selected option should return the last one");
+
+test(() => {
+ const select = document.getElementById("select-named-selected");
+
+ assert_equals(select.selectedOptions.constructor, HTMLCollection);
+ assert_equals(select.selectedOptions.namedItem("named-option"), select.children[2]);
+}, ".selectedOptions should return `HTMLCollection` instance");
+
+test(() => {
+ const select = document.getElementById("select-same-object");
+ const selectAgain = document.getElementById("select-same-object");
+
+ assert_equals(select.selectedOptions, selectAgain.selectedOptions);
+
+}, ".selectedOptions should always return the same value - [SameObject]");
+
+test(() => {
+ const select = document.getElementById("select-same-object-change");
+ const before = select.selectedOptions;
+ assert_equals(before.length, 3);
+
+ select.selectedOptions[1].selected = false;
+
+ const after = select.selectedOptions;
+
+ assert_equals(before, after);
+ assert_equals(before.length, 2);
+ assert_equals(after.length, 2);
+
+}, ".selectedOptions should return the same object after selection changes - [SameObject]");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-setcustomvalidity.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-setcustomvalidity.html
new file mode 100644
index 0000000000..15308c1a8f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-setcustomvalidity.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<title>select setCustomValidity</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<select id='select_test'></select>
+
+<script>
+
+test(() => {
+ let elem = document.getElementById("select_test");
+ assert_false(elem.validity.customError);
+ elem.setCustomValidity("custom error");
+ assert_true(elem.validity.customError);
+}, "select setCustomValidity is correct")
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-validity.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-validity.html
new file mode 100644
index 0000000000..9f044879d9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-validity.html
@@ -0,0 +1,124 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLSelectElement.checkValidity</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#the-select-element:attr-select-required-4">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+
+test(function() {
+ var select = document.createElement('select');
+ assert_true(select.willValidate, "A select element is a submittable element that is a candidate for constraint validation.");
+ var placeholder = document.createElement('option');
+ select.appendChild(placeholder);
+ assert_true(select.checkValidity(), "Always valid when the select isn't a required value.");
+ select.required = true;
+ assert_true(placeholder.selected, "If display size is 1, multiple is absent and no options have selectedness true, the first option is selected.");
+ assert_equals(select.value, "", "The placeholder's value should be the select's value right now");
+ assert_false(select.checkValidity(), "A selected placeholder option should invalidate the select.");
+ var emptyOption = document.createElement('option');
+ select.appendChild(emptyOption);
+ emptyOption.selected = true;
+ assert_equals(select.value, "", "The empty value should be set.");
+ assert_true(select.checkValidity(), "An empty non-placeholder option should be a valid choice.");
+ var filledOption = document.createElement('option');
+ filledOption.value = "test";
+ select.appendChild(filledOption);
+ filledOption.selected = true;
+ assert_equals(select.value, "test", "The non-empty value should be set.");
+ assert_true(select.checkValidity(), "A non-empty non-placeholder option should be a valid choice.");
+ select.removeChild(placeholder);
+ select.appendChild(emptyOption); // move emptyOption to second place
+ emptyOption.selected = true;
+ assert_equals(select.value, "", "The empty value should be set.");
+ assert_true(select.checkValidity(), "Only the first option can be seen as a placeholder.");
+ placeholder.disabled = true;
+ select.insertBefore(placeholder, filledOption);
+ placeholder.selected = true;
+ assert_equals(select.value, "", "A disabled first placeholder option should result in an empty value.");
+ assert_false(select.checkValidity(), "A disabled first placeholder option should invalidate the select.");
+}, "Placeholder label options within a select");
+
+test(function() {
+ var select = document.createElement('select');
+ select.required = true;
+ var optgroup = document.createElement('optgroup');
+ var emptyOption = document.createElement('option');
+ optgroup.appendChild(emptyOption);
+ select.appendChild(optgroup);
+ emptyOption.selected = true;
+ assert_equals(select.value, "", "The empty value should be set.");
+ assert_true(select.checkValidity(), "The first option is not considered a placeholder if it is located within an optgroup.");
+ var otherEmptyOption = document.createElement('option');
+ otherEmptyOption.value = "";
+ select.appendChild(otherEmptyOption);
+ otherEmptyOption.selected = true;
+ assert_equals(select.value, "", "The empty value should be set.");
+ assert_true(select.checkValidity(), "The empty option should be accepted as it is not the first option in the tree ordered list.");
+}, "Placeholder label-like options within optgroup");
+
+test(function() {
+ var select = document.createElement('select');
+ select.required = true;
+ select.size = 2;
+ var emptyOption = document.createElement('option');
+ select.appendChild(emptyOption);
+ assert_false(emptyOption.selected, "Display size is not 1, so the first option should not be selected.");
+ assert_false(select.checkValidity(), "If no options are selected the select must be seen as invalid.");
+ emptyOption.selected = true;
+ assert_true(select.checkValidity(), "If one option is selected, the select should be considered valid.");
+ var otherEmptyOption = document.createElement('option');
+ otherEmptyOption.value = "";
+ select.appendChild(otherEmptyOption);
+ otherEmptyOption.selected = true;
+ assert_false(emptyOption.selected, "Whenever an option has its selectiveness set to true, the other options must be set to false.");
+ otherEmptyOption.selected = false;
+ assert_false(otherEmptyOption.selected, "It should be possible to set the selectiveness to false with a display size more than one.");
+ assert_false(select.checkValidity(), "If no options are selected the select must be seen as invalid.");
+}, "Validation on selects with display size set as more than one");
+
+test(function() {
+ var select = document.createElement('select');
+ select.required = true;
+ select.multiple = true;
+ var emptyOption = document.createElement('option');
+ select.appendChild(emptyOption);
+ assert_false(select.checkValidity(), "If no options are selected the select must be seen as invalid.");
+ emptyOption.selected = true;
+ assert_true(select.checkValidity(), "If one option is selected, the select should be considered valid.");
+ var optgroup = document.createElement('optgroup');
+ optgroup.appendChild(emptyOption); // Move option to optgroup
+ select.appendChild(optgroup);
+ assert_true(select.checkValidity(), "If one option within an optgroup or not is selected, the select should be considered valid.");
+}, "Validation on selects with multiple set");
+
+test(function() {
+ var select = document.createElement('select');
+ select.required = true;
+ var option = document.createElement('option');
+ option.value = 'test';
+ option.disabled = true;
+ option.selected = true;
+ select.appendChild(option);
+ assert_true(select.checkValidity(), "When a required select has an option that is selected and disabled, the select should be considered valid.");
+}, "Validation on selects with non-empty disabled option");
+
+test(function() {
+ var select = document.createElement('select');
+ select.required = true;
+ var placeholder = document.createElement('option');
+ select.appendChild(placeholder);
+ var nonPlaceholder = document.createElement('option');
+ nonPlaceholder.textContent = "non-placeholder-option";
+ select.appendChild(nonPlaceholder);
+
+ assert_false(select.checkValidity(), "If the placeholder label option is selected, required select element shouldn't be valid.");
+ placeholder.remove();
+ assert_true(select.checkValidity(), "If the placeholder label option is removed, required select element should become valid.");
+ select.prepend(placeholder);
+ assert_false(select.checkValidity(), "If the placeholder label option is selected, required select element shouldn't be valid.");
+
+}, "Remove and add back the placeholder label option");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-value.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-value.html
new file mode 100644
index 0000000000..d8d5263e3e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-value.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>HTMLSelectElement.value</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#dom-select-value">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+
+<select id=sel1>
+ <option value=0></option>
+ <option selected value=1></option>
+</select>
+
+<select id=sel2>
+ <optgroup>
+ <option value=0></option>
+ </optgroup>
+ <optgroup></optgroup>
+ <optgroup>
+ <option></option>
+ <option value=1></option>
+ <option selected value=2></option>
+ </optgroup>
+</select>
+
+<select id=sel3>
+ <option selected value=1></option>
+</select>
+
+<select id=sel4></select>
+
+<script>
+test(function() {
+ var select = document.getElementById('sel1');
+ assert_equals(select.value, '1');
+}, 'options');
+
+test(function() {
+ var select = document.getElementById('sel2');
+ assert_equals(select.value, '2');
+}, 'optgroups');
+
+test(function() {
+ var select = document.getElementById('sel3');
+ var option = select.options[0];
+ var div = document.createElement('div');
+ select.appendChild(div);
+ div.appendChild(option);
+ assert_equals(select.value, '');
+}, 'option is child of div');
+
+test(function() {
+ var select = document.getElementById('sel4');
+ assert_equals(select.value, '');
+}, 'no options');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-willvalidate-readonly-attribute.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-willvalidate-readonly-attribute.html
new file mode 100644
index 0000000000..d3f4ce43cf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-willvalidate-readonly-attribute.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Select element with "readonly" attribute shouldn't be barred from constraint validation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<select id="singleSelect" readonly>
+ <option>1
+ <option>2
+</select>
+
+<select id="multiSelect" readonly multiple>
+ <option>a
+ <option>b
+ <option>c
+ <option>d
+</select>
+
+<script>
+ test(() => {
+ assert_true(singleSelect.willValidate);
+ assert_true(multiSelect.willValidate);
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/selected-index.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/selected-index.html
new file mode 100644
index 0000000000..7f7fd9a1a2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/selected-index.html
@@ -0,0 +1,143 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLSelectElement selectedIndex</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+
+<form id=form>
+ <select id=empty></select>
+
+ <select id=default>
+ <option></option>
+ <option></option>
+ <option></option>
+ <option></option>
+ <option></option>
+ </select>
+
+ <select id=disabled>
+ <option disabled></option>
+ <option></option>
+ </select>
+
+ <select id=selected>
+ <option></option>
+ <option selected></option>
+ </select>
+
+ <select id=display-none>
+ <option style="display:none"></option>
+ <option></option>
+ </select>
+
+ <select id=minus-one>
+ <option value=1>1</option>
+ <option value=2>2</option>
+ </select>
+</form>
+
+<script>
+function assertSelectedIndex(select, value) {
+ assert_equals(select.selectedIndex, value);
+ assert_equals(select.options.selectedIndex, value);
+}
+
+function assertSelectValue(select, value) {
+ assert_equals(select.value, value);
+}
+
+test(function () {
+ var select = document.getElementById('empty');
+ assertSelectedIndex(select, -1);
+}, "get empty");
+
+test(function () {
+ var select = document.getElementById('default');
+ assertSelectedIndex(select, 0);
+}, "get default");
+
+test(function () {
+ var select = document.getElementById('disabled');
+ assertSelectedIndex(select, 1);
+}, "get disabled");
+
+test(function () {
+ var select = document.getElementById('selected');
+ assertSelectedIndex(select, 1);
+}, "get unselected");
+
+test(function () {
+ var select = document.getElementById('empty');
+ select.selectedIndex = 1;
+ assertSelectedIndex(select, -1);
+}, "set empty (HTMLSelectElement)");
+
+test(function () {
+ var select = document.getElementById('empty');
+ select.options.selectedIndex = 1;
+ assertSelectedIndex(select, -1);
+}, "set empty (HTMLOptionsCollection)");
+
+test(function () {
+ var select = document.getElementById('default');
+ assertSelectedIndex(select, 0);
+ select.selectedIndex = 2;
+ assertSelectedIndex(select, 2);
+ this.add_cleanup(() => { select.selectedIndex = 0; });
+}, "set (HTMLSelectElement)");
+
+test(function () {
+ var select = document.getElementById('default');
+ assertSelectedIndex(select, 0);
+ select.options.selectedIndex = 2;
+ assertSelectedIndex(select, 2);
+ this.add_cleanup(() => { select.selectedIndex = 0; });
+}, "set (HTMLOptionsCollection)");
+
+test(function () {
+ var select = document.getElementById('selected');
+ var form = document.getElementById('form');
+ assertSelectedIndex(select, 1);
+ select.selectedIndex = 0;
+ assertSelectedIndex(select, 0);
+ form.reset();
+ assertSelectedIndex(select, 1);
+}, "set and reset (HTMLSelectElement)");
+
+test(function () {
+ var select = document.getElementById('selected');
+ var form = document.getElementById('form');
+ assertSelectedIndex(select, 1);
+ select.options.selectedIndex = 0;
+ assertSelectedIndex(select, 0);
+ form.reset();
+ assertSelectedIndex(select, 1);
+}, "set and reset (HTMLOptionsCollection)");
+
+test(function () {
+ var select = document.getElementById('display-none');
+ assertSelectedIndex(select, 0);
+}, "get display:none");
+
+test(function () {
+ var select = document.getElementById('display-none');
+ select.offsetTop; // force rendering
+ assertSelectedIndex(select, 0);
+ select.options[1].selected = true;
+ assertSelectedIndex(select, 1);
+ select.options[1].selected = false;
+ assertSelectedIndex(select, 0);
+}, "reset to display:none");
+
+test(function() {
+ var select = document.getElementById("minus-one");
+ assertSelectedIndex(select, 0);
+
+ select.selectedIndex = -1;
+
+ assertSelectedIndex(select, -1);
+ assertSelectValue(select, "");
+
+}, "set selectedIndex=-1");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-ask-for-reset.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-ask-for-reset.html
new file mode 100644
index 0000000000..3993c13950
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-ask-for-reset.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectMenuElement Test: ask-for-reset</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<form name="fm1" id="form1">
+ <selectmenu id="selectmenu1">
+ <option>one</option>
+ <option>two</option>
+ </selectmenu>
+
+ <selectmenu id="selectmenu2">
+ <option>one</option>
+ <option selected>two</option>
+ </selectmenu>
+
+ <selectmenu id="selectmenu3">
+ <option>one</option>
+ <option selected>two</option>
+ <option selected>three</option>
+ </selectmenu>
+</form>
+
+<script>
+function createSelectMenu(numberOfOptions) {
+ let selectMenu = document.createElement("selectmenu");
+ for (let i = 0; i < numberOfOptions; i++) {
+ let option = document.createElement("option");
+ option.value = i;
+ selectMenu.appendChild(option);
+ }
+ return selectMenu;
+}
+
+function checkSelection(selectMenu, selectedOptionIndex, msg) {
+ for (let i = 0; i < selectMenu.children.length; i++) {
+ if (i != selectedOptionIndex) {
+ assert_false(selectMenu.children[i].selected);
+ }
+ }
+ assert_true(selectMenu.children[selectedOptionIndex].selected, msg);
+ assert_equals(selectMenu.value, selectMenu.children[selectedOptionIndex].value);
+}
+
+test(() => {
+ let selectMenu = createSelectMenu(5);
+
+ selectMenu.children[4].selected = true;
+ checkSelection(selectMenu, 4);
+
+ selectMenu.children[4].remove();
+ checkSelection(selectMenu, 0, "After removing the selected option, selection should default to first option.");
+
+ selectMenu.children[3].selected = true;
+ checkSelection(selectMenu, 3);
+ selectMenu.children[0].remove();
+ checkSelection(selectMenu, 2, "Removing non-selected option should have no effect.");
+}, "ask-for-reset when removing option");
+
+test(() => {
+ let selectMenu = createSelectMenu(3);
+ selectMenu.children[1].selected = true;
+
+ let newOption = document.createElement("option");
+ newOption.selected = true;
+ selectMenu.appendChild(newOption);
+ checkSelection(selectMenu, 3, "Inserting a selected option should update selection.");
+
+ let newOption2 = document.createElement("option");
+ newOption2.selected = true;
+ selectMenu.prepend(newOption2);
+ checkSelection(selectMenu, 0, "Inserting a selected option should update selection, even though it's not last in tree order.");
+
+ let newOption3 = document.createElement("option");
+ selectMenu.appendChild(newOption3);
+ checkSelection(selectMenu, 0, "Inserting a non-selected option should have no effect.");
+}, "ask-for-reset when inserting option");
+
+test(() => {
+ let selectMenu = createSelectMenu(3);
+ let options = selectMenu.children;
+
+ // select options from first to last
+ for (let i = 0; i < options.length; i++) {
+ options[i].selected = true;
+ checkSelection(selectMenu, i);
+ }
+
+ // select options from last to first
+ for (let i = options.length - 1; i >= 0; i--) {
+ options[i].selected = true;
+ checkSelection(selectMenu, i);
+ }
+
+ options[2].selected = true;
+ checkSelection(selectMenu, 2);
+ options[2].selected = false;
+ checkSelection(selectMenu, 0, "First non-disabled option should be selected.");
+
+ options[0].disabled = true;
+ options[2].selected = true;
+ checkSelection(selectMenu, 2);
+ options[2].selected = false;
+ checkSelection(selectMenu, 1, "First non-disabled option should be selected.");
+}, "ask-for-reset when changing selectedness of option");
+
+test(() => {
+ let selectMenu1 = document.getElementById("selectmenu1");
+ let selectMenu2 = document.getElementById("selectmenu2");
+ let selectMenu3 = document.getElementById("selectmenu3");
+
+ document.getElementById("form1").reset();
+
+ assert_equals(selectMenu1.value, "one", "First non-disabled option should be selected.");
+ assert_equals(selectMenu2.value, "two", "The selected option should be selected.");
+ assert_equals(selectMenu3.value, "three", "Last selected option should be selected.")
+}, "ask-for-reset for form");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-events.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-events.tentative.html
new file mode 100644
index 0000000000..7e572b5418
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-events.tentative.html
@@ -0,0 +1,207 @@
+<!DOCTYPE html>
+<title>HTMLSelectMenuElement Test: events</title>
+<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>
+
+<selectmenu id="selectMenu0">
+ <div slot="button" behavior="button">
+ <span behavior="selected-value"></span>
+ <button id="selectMenu0-button">selectMenu0-button</button>
+ </div>
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectmenu>
+
+<selectmenu id="selectMenu1">
+ <option>one</option>
+ <option>
+ two
+ <button id="selectMenu1-button">selectMenu1-button</button>
+ </option>
+ <option>three</option>
+</selectmenu>
+
+<selectmenu id="selectMenu2">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectmenu>
+
+<selectmenu id="selectMenu3">
+ <option>same</option>
+ <option>same</option>
+</selectmenu>
+
+<selectmenu id="selectMenu4">
+ <option>one</option>
+ <option id="selectMenu4-option2">two</option>
+</selectmenu>
+
+<script>
+
+ function clickOn(element) {
+ const actions = new test_driver.Actions();
+ return actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ }
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu0");
+ const selectMenuButton = document.getElementById("selectMenu0-button");
+ assert_false(selectMenu.open);
+ const selectMenuButtonPromise = new Promise(async resolve => {
+ selectMenuButton.addEventListener("click", (e) => {
+ assert_false(selectMenu.open, "Listbox shouldn't have opened yet");
+ // PreventDefaulting the event here should prevent UA controller code
+ // on the button part from opening the listbox.
+ e.preventDefault();
+ resolve();
+ });
+ });
+
+ const selectMenuPromise = new Promise(async resolve => {
+ selectMenu.addEventListener("click", (e) => {
+ assert_true(e.defaultPrevented, "Event should have been defaultPrevented by selectMenuButton click handler");
+ assert_false(selectMenu.open, "Listbox shouldn't have opened, because click event was defaultPrevented.");
+ resolve();
+ });
+ });
+
+ await clickOn(selectMenuButton);
+ return Promise.all([selectMenuButtonPromise, selectMenuPromise]);
+ }, "Button controller code should not run if the click event is preventDefaulted.");
+
+ // See https://w3c.github.io/webdriver/#keyboard-actions
+ const KEY_CODE_MAP = {
+ 'Enter': '\uE007',
+ 'Space': '\uE00D',
+ 'ArrowUp': '\uE013',
+ 'ArrowDown': '\uE015'
+ };
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu1");
+ const selectMenuButton = document.getElementById("selectMenu1-button");
+ await clickOn(selectMenu);
+ assert_true(selectMenu.open);
+ const selectMenuButtonPromise = new Promise(async resolve => {
+ selectMenuButton.addEventListener("click", (e) => {
+ assert_true(selectMenu.open, "Listbox shouldn't have closed yet");
+ // PreventDefaulting the event here should prevent UA controller code
+ // on the listbox part from selecting the option and closing the listbox.
+ e.preventDefault();
+ resolve();
+ });
+ });
+
+ const selectMenuPromise = new Promise(async resolve => {
+ selectMenu.addEventListener("click", (e) => {
+ assert_true(e.defaultPrevented, "Event should have been defaultPrevented by selectMenuButton click handler");
+ assert_true(selectMenu.open, "Listbox shouldn't have closed, because keydown event was defaultPrevented.");
+ assert_equals(selectMenu.value, "one", "<selectmenu> shouldn't have changed value, because keydown event was defaultPrevented.");
+ resolve();
+ });
+ });
+
+ await clickOn(selectMenuButton);
+ return Promise.all([selectMenuButtonPromise, selectMenuPromise]);
+ }, "Listbox controller code should not run if the click event is preventDefaulted.");
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu2");
+ let input_event_count = 0;
+ let change_event_count = 0;
+
+ selectMenu.addEventListener("input", (e) => {
+ assert_true(e.composed, "input event should be composed");
+ assert_equals(input_event_count, 0, "input event should not fire twice");
+ assert_equals(change_event_count, 0, "input event should not fire before change");
+ input_event_count++;
+ });
+ selectMenu.addEventListener("change", (e) => {
+ assert_false(e.composed, "change event should not be composed");
+ assert_equals(input_event_count, 1, "change event should fire after input");
+ assert_equals(change_event_count, 0, "change event should not fire twice");
+ change_event_count++;
+ });
+
+ await clickOn(selectMenu);
+ assert_true(selectMenu.open);
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.Enter);
+ assert_false(selectMenu.open);
+ assert_equals(selectMenu.value, "one");
+ assert_equals(input_event_count, 0, "input event shouldn't fire if value wasn't changed");
+ assert_equals(change_event_count, 0, "change event shouldn't fire if value wasn't changed");
+
+ await clickOn(selectMenu);
+ assert_true(selectMenu.open);
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.ArrowDown);
+ assert_equals(selectMenu.value, "two", "value should change when user switches options with arrow key");
+ assert_equals(input_event_count, 1, "input event should fire when user switches options with arrow key");
+ assert_equals(change_event_count, 0, "change event shouldn't fire until popover is closed");
+
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.Enter);
+ assert_equals(selectMenu.value, "two");
+ assert_equals(input_event_count, 1, "input event should have fired");
+ assert_equals(change_event_count, 1, "change event should have fired");
+ }, "<selectmenu> should fire input and change events when new option is selected");
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu3");
+ let input_event_count = 0;
+ let change_event_count = 0;
+
+ selectMenu.addEventListener("input", (e) => {
+ assert_true(e.composed, "input event should be composed");
+ assert_equals(input_event_count, 0, "input event should not fire twice");
+ assert_equals(change_event_count, 0, "input event should not fire before change");
+ input_event_count++;
+ });
+ selectMenu.addEventListener("change", (e) => {
+ assert_false(e.composed, "change event should not be composed");
+ assert_equals(input_event_count, 1, "change event should fire after input");
+ assert_equals(change_event_count, 0, "change event should not fire twice");
+ change_event_count++;
+ });
+
+ await clickOn(selectMenu);
+ assert_true(selectMenu.open);
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.ArrowDown);
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.Enter);
+ assert_equals(input_event_count, 1, "input event should have fired");
+ assert_equals(change_event_count, 1, "change event should have fired");
+ }, "<selectmenu> should fire input and change events even when new selected option has the same value as the old");
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu4");
+ const selectMenuOption2 = document.getElementById("selectMenu4-option2");
+ let input_event_count = 0;
+ let change_event_count = 0;
+
+ selectMenu.addEventListener("input", (e) => {
+ assert_true(e.composed, "input event should be composed");
+ assert_equals(input_event_count, 0, "input event should not fire twice");
+ assert_equals(change_event_count, 0, "input event should not fire before change");
+ input_event_count++;
+ });
+
+ selectMenu.addEventListener("change", (e) => {
+ assert_false(e.composed, "change event should not be composed");
+ assert_equals(input_event_count, 1, "change event should fire after input");
+ assert_equals(change_event_count, 0, "change event should not fire twice");
+ change_event_count++;
+ });
+
+ await clickOn(selectMenu);
+ assert_true(selectMenu.open);
+ await clickOn(selectMenuOption2);
+ assert_equals(input_event_count, 1, "input event shouldn't fire when selected option didn't change");
+ assert_equals(change_event_count, 1, "change event shouldn't fire when selected option didn't change");
+ }, "<selectmenu> should fire input and change events when option in listbox is clicked");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-form-attribute.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-form-attribute.tentative.html
new file mode 100644
index 0000000000..16ee1b8045
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-form-attribute.tentative.html
@@ -0,0 +1,231 @@
+<!-- This test is tentative until the <selectmenu> gets adopted by standards. When that happens,
+ this test can be folded back into these tests:
+ html/semantics/forms/form-control-infrastructure/form_attribute.html
+ html/semantics/forms/form-control-infrastructure/form.html -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div>
+ <form id="f1"></form>
+ <form id="f2">
+ <input id="i1" />
+ <input id="i2" form="f1" />
+ <input id="i3" />
+ </form>
+ <script>
+ test(function() {
+ var i1 = document.getElementById("i1");
+ var i2 = document.getElementById("i2");
+ var i3 = document.getElementById("i3");
+ var f1 = document.getElementById("f1");
+ var f2 = document.getElementById("f2");
+
+ assert_equals(i1.form, f2,
+ "i1 must be associated with f2 by the parser");
+ assert_equals(i2.form, f1,
+ "i2 is not associated with f2 by the parser " +
+ "since it has the form attribute set to f1");
+
+ f1.appendChild(i1);
+ i3.setAttribute("form", "f1");
+
+ assert_equals(i1.form, f1,
+ "i1's form owner must be reset when parent changes");
+ assert_equals(i3.form, f1,
+ "i3's form owner must be reset when the form" +
+ "attribute is set");
+
+ assert_equals(i2.form, f1);
+ }, "Tests for parser inserted controls");
+ </script>
+ </div>
+
+ <div id="placeholder">
+ </div>
+
+ <script>
+ var reassociateableElements = [
+ "selectmenu",
+ ];
+
+ var form1 = null;
+ var form2 = null;
+ var placeholder = document.getElementById("placeholder");
+
+ reassociateableElements.forEach(function(localName) {
+ function testControl(test_, desc) {
+ test(function() {
+ var control = document.createElement(localName);
+
+ while(placeholder.firstChild)
+ placeholder.removeChild(placeholder.firstChild);
+
+ form1 = document.createElement("form");
+ form2 = document.createElement("form");
+ form1.id = "form1";
+ form2.id = "form2";
+ placeholder.appendChild(form1);
+ placeholder.appendChild(form2);
+
+ test_.call(control);
+ }, "[" + localName.toUpperCase() + "] " + desc);
+ }
+
+ testControl(function() {
+ form1.appendChild(this);
+ assert_equals(this.form, form1);
+ }, "Basic form association - control with no form attribute is associated with ancestor");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+ form1.appendChild(this);
+ assert_equals(this.form, form1);
+
+ form1.id = "form-one";
+ assert_equals(this.form, null);
+ }, "Form owner is reset to null when control's form attribute is set to an ID " +
+ "that does not exist in the document");
+
+ testControl(function() {
+ this.setAttribute("form", "");
+ form1.appendChild(this);
+ assert_equals(this.form, null);
+ }, "Control whose form attribute is an empty string has no form owner");
+
+ testControl(function() {
+ form1.id = "";
+ this.setAttribute("form", "");
+ form1.appendChild(this);
+ assert_equals(this.form, null);
+ }, "Control whose form attribute is an empty string has no form owner " +
+ "even when form with empty attribute is present");
+
+ testControl(function() {
+ form1.id = "FORM1";
+ this.setAttribute("form", "form1");
+ form1.appendChild(this);
+ assert_equals(this.form, null);
+ }, "Control's form attribute must be a case sensitive match for the form's id");
+
+ testControl(function() {
+ form1.appendChild(this);
+ assert_equals(this.form, form1);
+
+ this.setAttribute("form", "form2");
+ assert_equals(this.form, form2);
+ }, "Setting the form attribute of a control to the id of a non-ancestor form works");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+
+ form2.appendChild(this);
+ assert_equals(this.form, form1);
+
+ this.removeAttribute("form");
+ assert_equals(this.form, form2);
+ }, "Removing form id from a control resets the form owner to ancestor");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+
+ form2.appendChild(this);
+ assert_equals(this.form, form1);
+
+ placeholder.removeChild(form1);
+
+ assert_equals(this.form, null);
+ }, "Removing the form owner of a control with form attribute resets " +
+ "the form owner to null");
+
+ testControl(function() {
+ var form3 = document.createElement("form");
+ form3.id = "form3";
+ placeholder.appendChild(form3);
+ form3.appendChild(this);
+ assert_equals(this.form, form3);
+
+ this.setAttribute("form", "form2");
+ assert_equals(this.form, form2);
+
+ this.setAttribute("form", "form1");
+ assert_equals(this.form, form1);
+ }, "Changing form attibute of control resets form owner to correct form");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+ var form3 = document.createElement("form");
+ form3.id = "form3";
+
+ placeholder.appendChild(form3);
+ placeholder.appendChild(this);
+ assert_equals(this.form, form1);
+
+ form1.appendChild(this);
+ assert_equals(this.form, form1);
+
+ form2.appendChild(this);
+ assert_equals(this.form, form1);
+ }, "Moving a control with form attribute within the document " +
+ "does not change the form owner");
+
+ testControl(function() {
+ form1.id = "form-one";
+ this.setAttribute("form", "form1");
+ form2.appendChild(this);
+ assert_equals(this.form, null);
+
+ form1.id = "form1";
+ assert_equals(this.form, form1);
+ }, "When the id of a non-ancestor form changes from not being a match for the " +
+ "form attribute to being a match, the control's form owner is reset");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+ form1.appendChild(this);
+ assert_equals(this.form, form1);
+
+ form2.id = "form1";
+ form1.parentNode.insertBefore(form2, form1);
+ assert_equals(this.form, form2);
+
+ form2.parentNode.removeChild(form2);
+ assert_equals(this.form, form1);
+
+ form1.parentNode.appendChild(form2);
+ assert_equals(this.form, form1);
+ }, "When form element with same ID as the control's form attribute is inserted " +
+ "earlier in tree order, the form owner is changed to the inserted form");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+ form2.appendChild(this);
+ assert_equals(this.form, form1);
+
+ var span = document.createElement("span");
+ span.id = "form1";
+ form1.parentNode.insertBefore(span, form1);
+ assert_equals(this.form, null);
+
+ form1.parentNode.appendChild(span);
+ assert_equals(this.form, form1);
+ }, "When non-form element with same ID as the control's form attribute is " +
+ "inserted earlier in tree order, the control does not have a form owner");
+
+ testControl(function() {
+ this.setAttribute("form", "form1");
+ form1.appendChild(this);
+ assert_equals(this.form, form1);
+
+ form1.parentNode.removeChild(form1);
+ assert_equals(this.form, form1);
+ }, "A control that is not in the document but has the form attribute set " +
+ "is associated with the nearest ancestor form if one exists");
+
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-form-state-restore.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-form-state-restore.tentative.html
new file mode 100644
index 0000000000..1002355a5a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-form-state-restore.tentative.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectMenuElement Test: form state restore</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<input id="emptyOnFirstVisit">
+<form action="support/back.html" id="form0">
+<selectmenu id="selectmenu0">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectmenu>
+</form>
+
+<script>
+async_test(t => {
+ window.onload = () => t.step_timeout(() => {
+ let state = document.getElementById('emptyOnFirstVisit');
+ let selectMenu = document.getElementById("selectmenu0");
+
+ if (!state.value) {
+ // First visit.
+ t.step_timeout(() => {
+ state.value = 'visited';
+ assert_equals(selectMenu.value, "one");
+ selectMenu.value = "two";
+ // The form is submitted in a timeout to make sure that a new back/forward list item is created.
+ document.getElementById('form0').submit();
+ }, 0);
+ } else {
+ // Went back to this page again, and the form state should be restored.
+ assert_equals(selectMenu.value, "two");
+ t.done();
+ }
+ }, 1);
+}, "Test restoring state after form submission");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-form-submission.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-form-submission.tentative.html
new file mode 100644
index 0000000000..75a2b17e90
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-form-submission.tentative.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectMenuElement Test: form submission</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<form id="form0">
+ <selectmenu name="s0" id="selectmenu0">
+ <option selected>one</option>
+ <option>two</option>
+ <option>three</option>
+ </selectmenu>
+</form>
+
+<form id="form1">
+ <input type="text" name="i1" value="test">
+ <selectmenu id="selectmenu1">
+ <option selected>one</option>
+ <option>two</option>
+ <option>three</option>
+ </selectmenu>
+</form>
+
+<script>
+
+test(() => {
+ const form0 = document.getElementById("form0");
+ const selectMenu0 = document.getElementById("selectmenu0");
+ assert_equals(selectMenu0.value, "one");
+
+ const formData = new FormData(form0);
+ let entries = 0;
+ for (let entry of formData.entries()) {
+ assert_equals(entry[0], "s0");
+ assert_equals(entry[1], "one");
+ entries++;
+ }
+ assert_equals(entries, 1);
+}, "Test that HTMLSelectMenu.value is used for form submission");
+
+test(() => {
+ const form1 = document.getElementById("form1");
+ const selectMenu1 = document.getElementById("selectmenu1");
+ assert_equals(selectMenu1.value, "one");
+
+ const formData = new FormData(form1);
+ let entries = 0;
+ for (let entry of formData.entries()) {
+ assert_equals(entry[0], "i1");
+ assert_equals(entry[1], "test");
+ entries++;
+ }
+ assert_equals(entries, 1);
+}, "Test that HTMLSelectMenu.value is not used for form submission without name attribute");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-keyboard.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-keyboard.tentative.html
new file mode 100644
index 0000000000..9f937d7b70
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-keyboard.tentative.html
@@ -0,0 +1,123 @@
+<!doctype html>
+<title>HTMLSelectMenuElement Test: keyboard accessibility</title>
+<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>
+ <selectmenu id="selectMenu0">
+ <div id="selectMenu0-button0" slot="button" behavior="button" tabindex="0">button</div>
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+ </selectmenu>
+
+ <selectmenu id="selectMenu1">
+ <option id="selectMenu1-child0">one</option>
+ </selectmenu>
+
+ <selectmenu id="selectMenu2" disabled>
+ <div id="selectMenu2-button0" slot="button" behavior="button" tabindex="0">button</div>
+ <option disabled>one</option>
+ <option>two</option>
+ <option>three</option>
+ </selectmenu>
+
+ <selectmenu id="selectMenu3">
+ <div id="selectMenu3-button0" slot="button" behavior="button" tabindex="0">button</div>
+ <option>one</option>
+ <option disabled>two</option>
+ <option>three</option>
+ </selectmenu>
+<script>
+// See https://w3c.github.io/webdriver/#keyboard-actions
+const KEY_CODE_MAP = {
+ 'Enter': '\uE007',
+ 'Space': '\uE00D',
+ 'ArrowUp': '\uE013',
+ 'ArrowDown': '\uE015'
+};
+
+function clickOn(element) {
+ const actions = new test_driver.Actions();
+ return actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ }
+
+promise_test(async t => {
+ const selectMenu = document.querySelector("#selectMenu0");
+ const button = document.querySelector("#selectMenu0-button0");
+ assert_false(selectMenu.open, "selectmenu should not be initially open");
+
+ await test_driver.send_keys(button, KEY_CODE_MAP.Enter);
+ assert_true(selectMenu.open, "Enter key should open selectmenu");
+ assert_equals(selectMenu.value, "one");
+
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.ArrowDown);
+ assert_equals(selectMenu.value, "two", "Down arrow should go to next option");
+
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.ArrowDown);
+ assert_equals(selectMenu.value, "three", "Down arrow should go to next option");
+
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.ArrowDown);
+ assert_equals(selectMenu.value, "three", "Down arrow should do nothing if already at the last option");
+
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.ArrowUp);
+ assert_equals(selectMenu.value, "two", "Up arrow should go to the previous option");
+
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.ArrowUp);
+ assert_equals(selectMenu.value, "one", "Up arrow should go to the previous option");
+
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.ArrowUp);
+ assert_equals(selectMenu.value, "one", "Up arrow should do nothing if already at the first option");
+
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.Enter);
+ assert_false(selectMenu.open, "Enter key should close selectmenu");
+
+ await test_driver.send_keys(selectMenu, " ");
+ assert_true(selectMenu.open, "Space key should open selectmenu");
+
+ // This behavior is suspicious (since Space key can open the selectmenu),
+ // but it maches <select>. See https://github.com/openui/open-ui/issues/386
+ await test_driver.send_keys(selectMenu, " ");
+ assert_true(selectMenu.open, "Space key should *not* close selectmenu");
+
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.Enter);
+ assert_false(selectMenu.open, "Enter key should close selectmenu");
+}, "Validate Enter, Up/Down Arrow, and Space keyboard accessibility support for <selectmenu>");
+
+promise_test(async t => {
+ const selectMenuOption = document.getElementById("selectMenu1-child0");
+ const event = document.createEvent("Event");
+ event.initEvent("keydown");
+ selectMenuOption.dispatchEvent(event);
+}, "Firing a synthetic event at a selectmenu's option doesn't crash");
+
+promise_test(async t => {
+ const selectMenu2 = document.querySelector("#selectMenu2");
+ const selectMenu2Button = document.querySelector("#selectMenu2-button0");
+ assert_false(selectMenu2.open, "selectmenu should not be initially open");
+
+ await test_driver.send_keys(selectMenu2Button, KEY_CODE_MAP.Enter);
+ assert_false(selectMenu2.open, "Enter key should not open a disabled selectmenu");
+ await clickOn(selectMenu2);
+ assert_false(selectMenu2.open, "Click should not open a disabled selectmenu");
+ assert_equals(selectMenu2.value, "one");
+
+ const selectMenu3 = document.querySelector("#selectMenu3");
+ const selectMenu3Button = document.querySelector("#selectMenu3-button0");
+ assert_false(selectMenu3.open, "selectmenu should not be initially open");
+
+ await test_driver.send_keys(selectMenu3Button, KEY_CODE_MAP.Enter);
+ assert_true(selectMenu3.open, "Enter key should open selectmenu");
+ assert_equals(selectMenu3.value, "one");
+
+ await test_driver.send_keys(selectMenu3, KEY_CODE_MAP.ArrowDown);
+ assert_equals(selectMenu3.value, "three", "Down arrow should go to next non-disabled option");
+
+ await test_driver.send_keys(selectMenu3, KEY_CODE_MAP.ArrowUp);
+ assert_equals(selectMenu3.value, "one", "Up arrow should go to the previous non-disabled option");
+}, "Validate Enter, Up/Down Arrow keyboard accessibility support for disabled <selectmenu>");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-labels.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-labels.tentative.html
new file mode 100644
index 0000000000..6f94043299
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-labels.tentative.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectMenuElement Test: labels</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<label id="label1" for="selectmenu1">Label 1</label>
+<selectmenu id="selectmenu1">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+ <option>four</option>
+</selectmenu>
+<label id="label2" for="selectmenu1">Label 2</label>
+<label id="label3" for="invalid-selectmenu1">Label 3</label>
+
+<script>
+
+test(() => {
+ let selectMenu = document.getElementById("selectmenu1");
+
+ assert_true(selectMenu.labels instanceof NodeList);
+ assert_equals(selectMenu.labels.length, 2);
+ assert_equals(selectMenu.labels[0], document.getElementById("label1"));
+ assert_equals(selectMenu.labels[1], document.getElementById("label2"));
+}, "Validate selectmenu.labels");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-many-options.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-many-options.tentative.html
new file mode 100644
index 0000000000..b828326197
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-many-options.tentative.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<html>
+<title>HTMLSelectMenuElement Test: many options</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<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>
+
+<style>
+ #selectMenu0 {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ }
+
+ #selectMenu0-popover {
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ border-radius: 4px;
+ box-shadow: 0px 12.8px 28.8px rgba(0, 0, 0, 0.13), 0px 0px 9.2px rgba(0, 0, 0, 0.11);
+ box-sizing: border-box;
+ overflow: auto;
+ padding: 4px;
+ }
+</style>
+
+<selectmenu id="selectMenu0">
+ <div popover slot="listbox" behavior="listbox" id="selectMenu0-popover">
+ <option>bottom left</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ <option>two</option>
+ <option>three</option>
+ </div>
+</selectmenu>
+<br>
+
+<script>
+ function clickOn(element) {
+ const actions = new test_driver.Actions();
+ return actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ }
+
+ promise_test(async () => {
+ const selectMenu0 = document.getElementById("selectMenu0");
+ const selectMenu0Popover = document.getElementById("selectMenu0-popover");
+
+ await clickOn(selectMenu0);
+ assert_equals(Math.round(selectMenu0.getBoundingClientRect().bottom), Math.round(selectMenu0Popover.getBoundingClientRect().top));
+ assert_equals(Math.round(selectMenu0.getBoundingClientRect().left), Math.round(selectMenu0Popover.getBoundingClientRect().left));
+ assert_equals(window.innerHeight, Math.round(selectMenu0Popover.getBoundingClientRect().bottom));
+ }, "The popover should be bottom left positioned");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-nested.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-nested.tentative.html
new file mode 100644
index 0000000000..06b4fb6eb2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-nested.tentative.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectMenuElement Test: nested selects</title>
+<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>
+
+<selectmenu id="selectMenu0">
+ <div popover slot="listbox" behavior="listbox">
+ <selectmenu id="nested0">
+ <option id="child1">one</option>
+ <option id="child2">two</option>
+ </selectmenu>
+ <option id="child3">three</option>
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu1">
+ <div popover slot="listbox" behavior="listbox">
+ <select>
+ <option>one</option>
+ <option>two</option>
+ </select>
+ <option>three</option>
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu2">
+ <div slot="button">
+ <selectmenu id="nested2">
+ <div slot="button" behavior="button" id="selectMenu2-button0">button0</div>
+ <option id="nested2-option1">one</option>
+ </selectmenu>
+ <div behavior="button" id="selectMenu2-button1">button1</div>
+ </div>
+ <option>two</option>
+</selectmenu>
+
+<script>
+ function clickOn(element) {
+ const actions = new test_driver.Actions();
+ return actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ }
+
+ promise_test(async () => {
+ const selectMenu0 = document.getElementById("selectMenu0");
+ const nested0 = document.getElementById("nested0");
+ const child2 = document.getElementById("child2");
+ assert_equals(selectMenu0.value, "three", "Options nested in another <selectmenu> should not get controller code from outer <selectmenu>");
+ await clickOn(selectMenu0);
+ assert_true(selectMenu0.open);
+ assert_false(nested0.open);
+
+ await clickOn(nested0);
+ assert_true(nested0.open);
+
+ await clickOn(child2);
+ assert_false(nested0.open);
+ assert_equals(nested0.value, "two");
+ assert_true(selectMenu0.open, "click on option in inner <selectmenu> should not close outer <selectmenu>");
+ assert_equals(selectMenu0.value, "three", "click on option in inner <selectmenu> should not change value of outer <selectmenu>");
+ }, "A <selectmenu> shouldn't apply controller code to parts nested in a <selectmenu> child");
+
+ promise_test(async () => {
+ const selectMenu1 = document.getElementById("selectMenu1");
+ assert_equals(selectMenu0.value, "three");
+ }, "A <selectmenu> shouldn't apply controller code to parts nested in a <select> child");
+
+ promise_test(async () => {
+ const selectMenu2 = document.getElementById("selectMenu2");
+ const nested2 = document.getElementById("nested2");
+ const button0 = document.getElementById("selectMenu2-button0");
+ const button1 = document.getElementById("selectMenu2-button1");
+ const nested2Option1 = document.getElementById("nested2-option1");
+ assert_false(selectMenu2.open);
+ assert_false(nested2.open);
+
+ await clickOn(button0);
+ assert_false(selectMenu2.open, "Clicking the button of a nested <selectmenu> should not open the outer <selectmenu>");
+ assert_true(nested2.open, "Clicking the button of a nested <selectmenu> should open the outer <selectmenu>");
+
+ await clickOn(nested2Option1);
+ assert_false(nested2.open);
+
+ await clickOn(button1);
+ assert_true(selectMenu2.open);
+ assert_false(nested2.open);
+ }, "A nested button part in a nested <selectmenu> shouldn't get controller code even if it comes first in document order");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-displayed-ref.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-displayed-ref.tentative.html
new file mode 100644
index 0000000000..3933b93894
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-displayed-ref.tentative.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<link rel="stylesheet" href="/fonts/ahem.css">
+
+<selectmenu id="selectMenu0">
+ <div popover slot="listbox" behavior="listbox">
+ <option>
+ option with image displayed
+ <img src="/images/green-256x256.png">
+ </option>
+ </div>
+</selectmenu>
+<div id=fakelistbox>
+ option with image displayed
+ <img src="/images/green-256x256.png">
+</div>
+
+<style>
+ html {
+ font-family: Ahem;
+ }
+ #fakelistbox {
+ /* Per spec: */
+ display: block;
+ position: fixed;
+ top: 20px;
+ left: 0;
+ /* Per settings in test file: */
+ width: fit-content;
+ height: fit-content;
+ background: -internal-light-dark(white, black);
+ color: -internal-light-dark(black, white);
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ border-radius: 0px;
+ box-shadow: 0px 12.8px 28.8px rgba(0, 0, 0, 0.13), 0px 0px 9.2px rgba(0, 0, 0, 0.11);
+ box-sizing: border-box;
+ overflow: auto;
+ padding: 4px;
+ }
+
+ selectmenu {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ height: 20px;
+ }
+</style>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-displayed.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-displayed.tentative.html
new file mode 100644
index 0000000000..0fad1a1918
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-displayed.tentative.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>HTMLSelectMenuElement Test: option arbitrary content displayed</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<link rel=match href="selectmenu-option-arbitrary-content-displayed-ref.tentative.html">
+<link rel="stylesheet" href="/fonts/ahem.css">
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<style>
+ html {
+ font-family: Ahem;
+ }
+ selectmenu {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ height: 20px;
+ }
+
+ [popover] {
+ width: fit-content;
+ height: fit-content;
+ background: white;
+ color: black;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ border-radius: 0px;
+ box-shadow: 0px 12.8px 28.8px rgba(0, 0, 0, 0.13), 0px 0px 9.2px rgba(0, 0, 0, 0.11);
+ box-sizing: border-box;
+ overflow: auto;
+ padding: 4px;
+ }
+
+ option {
+ background-color: white !important;
+ padding: 0px;
+ }
+</style>
+
+<selectmenu id="selectMenu0">
+ <div popover slot="listbox" behavior="listbox">
+ <option>
+ option with image displayed
+ <img src="/images/green-256x256.png">
+ </option>
+ </div>
+</selectmenu>
+
+<script>
+ function clickOn(element) {
+ const actions = new test_driver.Actions();
+ return actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ }
+
+ async function test() {
+ const selectMenu0 = document.getElementById("selectMenu0");
+
+ await clickOn(selectMenu0);
+ document.documentElement.classList.remove('reftest-wait');
+ }
+
+ test();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-not-displayed-ref.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-not-displayed-ref.tentative.html
new file mode 100644
index 0000000000..d12943105f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-not-displayed-ref.tentative.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+
+<option>
+ option with image not displayed
+</option>
+
+<selectmenu>
+</selectmenu>
+
+<option>
+ option with image not displayed
+</option> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-not-displayed.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-not-displayed.tentative.html
new file mode 100644
index 0000000000..7bc936e7dc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-not-displayed.tentative.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<title>HTMLSelectMenuElement Test: option arbitrary content not displayed</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<link rel=match href="selectmenu-option-arbitrary-content-not-displayed-ref.tentative.html">
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<option>
+ option with image not displayed
+ <img src="/images/green-256x256.png">
+</option>
+
+<selectmenu id="selectMenu0">
+ <option id="selectMenu0-option">
+ option with image not displayed
+ <img src="/images/green-256x256.png">
+ </option>
+</selectmenu>
+
+<script>
+ const selectMenu0Option = document.getElementById("selectMenu0-option");
+
+ // removing an option from <selectmenu> should revert back to its original display behavior
+ selectMenu0Option.remove();
+ document.body.append(selectMenu0Option);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-focusable.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-focusable.tentative.html
new file mode 100644
index 0000000000..a78b972a2a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-option-focusable.tentative.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectMenuElement Test: option facusable</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<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>
+
+<selectmenu id="selectmenu0">
+ <option>one</option>
+ <option id="selectmenu0-option2">two</option>
+ <option>three</option>
+</selectmenu>
+
+<script>
+// See https://w3c.github.io/webdriver/#keyboard-actions
+const KEY_CODE_MAP = {
+ 'Enter': '\uE007',
+ 'Space': '\uE00D',
+ 'ArrowUp': '\uE013',
+ 'ArrowDown': '\uE015'
+};
+
+function clickOn(element) {
+ const actions = new test_driver.Actions();
+ return actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+}
+
+promise_test(async t => {
+ const selectMenu = document.querySelector("#selectmenu0");
+ assert_false(selectMenu.open, "selectmenu should not be initially open");
+
+ await clickOn(selectMenu);
+ assert_true(selectMenu.open);
+ assert_equals(selectMenu.value, "one");
+
+ const option2 = document.querySelector('#selectmenu0-option2');
+ option2.focus();
+ assert_equals(document.activeElement, option2);
+
+ await test_driver.send_keys(selectMenu, KEY_CODE_MAP.Enter);
+ assert_equals(selectMenu.value, "two");
+}, "Validate <option> is focusable when is a descendant of <selectmenu>");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-parts-structure.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-parts-structure.tentative.html
new file mode 100644
index 0000000000..4f49d3f0ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-parts-structure.tentative.html
@@ -0,0 +1,530 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectMenuElement Test: part structure</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<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>
+
+<selectmenu id="selectMenu0">
+ <div popover slot="listbox" behavior="listbox">
+ <option id="selectMenu0-child1">one</option>
+ <option id="selectMenu0-child2">two</option>
+ <div behavior="option" id="selectMenu0-child3">three</div>
+ </div>
+ <option id="selectMenu0-child4">four</option>
+</selectmenu>
+
+<selectmenu id="selectMenu1">
+ <div popover slot="listbox" behavior="listbox" id="selectMenu1-popover">
+ <div behavior="button" id="selectMenu1-button">
+ Custom button
+ </div>
+ <option>one</option>
+ <option id="selectMenu1-child2">two</option>
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu2">
+ <div slot="button" behavior="button" id="selectMenu2-button">
+ Custom button
+ <div popover behavior="listbox" id="selectMenu2-popover">
+ <option>one</option>
+ <option id="selectMenu2-child2">two</option>
+ </div>
+ </div>
+ <option>three</option>
+ <div>
+ This is some text.
+ <option id="selectMenu2-child4">four</option>
+ More text.
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu3">
+ <div slot="button" id="selectMenu3-button-slot">
+ <div behavior="button" id="selectMenu3-button0">button0</div>
+ </div>
+ <option>one</option>
+</selectmenu>
+
+<selectmenu id="selectMenu4">
+ <div slot="button" behavior="button" id="selectMenu4-button0">button0</div>
+ <div slot="listbox" id="selectMenu4-listbox-slot">
+ <div popover behavior="listbox" id="selectMenu4-listbox0">
+ <option>one</option>
+ <option id="selectMenu4-option2">two</option>
+ </div>
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu5">
+ <div slot="button" id="selectMenu5-button-slot">
+ <div behavior="button" id="selectMenu5-button0">button0</div>
+ <div behavior="selected-value" id="selectMenu5-selectedValue0"></div>
+ </div>
+ <option>one</option>
+ <option id="selectMenu5-option0">two</option>
+</selectmenu>
+
+<!-- No associated JS test -- just don't crash when parsing! -->
+<selectmenu id="selectMenu6">
+ <div slot="button"></div>
+ <div popover slot="listbox" behavior="listbox"></div>
+</selectmenu>
+
+<!-- No associated JS test -- just don't crash when parsing! -->
+<selectmenu id="selectMenu7">
+ <div slot="listbox"></div>
+ <div slot="button" behavior="button"></div>
+</selectmenu>
+
+<!-- No associated JS test -- just don't crash when parsing! -->
+<selectmenu id="selectMenu8">
+ <div slot="listbox"></div>
+ <option>one</option>
+</selectmenu>
+
+<selectmenu id="selectMenu9">
+ <div slot="listbox" id="selectMenu9-listbox-slot">
+ <div popover behavior="listbox" id="selectMenu9-originalListbox">
+ <option>one</option>
+ <option id="selectMenu9-option2">two</option>
+ </div>
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu10">
+ <option slot="button" id="selectMenu10-slottedOption">Test 10</option>
+ <option>one</option>
+ <option id="selectMenu10-option2">two</option>
+</selectmenu>
+
+<selectmenu id="selectMenu11">
+ <div popover slot="listbox" behavior="listbox">
+ <option>one</option>
+ </div>
+ <div slot="button" behavior="listbox" id="selectMenu11-button">Test</div>
+</selectmenu>
+
+<selectmenu id="selectMenu12">
+ <div slot="button" id="selectMenu12-button-slot">
+ <div behavior="button" id="selectMenu12-button0">button0</div>
+ </div>
+ <div slot="listbox" id="selectMenu12-listbox-slot">
+ <div popover behavior="listbox" id="selectMenu12-originalListbox">
+ <option id="selectMenu12-option1">one</option>
+ <option>two</option>
+ </div>
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu13">
+ <div slot="button" id="selectMenu12-button-slot">
+ <div id="selectMenu13-removeContent-button">
+ <div behavior="button" id="selectMenu13-button0">button0</div>
+ <div behavior="button" id="selectMenu13-button1">button1</div>
+ </div>
+ <div behavior="button" id="selectMenu13-button2">button2</div>
+ </div>
+ <div slot="listbox" id="selectMenu13-listbox-slot">
+ <div id="selectMenu13-removeContent-listbox">
+ <div popover behavior="listbox" id="selectMenu13-originalListbox">
+ <option id="selectMenu13-option1">one</option>
+ <option id="selectMenu13-option2">two</option>
+ </div>
+ </div>
+ <div popover behavior="listbox" id="selectMenu13-newListbox">
+ <option>three</option>
+ <option id="selectMenu13-option4">four</option>
+ </div>
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu14">
+ <div slot="button" behavior="button" id="selectMenu14-button0">button0</div>
+ <option>one</option>
+ <option id="selectMenu14-option2">two</option>
+</selectmenu>
+
+<selectmenu id="selectMenu15">
+ <div slot="button" id="selectMenu15-div0"></div>
+ <option>one</option>
+</selectmenu>
+
+<selectmenu id="selectMenu16">
+ <div slot="button">
+ <div id="selectMenu16-div0">
+ <div behavior="button" id="selectMenu16-button0">button</div>
+ </div>
+ </div>
+ <option>one</option>
+</selectmenu>
+
+<script>
+ function clickOn(element) {
+ const actions = new test_driver.Actions();
+ return actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ }
+
+ promise_test(async () => {
+ const selectMenu0 = document.getElementById("selectMenu0");
+ const selectMenu0Child1 = document.getElementById("selectMenu0-child1");
+ const selectMenu0Child2 = document.getElementById("selectMenu0-child2");
+ const selectMenu0Child3 = document.getElementById("selectMenu0-child3");
+ assert_equals(selectMenu0.value, "one");
+ await clickOn(selectMenu0);
+ await clickOn(selectMenu0Child2);
+ assert_equals(selectMenu0.value, "two");
+
+ await clickOn(selectMenu0);
+ await clickOn(selectMenu0Child3);
+ assert_equals(selectMenu0.value, "two", "Clicking a non-HTMLOptionElement labeled as an option should do nothing");
+
+ await clickOn(selectMenu0Child1);
+ assert_equals(selectMenu0.value, "one");
+ assert_false(selectMenu0.open);
+ }, "HTMLOptionElements (and not other element types) should receive option controller code");
+
+
+ promise_test(async () => {
+ const selectMenu0 = document.getElementById("selectMenu0");
+ const selectMenu0Child4 = document.getElementById("selectMenu0-child4");
+
+ assert_equals(selectMenu0.value, "one");
+ await clickOn(selectMenu0);
+ assert_true(selectMenu0.open);
+ selectMenu0Child4.click();
+ assert_equals(selectMenu0.value, "one", "Clicking an option outside of the popover should not change the value");
+ }, "To receive option part controller code, an option must be a descendant of the listbox part in a flat tree traversal");
+
+ promise_test(async () => {
+ const selectMenu1 = document.getElementById("selectMenu1");
+ const selectMenu1Popover = document.getElementById("selectMenu1-popover");
+ const selectMenu1Button = document.getElementById("selectMenu1-button");
+ const selectMenu1Child2 = document.getElementById("selectMenu1-child2");
+ assert_false(selectMenu1Popover.matches(':open'));
+ selectMenu1Button.click();
+ assert_false(selectMenu1Popover.matches(':open'), "Clicking a button part that is a descendant of the listbox part should have no effect");
+
+ assert_equals(selectMenu1.value, "one");
+ await clickOn(selectMenu1);
+ assert_true(selectMenu1Popover.matches(':open'));
+ await clickOn(selectMenu1Child2);
+ assert_equals(selectMenu1.value, "two", "Clicking an <option> should change the value");
+ }, "To receive button part controller code, an element labeled as a button must not be a descendant of the listbox part in a flat tree traversal");
+
+ promise_test(async () => {
+ const selectMenu2 = document.getElementById("selectMenu2");
+ const selectMenu2Popover = document.getElementById("selectMenu2-popover");
+ const selectMenu2Button = document.getElementById("selectMenu2-button");
+ const selectMenu2Child2 = document.getElementById("selectMenu2-child2");
+ const selectMenu2Child4 = document.getElementById("selectMenu2-child4");
+
+ assert_false(selectMenu2Popover.matches(':open'));
+ await clickOn(selectMenu2Button);
+ assert_false(selectMenu2Popover.matches(':open'), "Clicking a button part should not show an invalid listbox part");
+
+ assert_equals(selectMenu2.value, "three");
+ await clickOn(selectMenu2Button);
+ await clickOn(selectMenu2Child4);
+ assert_equals(selectMenu2.value, "four", "Clicking an <option> that is a descendant of a valid listbox part should update the value");
+ }, "To receive listbox part controller code, an element labeled as a listbox must not be a descendant of the button part in a flat tree traversal");
+
+ promise_test(async () => {
+ const selectMenu3 = document.getElementById("selectMenu3");
+ const selectMenu3ButtonSlot = document.getElementById("selectMenu3-button-slot");
+ const selectMenu3Button0 = document.getElementById("selectMenu3-button0");
+
+ assert_false(selectMenu3.open);
+
+ let button1 = document.createElement("div");
+ button1.innerText = "button1";
+ button1.setAttribute("behavior", "button");
+ selectMenu3ButtonSlot.appendChild(button1);
+
+ await clickOn(button1);
+ assert_false(selectMenu3.open, "A button part should only get controller code if it's first in document order, even if added dynamically");
+
+ await clickOn(selectMenu3Button0);
+ assert_true(selectMenu3.open, "A button part should get controller code if it's first in document order");
+ }, "Button controller code should be applied in flat tree traversal order regardless of dynamic insertion order");
+
+ promise_test(async () => {
+ const selectMenu4 = document.getElementById("selectMenu4");
+ const selectMenu4Button0 = document.getElementById("selectMenu4-button0");
+ const selectMenu4ListboxSlot = document.getElementById("selectMenu4-listbox-slot");
+ const selectMenu4Option2 = document.getElementById("selectMenu4-option2");
+
+ assert_false(selectMenu4.open);
+
+ let listbox2 = document.createElement("div");
+ listbox2.innerHTML = `
+ <option>three</option>
+ <option id="selectMenu4-option4">four</option>
+ `;
+ listbox2.setAttribute("behavior", "listbox");
+ selectMenu4ListboxSlot.appendChild(listbox2);
+
+ await clickOn(selectMenu4Button0);
+ assert_true(selectMenu4.open);
+
+ const selectMenu4Option4 = document.getElementById("selectMenu4-option4");
+ await clickOn(selectMenu4Option4);
+ assert_equals(selectMenu3.value, "one", "An option in a listbox should not get controller code if its listbox isn't first in document order, even if added dynamically");
+
+ await clickOn(selectMenu4Button0);
+ assert_true(selectMenu4.open);
+
+ await clickOn(selectMenu4Option2);
+ assert_equals(selectMenu4.value, "two", "An option in a listbox should get controller code if its listbox is first in document order, even if another listbox was added dynamically");
+}, "Listbox controller code should be applied in flat tree traversal order regardless of dynamic insertion order");
+
+promise_test(async () => {
+ const selectMenu5 = document.getElementById("selectMenu5");
+ const selectMenu5ButtonSlot = document.getElementById("selectMenu5-button-slot");
+ const selectMenu5Button0 = document.getElementById("selectMenu5-button0");
+ const selectMenu5SelectedValue0 = document.getElementById("selectMenu5-selectedValue0");
+
+ assert_false(selectMenu3.open);
+ assert_equals(selectMenu5SelectedValue0.innerText, "one");
+
+ let selectedValue1 = document.createElement("div");
+ selectMenu5ButtonSlot.appendChild(selectedValue1);
+
+ await clickOn(selectMenu5Button0);
+ assert_true(selectMenu5.open);
+
+ await clickOn(document.getElementById("selectMenu5-option0"));
+ assert_false(selectMenu5.open);
+ assert_equals(selectMenu5SelectedValue0.innerText, "two", "first selected-value part in flat tree order should get controller code");
+ assert_equals(selectedValue1.innerText, "", "Dynamically inserted selected-value part shouldn't get controller code if it's not first in flat tree order");
+ }, "selected-value controller code should be applied in flat tree traversal order regardless of dynamic insertion order");
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu9");
+ const originalListbox = document.getElementById("selectMenu9-originalListbox");
+ const option2 = document.getElementById("selectMenu9-option2");
+ assert_equals(selectMenu.value, "one", "Initial value should be the first option");
+
+ let newListbox = document.createElement("div");
+ newListbox.setAttribute("popover", "auto");
+ newListbox.setAttribute("behavior", "listbox");
+ let newOption = document.createElement("option");
+ newOption.innerText = "three";
+ newListbox.appendChild(newOption);
+ let newOption2 = document.createElement("option");
+ newOption2.innerText = "four";
+ newListbox.appendChild(newOption2);
+ originalListbox.parentElement.insertBefore(newListbox, originalListbox);
+
+ await clickOn(selectMenu);
+ assert_true(selectMenu.open, "Menu should open when clicked");
+
+ option2.click(); // clickOn doesn't work because the old options are not displayed
+ assert_equals(selectMenu.value, "three", "Elements in second popover should no longer be option parts");
+ assert_true(selectMenu.open, "Clicking non-part options shouldn't close the popover");
+
+ await clickOn(newOption2);
+ assert_false(selectMenu.open);
+ assert_equals(selectMenu.value, "four", "New options should get controller code after listbox switch");
+ }, "Ensure that option controller code is updated when listbox changes");
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu10");
+ const selectMenu10SlottedOption = document.getElementById("selectMenu10-slottedOption");
+
+ await clickOn(selectMenu10SlottedOption);
+ assert_false(selectMenu.open, "Controller code not applied due to part attribute missing");
+ selectMenu10SlottedOption.setAttribute("behavior", "button");
+ await clickOn(selectMenu10SlottedOption);
+ assert_true(selectMenu.open);
+
+ const option2 = document.getElementById("selectMenu10-option2");
+ await clickOn(option2);
+ assert_equals(selectMenu.value, "two");
+ assert_false(selectMenu.open);
+
+ selectMenu10SlottedOption.slot = "";
+ await clickOn(selectMenu);
+ assert_true(selectMenu.open, "Default button part should be used");
+ await clickOn(selectMenu10SlottedOption);
+ assert_equals(selectMenu.value, "Test 10");
+ }, "Ensure that controller code is applied after updating the slot attribute");
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu11");
+ const selectMenu11Button= document.getElementById("selectMenu11-button");
+
+ await clickOn(selectMenu11Button);
+ assert_false(selectMenu.open, "Controller code not applied due to part attribute not being button");
+ selectMenu11Button.remove();
+
+ await clickOn(selectMenu);
+ assert_true(selectMenu.open, "Default button part should be used");
+ }, "Ensure that controller code is applied when slot and part attributes are different");
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu12");
+ const originalListbox = document.getElementById("selectMenu12-originalListbox");
+ assert_equals(selectMenu.value, "one", "Initial value should be the first option");
+
+ const selectMenuButtonSlot = document.getElementById("selectMenu12-button-slot");
+ const selectMenuButton0 = document.getElementById("selectMenu12-button0");
+ const selectMenuOption1 = document.getElementById("selectMenu12-option1");
+
+ assert_false(selectMenu.open);
+ let button1 = document.createElement("div");
+ button1.innerText = "button1";
+ button1.setAttribute("behavior", "button");
+ selectMenuButtonSlot.insertBefore(button1, selectMenuButton0);
+ button1.click();
+ assert_true(selectMenu.open, "Controller code should be applied to the new first button in document order");
+ await clickOn(selectMenuOption1);
+ assert_false(selectMenu.open);
+ selectMenuButton0.click();
+ assert_false(selectMenu.open);
+
+ let button2 = document.createElement("div");
+ button2.innerText = "button2";
+ selectMenuButtonSlot.insertBefore(button2, button1);
+ button2.click();
+ assert_false(selectMenu.open, "Controller code should not be applied to button2 since it doesn't have behavior attribute set");
+ button2.setAttribute("behavior", "button");
+ button2.click();
+ assert_true(selectMenu.open, "Controller code should be applied to the new button part");
+ await clickOn(selectMenuOption1);
+ assert_false(selectMenu.open);
+
+ let newListbox = document.createElement("div");
+ newListbox.setAttribute("popover", "auto");
+ newListbox.setAttribute("behavior", "listbox");
+ let newOption = document.createElement("option");
+ newOption.innerText = "three";
+ newListbox.appendChild(newOption);
+ let newOption2 = document.createElement("option");
+ newOption2.innerText = "four";
+ newListbox.appendChild(newOption2);
+ originalListbox.parentElement.insertBefore(newListbox, originalListbox);
+ assert_equals(selectMenu.value, "three", "New value should be the first option");
+
+ newListbox.innerHTML = "<option>five</option><option>six</option>";
+ assert_equals(selectMenu.value, "five", "New value should be the first option");
+
+ selectMenu.innerHTML = "<option>seven</option><option id='selectMenu12-option2'>eight</option>";
+ assert_equals(selectMenu.value, "seven", "New value should be the first option");
+ const selectMenuOption2 = document.getElementById("selectMenu12-option2");
+ await clickOn(selectMenu);
+ assert_true(selectMenu.open);
+ await clickOn(selectMenuOption2);
+ assert_equals(selectMenu.value, "eight", "Controller code should be applied to new options");
+
+ selectMenuOption2.slot = "button";
+ assert_equals(selectMenu.value, "seven", "Previous selected option should become invalid");
+ }, "Ensure that controller code is synchronously applied");
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu13");
+ assert_equals(selectMenu.value, "one");
+
+ const selectMenuButton0 = document.getElementById("selectMenu13-button0");
+ const selectMenuButton1 = document.getElementById("selectMenu13-button1");
+ selectMenuButton1.click();
+ assert_false(selectMenu.open);
+ selectMenuButton0.click();
+ assert_true(selectMenu.open, "First button should receive controller code");
+ await clickOn(document.getElementById("selectMenu13-option2"));
+ assert_equals(selectMenu.value, "two");
+ let divButtonToRemove = document.getElementById("selectMenu13-removeContent-button");
+ divButtonToRemove.innerHTML = "";
+ selectMenuButton0.click();
+ assert_false(selectMenu.open, "The first button is invalid");
+ const selectMenuButton2 = document.getElementById("selectMenu13-button2");
+ selectMenuButton2.click();
+ assert_true(selectMenu.open, "The button part should be updated")
+ await clickOn(document.getElementById("selectMenu13-option1"));
+ assert_equals(selectMenu.value, "one");
+
+ const selectMenuOption4 = document.getElementById("selectMenu13-option4");
+ selectMenuOption4.click();
+ assert_equals(selectMenu.value, "one");
+ let divListboxToRemove = document.getElementById("selectMenu13-removeContent-listbox");
+ divListboxToRemove.innerHTML = "";
+ assert_equals(selectMenu.value, "three", "The listbox part should be updated");
+ selectMenuOption4.click();
+ assert_equals(selectMenu.value, "four", "Controller code should be applied to the new options");
+
+ let selectMenuNewListbox = document.getElementById("selectMenu13-newListbox");
+ selectMenuNewListbox.innerHTML = "";
+ assert_equals(selectMenu.value, "");
+ selectMenuOption4.click();
+ assert_equals(selectMenu.value, "");
+ }, "Controller code should be updated when nested parts are removed");
+
+ promise_test(async () => {
+ let selectMenu = document.getElementById("selectMenu14");
+ assert_equals(selectMenu.value, "one");
+ const selectMenuButton0 = document.getElementById("selectMenu14-button0");
+ const selectMenuOption2 = document.getElementById("selectMenu14-option2");
+
+ selectMenuButton0.click();
+ assert_true(selectMenu.open);
+ await clickOn(selectMenuOption2);
+ assert_equals(selectMenu.value, "two");
+
+ document.body.removeChild(selectMenu);
+ selectMenu.removeChild(selectMenuOption2);
+ assert_equals(selectMenu.value, "one");
+ let newOption = document.createElement("option");
+ newOption.innerText = "three";
+ selectMenu.appendChild(newOption);
+ newOption.click();
+ assert_equals(selectMenu.value, "three", "New option should receive controller code");
+
+ let doc = document.implementation.createHTMLDocument('');
+ let selectMenu1 = doc.createElement('selectmenu');
+ let firstOption = doc.createElement('option');
+ firstOption.innerText = 'one';
+ let secondOption = doc.createElement('option');
+ secondOption.innerText = 'two';
+ selectMenu1.appendChild(firstOption);
+ selectMenu1.appendChild(secondOption);
+ assert_equals(selectMenu1.value, "one");
+ secondOption.click();
+ assert_equals(selectMenu1.value, "two");
+ document.body.appendChild(selectMenu1);
+ selectMenu1.removeChild(secondOption);
+ assert_equals(selectMenu1.value, "one");
+ }, "Moving a selectmenu between documents should keep controller code active");
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu15");
+ const selectMenuButtonContainer = document.getElementById("selectMenu15-div0");
+
+ const outerDiv = document.createElement("div");
+ const button = document.createElement("input");
+ button.type = button.value = "button";
+ button.setAttribute("behavior", "button");
+ outerDiv.appendChild(button);
+ selectMenuButtonContainer.appendChild(outerDiv);
+
+ await clickOn(selectMenu);
+ assert_true(selectMenu.open, "New button should receive controller code");
+ }, "New parts should be detected even when in the subtree of an inserted node");
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu16");
+ const selectMenuButtonContainer = document.getElementById("selectMenu16-div0");
+ const selectMenuButton = document.getElementById("selectMenu16-button0");
+
+ selectMenuButtonContainer.remove();
+
+ selectMenuButton.click();
+ assert_false(selectMenu.open, "Removed button should no longer have controller code");
+ }, "Part removals should be detected even when in the subtree of a removed node");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position-with-zoom.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position-with-zoom.tentative.html
new file mode 100644
index 0000000000..85d73d6a40
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position-with-zoom.tentative.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+<title>HTMLSelectMenuElement Test: popover position with zoom</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<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>
+
+<style>
+ #selectMenu0 {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ zoom: 2;
+ }
+
+ #selectMenu1 {
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+ zoom: 1.5;
+ }
+
+ #selectMenu1-popover {
+ zoom: 2;
+ }
+
+ #selectMenu2 {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ zoom: 3;
+ }
+
+ #selectMenu3 {
+ position: absolute;
+ bottom: 0px;
+ right: 0px;
+ zoom: 4;
+ }
+
+ #selectMenu3-popover {
+ zoom: 1.5;
+ }
+</style>
+
+<selectmenu id="selectMenu0">
+ <div slot="button" behavior="button" id="selectMenu0-button">Custom bottom left</div>
+ <div popover slot="listbox" behavior="listbox" id="selectMenu0-popover">
+ <option>bottom left</option>
+ <option>two</option>
+ <option>three</option>
+ </div>
+</selectmenu>
+<br>
+
+<selectmenu id="selectMenu1">
+ <div slot="button" behavior="button" id="selectMenu1-button">Custom top left</div>
+ <div popover slot="listbox" behavior="listbox" id="selectMenu1-popover">
+ <option>top left</option>
+ <option>two</option>
+ <option>three</option>
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu2">
+ <div slot="button" behavior="button" id="selectMenu2-button">Custom bottom right</div>
+ <div popover slot="listbox" behavior="listbox" id="selectMenu2-popover">
+ <option>bottom right</option>
+ <option>two</option>
+ <option>three</option>
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu3">
+ <div slot="button" behavior="button" id="selectMenu3-button">Custom top right</div>
+ <div popover slot="listbox" behavior="listbox" id="selectMenu3-popover">
+ <option>top right</option>
+ <option>two</option>
+ <option>three</option>
+ </div>
+</selectmenu>
+
+<script>
+ function clickOn(element) {
+ const actions = new test_driver.Actions();
+ return actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ }
+
+ promise_test(async () => {
+ const selectMenu0 = document.getElementById("selectMenu0");
+ const selectMenu0Popover = document.getElementById("selectMenu0-popover");
+ const selectMenu0Button = document.getElementById("selectMenu0-button");
+
+ await clickOn(selectMenu0);
+ assert_equals(Math.abs(Math.trunc(selectMenu0.getBoundingClientRect().bottom - selectMenu0Popover.getBoundingClientRect().top)), 0);
+ assert_equals(Math.abs(Math.trunc(selectMenu0.getBoundingClientRect().left - selectMenu0Popover.getBoundingClientRect().left)), 0);
+ }, "The popover should be bottom left positioned");
+
+ promise_test(async () => {
+ const selectMenu1 = document.getElementById("selectMenu1");
+ const selectMenu1Popover = document.getElementById("selectMenu1-popover");
+ const selectMenu1Button = document.getElementById("selectMenu1-button");
+
+ selectMenu1Button.click();
+ assert_equals(Math.abs(Math.trunc(selectMenu1.getBoundingClientRect().top - selectMenu1Popover.getBoundingClientRect().bottom * 2)), 0);
+ assert_equals(Math.abs(Math.trunc(selectMenu1.getBoundingClientRect().left - selectMenu1Popover.getBoundingClientRect().left * 2)), 0);
+ }, "The popover should be top left positioned");
+
+ promise_test(async () => {
+ const selectMenu2 = document.getElementById("selectMenu2");
+ const selectMenu2Popover = document.getElementById("selectMenu2-popover");
+ const selectMenu2Button = document.getElementById("selectMenu2-button");
+
+ selectMenu2Button.click();
+ assert_equals(Math.abs(Math.trunc(selectMenu2.getBoundingClientRect().bottom - selectMenu2Popover.getBoundingClientRect().top)), 0);
+ assert_equals(Math.abs(Math.trunc(selectMenu2.getBoundingClientRect().right - selectMenu2Popover.getBoundingClientRect().right)), 0);
+ }, "The popover should be bottom right positioned");
+
+ promise_test(async () => {
+ const selectMenu3 = document.getElementById("selectMenu3");
+ const selectMenu3Popover = document.getElementById("selectMenu3-popover");
+ const selectMenu3Button = document.getElementById("selectMenu3-button");
+
+ selectMenu3Button.click();
+ assert_equals(Math.abs(Math.trunc(selectMenu3.getBoundingClientRect().top - selectMenu3Popover.getBoundingClientRect().bottom * 1.5)), 0);
+ assert_equals(Math.abs(Math.trunc(selectMenu3.getBoundingClientRect().right - selectMenu3Popover.getBoundingClientRect().right * 1.5)), 0);
+ }, "The popover should be top right positioned");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position.tentative.html
new file mode 100644
index 0000000000..d83015f4ef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position.tentative.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html>
+<title>HTMLSelectMenuElement Test: popover position</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<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>
+
+<style>
+ #selectMenu0 {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ }
+
+ #selectMenu1 {
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+ }
+
+ #selectMenu2 {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ }
+
+ #selectMenu3 {
+ position: absolute;
+ bottom: 0px;
+ right: 0px;
+ }
+</style>
+
+<selectmenu id="selectMenu0">
+ <div popover slot="listbox" behavior="listbox" id="selectMenu0-popover">
+ <option>bottom left</option>
+ <option>two</option>
+ <option>three</option>
+ </div>
+</selectmenu>
+<br>
+
+<selectmenu id="selectMenu1">
+ <div popover slot="listbox" behavior="listbox" id="selectMenu1-popover">
+ <option>top left</option>
+ <option>two</option>
+ <option>three</option>
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu2">
+ <div popover slot="listbox" behavior="listbox" id="selectMenu2-popover">
+ <option>bottom right</option>
+ <option>two</option>
+ <option>three</option>
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu3">
+ <div popover slot="listbox" behavior="listbox" id="selectMenu3-popover">
+ <option>top right</option>
+ <option>two</option>
+ <option>three</option>
+ </div>
+</selectmenu>
+
+<script>
+ function clickOn(element) {
+ const actions = new test_driver.Actions();
+ return actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ }
+
+ promise_test(async () => {
+ const selectMenu0 = document.getElementById("selectMenu0");
+ const selectMenu0Popover = document.getElementById("selectMenu0-popover");
+
+ await clickOn(selectMenu0);
+ assert_equals(Math.abs(Math.trunc(selectMenu0.getBoundingClientRect().bottom - selectMenu0Popover.getBoundingClientRect().top)), 0);
+ assert_equals(Math.abs(Math.trunc(selectMenu0.getBoundingClientRect().left - selectMenu0Popover.getBoundingClientRect().left)), 0);
+ }, "The popover should be bottom left positioned");
+
+ promise_test(async () => {
+ const selectMenu1 = document.getElementById("selectMenu1");
+ const selectMenu1Popover = document.getElementById("selectMenu1-popover");
+
+ await clickOn(selectMenu1);
+ assert_equals(Math.abs(Math.trunc(selectMenu1.getBoundingClientRect().top - selectMenu1Popover.getBoundingClientRect().bottom)), 0);
+ assert_equals(Math.abs(Math.trunc(selectMenu1.getBoundingClientRect().left - selectMenu1Popover.getBoundingClientRect().left)), 0);
+ }, "The popover should be top left positioned");
+
+ promise_test(async () => {
+ const selectMenu2 = document.getElementById("selectMenu2");
+ const selectMenu2Popover = document.getElementById("selectMenu2-popover");
+
+ await clickOn(selectMenu2);
+ assert_equals(Math.abs(Math.trunc(selectMenu2.getBoundingClientRect().bottom - selectMenu2Popover.getBoundingClientRect().top)), 0);
+ assert_equals(Math.abs(Math.trunc(selectMenu2.getBoundingClientRect().right - selectMenu2Popover.getBoundingClientRect().right)), 0);
+ }, "The popover should be bottom right positioned");
+
+ promise_test(async () => {
+ const selectMenu3 = document.getElementById("selectMenu3");
+ const selectMenu3Popover = document.getElementById("selectMenu3-popover");
+
+ await clickOn(selectMenu3);
+ assert_equals(Math.abs(Math.trunc(selectMenu3.getBoundingClientRect().top - selectMenu3Popover.getBoundingClientRect().bottom)), 0);
+ assert_equals(Math.abs(Math.trunc(selectMenu3.getBoundingClientRect().right - selectMenu3Popover.getBoundingClientRect().right)), 0);
+ }, "The popover should be top right positioned");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative.html
new file mode 100644
index 0000000000..74aa4f828c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<title>HTMLSelectMenuElement Test: popover</title>
+<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>
+
+<selectmenu id="selectMenu0">
+ <option>one</option>
+ <option id="selectMenu0-child2">two</option>
+ <div id="selectMenu0-child3">I'm a div with no part attr</div>
+ <option>three</option>
+ <option>four</option>
+</selectmenu>
+
+<selectmenu id="selectMenu1">
+ <div slot="button" behavior="button" class="button">
+ Custom button
+ </div>
+ <div popover slot="listbox" behavior="listbox">
+ <option>one</option>
+ <option class="child2">two</option>
+ <option class="child3">three</option>
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu2">
+ <!-- Swap out the listbox part without providing a replacement -->
+ <div slot="listbox"></div>
+</selectmenu>
+
+<selectmenu id="selectMenu3">
+ <div slot="listbox">
+ <div popover behavior="listbox" id="selectMenu3-listbox">
+ <option>one</option>
+ </div>
+ </div>
+</selectmenu>
+<script>
+
+ function clickOn(element) {
+ const actions = new test_driver.Actions();
+ return actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ }
+
+ promise_test(async () => {
+ const selectMenu0 = document.getElementById("selectMenu0");
+ const selectMenu0Child2 = document.getElementById("selectMenu0-child2");
+ const selectMenu0Child3 = document.getElementById("selectMenu0-child3");
+ assert_equals(selectMenu0.value, "one");
+ assert_equals(selectMenu0.open, false);
+ await clickOn(selectMenu0);
+ assert_equals(selectMenu0.open, true);
+ await clickOn(selectMenu0Child2);
+ assert_equals(selectMenu0.value, "two");
+ assert_equals(selectMenu0.open, false);
+
+ await clickOn(selectMenu0);
+ assert_equals(selectMenu0.open, true);
+ await clickOn(selectMenu0Child3);
+ assert_equals(selectMenu0.value, "two", "Clicking a non-option should not change the value");
+ assert_equals(selectMenu0.open, true);
+ await clickOn(selectMenu0Child2);
+ assert_equals(selectMenu0.open, false);
+ }, "Opening the popover and clicking an option should change the selectmenu's value");
+
+ promise_test(async () => {
+ const selectMenu1 = document.getElementById("selectMenu1");
+ const button = selectMenu1.querySelector(".button");
+ const child2 = selectMenu1.querySelector(".child2");
+ const child3 = selectMenu1.querySelector(".child3");
+ assert_equals(selectMenu1.value, "one");
+ assert_equals(selectMenu1.open, false);
+ await clickOn(button);
+ assert_equals(selectMenu1.open, true);
+ await clickOn(child2);
+ assert_equals(selectMenu1.value, "two", "Clicking an <option> should change the value");
+ assert_equals(selectMenu1.open, false);
+
+ await clickOn(button);
+ assert_equals(selectMenu1.open, true);
+ await clickOn(child3);
+ assert_equals(selectMenu1.value, "three", "Clicking a <div part='option'> should change the value");
+ assert_equals(selectMenu1.open, false);
+ }, "With custom button and popover: opening the popover and clicking an option should change the selectmenu's value");
+
+ promise_test(async () => {
+ const selectMenu2 = document.getElementById("selectMenu2");
+ await clickOn(selectMenu2);
+ assert_equals(selectMenu2.value, "");
+ assert_equals(selectMenu2.open, false);
+ }, "Clicking a popover with no listbox part does nothing");
+
+ promise_test(async () => {
+ const selectMenu3 = document.getElementById("selectMenu3");
+ const selectMenu3Listbox = document.getElementById("selectMenu3-listbox");
+ selectMenu3Listbox.remove();
+
+ await clickOn(selectMenu3);
+ assert_equals(selectMenu3.value, "");
+ assert_equals(selectMenu3.open, false);
+ }, "Clicking a popover with a listbox that was removed does nothing");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-required-attribute.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-required-attribute.tentative.html
new file mode 100644
index 0000000000..ea6d1b215d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-required-attribute.tentative.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectMenuElement Test: required attribute</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ selectmenu:required {
+ border: 3px dashed rgb(255, 0, 0);
+ }
+
+ selectmenu:optional {
+ border: 1px solid rgb(128, 128, 128);
+ }
+</style>
+
+<selectmenu id="selectmenu0" required>
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectmenu>
+
+<selectmenu id="selectmenu1">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectmenu>
+
+<selectmenu id="selectmenu2">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectmenu>
+
+<script>
+function checkRequired(style) {
+ assert_equals(style.borderWidth, '3px');
+ assert_equals(style.borderStyle, 'dashed');
+ assert_equals(style.borderColor, 'rgb(255, 0, 0)');
+}
+
+function checkOptional(style) {
+ assert_equals(style.borderWidth, '1px');
+ assert_equals(style.borderStyle, 'solid');
+ assert_equals(style.borderColor, 'rgb(128, 128, 128)');
+}
+
+test(() => {
+ const selectMenu0 = document.getElementById("selectmenu0");
+ const selectMenu1 = document.getElementById("selectmenu1");
+ const selectMenu2 = document.getElementById("selectmenu2");
+
+ checkRequired(window.getComputedStyle(selectMenu0));
+ checkOptional(window.getComputedStyle(selectMenu1));
+ checkOptional(window.getComputedStyle(selectMenu2));
+ selectMenu2.required = true;
+ checkRequired(window.getComputedStyle(selectMenu2));
+}, "Test required attribute");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-validity.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-validity.tentative.html
new file mode 100644
index 0000000000..a58fe54ff6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-validity.tentative.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectMenuElement Test: validity</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<selectmenu id="selectmenu1" required>
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+ <option>four</option>
+</selectmenu>
+
+<form>
+ <selectmenu id="selectmenu2" required>
+ </selectmenu>
+</form>
+
+<script>
+
+test(() => {
+ let selectMenu = document.createElement('selectmenu');
+ assert_true(selectMenu.willValidate, "A selectmenu element is a submittable element that is a candidate for constraint validation.");
+ let option = document.createElement('option');
+ selectMenu.appendChild(option);
+ assert_true(selectMenu.checkValidity(), "Always valid when the selectmenu isn't a required value.");
+
+ selectMenu.required = true;
+ assert_equals(selectMenu.value, "");
+ assert_false(selectMenu.checkValidity(), "A selected placeholder option should invalidate the selectmenu.");
+
+ let emptyOption = document.createElement('option');
+ selectMenu.appendChild(emptyOption);
+ assert_false(selectMenu.checkValidity(), "A selected placeholder option should invalidate the selectmenu even if there are multiple options.");
+ emptyOption.selected = true;
+ assert_true(selectMenu.checkValidity(), "An empty non-placeholder option should be a valid choice.");
+
+ let filledOption = document.createElement('option');
+ filledOption.value = "test";
+ selectMenu.appendChild(filledOption);
+ filledOption.selected = true;
+ assert_equals(selectMenu.value, "test", "The non-empty value should be set.");
+ assert_true(selectMenu.checkValidity(), "A non-empty non-placeholder option should be a valid choice.");
+
+ selectMenu.removeChild(option);
+ selectMenu.appendChild(emptyOption);
+ emptyOption.selected = true;
+ assert_equals(selectMenu.value, "", "The empty value should be set.");
+ assert_true(selectMenu.checkValidity(), "Only the first option can be seen as a placeholder.");
+
+ selectMenu.removeChild(filledOption);
+ assert_false(selectMenu.checkValidity(), "A selected placeholder option should invalidate the selectmenu.");
+
+ emptyOption.value = "test2";
+ assert_equals(selectMenu.value, "test2");
+ assert_true(selectMenu.checkValidity(), "A non-empty option value should be a valid choice.");
+
+ emptyOption.removeAttribute("value");
+ assert_equals(selectMenu.value, "");
+ assert_false(selectMenu.checkValidity());
+ emptyOption.innerText = "test";
+ assert_equals(selectMenu.value, "test");
+ assert_true(selectMenu.checkValidity(), "A non-empty option should be a valid choice.");
+
+ const selectMenu1 = document.getElementById('selectmenu1');
+ assert_equals(selectMenu1.value, "one");
+ assert_true(selectMenu1.checkValidity(), "A selectmenu with non-empty placeholder option should be valid.");
+}, "Validation for placeholder option");
+
+test(() => {
+ const selectMenu2 = document.getElementById('selectmenu2');
+ assert_equals(selectMenu2.value, "");
+ assert_false(selectMenu2.checkValidity());
+ let form = document.querySelector('form');
+ let invalidControl = form.querySelector('selectmenu:invalid');
+ assert_equals(selectMenu2, invalidControl);
+ let didDispatchInvalid = false;
+ invalidControl.addEventListener('invalid', e => { didDispatchInvalid = true; });
+ let didDispatchSubmit = false;
+ form.addEventListener('submit', event => { event.preventDefault(); didDispatchSubmit = true; });
+
+ form.requestSubmit();
+ assert_true(didDispatchInvalid);
+ assert_false(didDispatchSubmit);
+}, "Check form not submitted for invalid selectmenu");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-value-selectedOption.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-value-selectedOption.tentative.html
new file mode 100644
index 0000000000..3ba7da6b5a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/selectmenu-value-selectedOption.tentative.html
@@ -0,0 +1,204 @@
+<!DOCTYPE html>
+<title>HTMLSelectMenuElement Test: value and selectedOption</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<selectmenu id="selectMenu0"></selectmenu>
+
+<selectmenu id="selectMenu1">
+ <option>one</option>
+ <option>two</option>
+ <div>I'm a div with no part attr</div>
+ <option id="selectMenu1-option3">three</option>
+ <option>four</option>
+</selectmenu>
+
+<selectmenu id="selectMenu2">
+ <div behavior="option">one</div>
+ <div behavior="option">two</div>
+ <div>I'm a div with no part attr</div>
+ <div behavior="option">three</div>
+ <div behavior="option">four</div>
+</selectmenu>
+
+<selectmenu id="selectMenu3">
+ <div>I'm a div with no part attr</div>
+ <option id="selectMenu3-child1">one</option>
+ <option id="selectMenu3-child2">two</option>
+ <option id="selectMenu3-child3">three</option>
+</selectmenu>
+
+<selectmenu id="selectMenu4">
+ <div slot="button" behavior="button">
+ <div behavior="selected-value" id="selectMenu4-custom-selected-value">Default custom selected-value text</div>
+ </div>
+ <option id="selectMenu4-option1">one</option>
+ <option id="selectMenu4-option2">two</option>
+</selectmenu>
+
+<selectmenu id="selectMenu5">
+ <div slot="button" behavior="button">
+ <div behavior="selected-value" id="selectMenu5-custom-selected-value">Default custom selected-value text</div>
+ </div>
+ <div popover slot="listbox" behavior="listbox">
+ <option id="selectMenu5-option1">one</option>
+ <option id="selectMenu5-option2">two</option>
+ </div>
+</selectmenu>
+
+<selectmenu id="selectMenu6">
+ <option id="selectMenu6-option1">one</option>
+ <option id="selectMenu6-option2" selected>two</option>
+ <option id="selectMenu6-option3">three</option>
+</selectmenu>
+
+<selectmenu id="selectMenu7">
+ <option id="selectMenu7-option1">one</option>
+ <option id="selectMenu7-option2" selected value="test">two</option>
+ <option>three</option>
+</selectmenu>
+
+<script>
+
+test(() => {
+ const selectMenu0 = document.getElementById("selectMenu0");
+ assert_equals(selectMenu0.value, "");
+ assert_equals(selectMenu0.selectedOption, null);
+ selectMenu0.value = "something";
+ assert_equals(selectMenu0.value, "", "Setting value should have no effect if there is no matching option");
+ assert_equals(selectMenu0.selectedOption, null);
+}, "Test that HTMLSelectMenu with no options has empty string for value and null for selectedOption");
+
+test(() => {
+ const selectMenu1 = document.getElementById("selectMenu1");
+ assert_equals(selectMenu1.value, "one", "value should start with the text of the first option part");
+
+ selectMenu1.value = "three";
+ assert_equals(selectMenu1.value, "three", "value can be set to the text of an option part");
+ assert_equals(selectMenu1.selectedOption, document.getElementById("selectMenu1-option3"));
+
+ selectMenu1.value = "I'm a div with no part attr";
+ assert_equals(selectMenu1.value, "three", "Setting value should have no effect if there is no matching option");
+ assert_equals(selectMenu1.selectedOption, document.getElementById("selectMenu1-option3"));
+}, "Test value and selectedOption with HTMLOptionElement element option parts");
+
+test(() => {
+ const selectMenu2 = document.getElementById("selectMenu2");
+ assert_equals(selectMenu2.value, "", "Non-HTMLOptionElements shouldn't be treated as option parts");
+ assert_equals(selectMenu2.selectedOption, null);
+
+ selectMenu2.value = "three";
+ assert_equals(selectMenu2.value, "", "value can't be set when there are no option parts'");
+ assert_equals(selectMenu2.selectedOption, null);
+}, "Test value with non-HTMLOptionElement elements labeled as parts");
+
+test(() => {
+ const selectMenu3 = document.getElementById("selectMenu3");
+ assert_equals(selectMenu3.value, "one", "value should start with the text of the first option part");
+ assert_equals(selectMenu3.selectedOption, document.getElementById("selectMenu3-child1"));
+
+ document.getElementById("selectMenu3-child3").remove();
+ assert_equals(selectMenu3.value, "one", "Removing a non-selected option should not change the value");
+ assert_equals(selectMenu3.selectedOption, document.getElementById("selectMenu3-child1"));
+
+ document.getElementById("selectMenu3-child1").remove();
+ assert_equals(selectMenu3.value, "two", "When the selected option is removed, the new first option should become selected");
+ assert_equals(selectMenu3.selectedOption, document.getElementById("selectMenu3-child2"));
+
+ document.getElementById("selectMenu3-child2").remove();
+ assert_equals(selectMenu3.value, "", "When all options are removed, value should be the empty string");
+ assert_equals(selectMenu3.selectedOption, null);
+}, "Test that value and selectedOption are updated when options are removed");
+
+test(() => {
+ const selectMenu4 = document.getElementById("selectMenu4");
+ let customSelectedValuePart = document.getElementById("selectMenu4-custom-selected-value");
+ assert_equals(selectMenu4.value, "one", "value should start with the text of the first option part");
+ assert_equals(selectMenu4.selectedOption, document.getElementById("selectMenu4-option1"));
+ assert_equals(customSelectedValuePart.innerText, "one", "Custom selected value part should be set to initial value of selectmenu");
+
+ selectMenu4.value = "two";
+ assert_equals(customSelectedValuePart.innerText, "two", "Custom selected value part should be updated when value of selectmenu changes");
+ assert_equals(selectMenu4.selectedOption, document.getElementById("selectMenu4-option2"));
+}, "Test that slotted-in selected-value part is updated to value of selectmenu");
+
+test(() => {
+ const selectMenu5 = document.getElementById("selectMenu5");
+ let customSelectedValuePart = document.getElementById("selectMenu5-custom-selected-value");
+ assert_equals(selectMenu5.value, "one", "value should start with the text of the first option part");
+ assert_equals(selectMenu5.selectedOption, document.getElementById("selectMenu5-option1"));
+ assert_equals(customSelectedValuePart.innerText, "one", "Custom selected value part should be set to initial value of selectmenu");
+
+ selectMenu5.value = "two";
+ assert_equals(customSelectedValuePart.innerText, "two", "Custom selected value part should be updated when value of selectmenu changes");
+ assert_equals(selectMenu5.selectedOption, document.getElementById("selectMenu5-option2"));
+}, "Test that option parts in a slotted-in listbox are reflected in the value property");
+
+test(() => {
+ let selectMenu = document.createElement('selectmenu');
+ assert_equals(selectMenu.value, "");
+ let option = document.createElement('option');
+ option.innerText = "one";
+ selectMenu.appendChild(option);
+ assert_equals(selectMenu.value, "one");
+ assert_equals(selectMenu.selectedOption, option);
+
+ let newOption = document.createElement('option');
+ newOption.innerText = 'two';
+ selectMenu.appendChild(newOption);
+ selectMenu.value = "two";
+ assert_equals(selectMenu.value, "two");
+ assert_equals(selectMenu.selectedOption, newOption);
+
+ option.click();
+ assert_equals(selectMenu.value, "one");
+ assert_equals(selectMenu.selectedOption, option);
+}, "Test that value and selectedOption are correctly updated");
+
+test(() => {
+ const selectMenu = document.getElementById("selectMenu6");
+ let selectMenuOption1 = document.getElementById("selectMenu6-option1");
+
+ assert_equals(selectMenu.value, "two");
+ assert_equals(selectMenu.selectedOption, document.getElementById("selectMenu6-option2"));
+ assert_false(selectMenuOption1.selected);
+ selectMenuOption1.selected = true;
+ assert_equals(selectMenu.value, "one");
+ assert_equals(selectMenu.selectedOption, selectMenuOption1);
+
+ let newOption = document.createElement("option");
+ newOption.innerText = "four";
+ newOption.selected = true;
+ selectMenu.appendChild(newOption);
+ assert_equals(selectMenu.value, "four");
+ assert_equals(selectMenu.selectedOption, newOption);
+ assert_false(selectMenuOption1.selected);
+
+ selectMenu.value = "three";
+ assert_equals(selectMenu.selectedOption, document.getElementById("selectMenu6-option3"));
+ assert_false(newOption.selected);
+}, "Test that HTMLOption.selected updates selectmenu.value and selectmenu.selectedOption");
+
+test(() => {
+ const selectMenu = document.getElementById("selectMenu7");
+ let selectMenuOption1 = document.getElementById("selectMenu7-option1");
+
+ assert_equals(selectMenu.value, "test");
+ assert_equals(selectMenu.selectedOption, document.getElementById("selectMenu7-option2"));
+ assert_false(selectMenuOption1.selected);
+ selectMenuOption1.selected = true;
+ assert_equals(selectMenu.value, "one");
+ assert_equals(selectMenu.selectedOption, selectMenuOption1);
+
+ selectMenuOption1.value = "new test";
+ assert_equals(selectMenu.value, "new test");
+ assert_equals(selectMenu.selectedOption, selectMenuOption1);
+ selectMenuOption1.removeAttribute("value");
+ assert_equals(selectMenu.value, "one");
+ assert_equals(selectMenu.selectedOption, selectMenuOption1);
+ selectMenuOption1.value = "";
+ assert_equals(selectMenu.value, "");
+ assert_equals(selectMenu.selectedOption, selectMenuOption1);
+}, "Test that HTMLOption.value updates selectmenu.value");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/support/back.html b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/support/back.html
new file mode 100644
index 0000000000..0d13a0f3b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectmenu-element/support/back.html
@@ -0,0 +1 @@
+<script>history.back()</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/cloning-steps.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/cloning-steps.html
new file mode 100644
index 0000000000..7a85bd26a1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/cloning-steps.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Cloning of textarea elements</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-node-clonenode">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-clone">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-clone-ext">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#the-textarea-element:concept-node-clone-ext">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(function() {
+ var textarea = document.createElement("textarea");
+ textarea.value = "foo bar";
+
+ var copy = textarea.cloneNode();
+ assert_equals(copy.value, "foo bar");
+}, "textarea element's value should be cloned");
+
+test(function() {
+ var textarea = document.createElement("textarea");
+ textarea.value = "foo bar";
+
+ var copy = textarea.cloneNode();
+ copy.setAttribute("value", "something else");
+
+ assert_equals(copy.value, "foo bar");
+}, "textarea element's dirty value flag should be cloned, so setAttribute doesn't affect the cloned textarea's value");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder-cr.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder-cr.html
new file mode 100644
index 0000000000..08d0982ba5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder-cr.html
@@ -0,0 +1 @@
+<!doctype html> <html class="reftest-wait"> <meta charset="utf-8"> <title>textarea multiline placeholder (CR)</title> <link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#attr-textarea-placeholder"> <meta name="assert" content="textarea element's placeholder preserves newlines (CR)"> <link rel="match" href="multiline-placeholder-ref.html"> <link rel="stylesheet" href="support/placeholder.css"> <textarea rows="5" placeholder="this is a multiline placeholder"></textarea> <textarea rows="5" placeholder="this is&#xd;a multiline&#xd;&#xd;placeholder"></textarea> <textarea rows="5" id="dynamic"></textarea> <script> document.querySelector("#dynamic") .setAttribute("placeholder", "this is\ra multiline\r\rplaceholder"); document.documentElement.classList.remove("reftest-wait"); </script> </html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder-crlf.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder-crlf.html
new file mode 100644
index 0000000000..b82a203076
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder-crlf.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>textarea multiline placeholder (CRLF)</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#attr-textarea-placeholder">
+<meta name="assert" content="textarea element's placeholder preserves newlines (CRLF)">
+<link rel="match" href="multiline-placeholder-ref.html">
+<link rel="stylesheet" href="support/placeholder.css">
+<textarea rows="5" placeholder="this is
+a multiline
+
+placeholder"></textarea>
+<textarea rows="5" placeholder="this is&#xd;&#xa;a multiline&#xd;&#xa;&#xd;&#xa;placeholder"></textarea>
+<textarea rows="5" id="dynamic"></textarea>
+<script>
+ document.querySelector("#dynamic")
+ .setAttribute("placeholder", "this is\r\na multiline\r\n\r\nplaceholder");
+ document.documentElement.classList.remove("reftest-wait");
+</script>
+</html>
+
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder-ref.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder-ref.html
new file mode 100644
index 0000000000..0234ed64c9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder-ref.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset="utf-8">
+<link rel="stylesheet" href="support/placeholder.css">
+<textarea rows="5" class="placeholder">this is
+a multiline
+
+placeholder</textarea>
+<textarea rows="5" class="placeholder">this is
+a multiline
+
+placeholder</textarea>
+<textarea rows="5" class="placeholder">this is
+a multiline
+
+placeholder</textarea>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder.html
new file mode 100644
index 0000000000..4e835a6f56
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/multiline-placeholder.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>textarea multiline placeholder</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#attr-textarea-placeholder">
+<meta name="assert" content="textarea element's placeholder preserves newlines">
+<link rel="match" href="multiline-placeholder-ref.html">
+<link rel="stylesheet" href="support/placeholder.css">
+<textarea rows="5" placeholder="this is
+a multiline
+
+placeholder"></textarea>
+<textarea rows="5" placeholder="this is&#xa;a multiline&#xa;&#xa;placeholder"></textarea>
+<textarea rows="5" id="dynamic"></textarea>
+<script>
+ document.querySelector("#dynamic")
+ .setAttribute("placeholder", "this is\na multiline\n\nplaceholder");
+ document.documentElement.classList.remove("reftest-wait");
+</script>
+</html>
+
+
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/placeholder-white-space-notref.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/placeholder-white-space-notref.html
new file mode 100644
index 0000000000..375bdef874
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/placeholder-white-space-notref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<style>
+ textarea {
+ max-width: 100px;
+ }
+</style>
+<textarea placeholder="This is a really long string that needs to be truncated"></textarea>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/placeholder-white-space.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/placeholder-white-space.tentative.html
new file mode 100644
index 0000000000..7af55643fb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/placeholder-white-space.tentative.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Textarea placeholder honors textarea's text-overflow</title>
+<link rel=author href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel=author href="https://mozilla.com" title="Mozilla">
+<link rel=mismatch href="placeholder-white-space-notref.html">
+<link rel=help href="https://github.com/w3c/csswg-drafts/issues/6669">
+<style>
+ textarea {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ max-width: 100px;
+ }
+</style>
+<textarea placeholder="This is a really long string that needs to be truncated"></textarea>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/support/placeholder.css b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/support/placeholder.css
new file mode 100644
index 0000000000..9aaed05c86
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/support/placeholder.css
@@ -0,0 +1,6 @@
+textarea.placeholder,
+textarea::placeholder {
+ /* revert browser styling of the placeholder */
+ color: GrayText; /* blink/webkit use colour */
+ opacity: 1.0; /* gecko uses opacity */
+}
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-maxlength.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-maxlength.html
new file mode 100644
index 0000000000..3ea6739518
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-maxlength.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>textarea maxlength</title>
+ <link rel="author" title="tigercosmos" href="mailto:phy.tiger@gmail.com">
+ <link rel=help href="https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+
+ <textarea id="none"></textarea>
+ <textarea id="negative" maxlength="-5"></textarea>
+ <textarea id="non-numeric" maxlength="not-a-number"></textarea>
+ <textarea id="assign-negative"></textarea>
+ <textarea id="assign-non-numeric"></textarea>
+
+ <script>
+ test(
+ function () {
+ assert_equals(document.getElementById("none").maxLength, -1);
+ }, "Unset maxlength is -1");
+
+ test(
+ function () {
+ assert_equals(document.getElementById("negative").maxLength, -1);
+ }, "Negative maxlength is always -1");
+
+ test(
+ function () {
+ assert_equals(document.getElementById("non-numeric").maxLength, -1);
+ }, "Non-numeric maxlength is -1");
+
+ test(
+ function () {
+ assert_throws_dom("INDEX_SIZE_ERR", function () {
+ document.getElementById("assign-negative").maxLength = -5;
+ });
+ }, "Assigning negative integer throws IndexSizeError");
+
+ test(
+ function () {
+ document.getElementById("assign-non-numeric").maxLength = "not-a-number";
+ assert_equals(document.getElementById("assign-non-numeric").maxLength, 0);
+ }, "Assigning non-numeric to maxlength sets maxlength to 0");
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-minlength.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-minlength.html
new file mode 100644
index 0000000000..2d40901b40
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-minlength.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>textarea minlength</title>
+ <link rel="author" title="tigercosmos" href="mailto:phy.tiger@gmail.com">
+ <link rel=help href="https://html.spec.whatwg.org/multipage/#attr-textarea-minlength">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+
+ <textarea id="none"></textarea>
+ <textarea id="negative" minlength=-5></textarea>
+ <textarea id="non-numeric" minlength="not-a-number"></textarea>
+ <textarea id="assign-negative"></textarea>
+ <textarea id="assign-non-numeric"></textarea>
+
+ <script>
+ test(
+ function () {
+ assert_equals(document.getElementById("none").minLength, -1);
+ }, "Unset minlength is -1");
+
+ test(
+ function () {
+ assert_equals(document.getElementById("negative").minLength, -1);
+ }, "Negative minlength is always -1");
+
+ test(
+ function () {
+ assert_equals(document.getElementById("non-numeric").minLength, -1);
+ }, "Non-numeric minlength is -1");
+
+ test(
+ function () {
+ assert_throws_dom("INDEX_SIZE_ERR", function () {
+ document.getElementById("assign-negative").minLength = -5;
+ });
+ }, "Assigning negative integer throws IndexSizeError");
+
+ test(
+ function () {
+ document.getElementById("assign-non-numeric").minLength = "not-a-number";
+ assert_equals(document.getElementById("assign-non-numeric").minLength, 0);
+ }, "Assigning non-numeric to minlength sets minlength to 0");
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-newline-bidi-ref.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-newline-bidi-ref.html
new file mode 100644
index 0000000000..d69195b4f4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-newline-bidi-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>HTML Test reference: newline in &lt;textarea&gt; separates bidi paragraphs</title>
+ <link rel="author" title="Amir E. Aharoni" href="mailto:amir.aharoni@mail.huji.ac.il"/>
+ <link rel="author" title="Eyal Sela" href="mailto:eyal@post.isoc.org.il"/>
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-textarea-element"/>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the rightmost character in the first line below is a full stop and to the left of it is a Hebrew letter.</p></div>
+ <div class="test">
+ <textarea cols="70" rows="3">
+A Hebrew letter and a full stop: &#x05d0;.&lrm;
+&#x05d0; this line begins with a Hebrew letter.
+ </textarea>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-newline-bidi.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-newline-bidi.html
new file mode 100644
index 0000000000..ce1ff944c2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-newline-bidi.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: newline in &lt;textarea&gt; separates bidi paragraphs</title>
+ <link rel="match" href="textarea-newline-bidi-ref.html" />
+ <link rel="author" title="Amir E. Aharoni" href="mailto:amir.aharoni@mail.huji.ac.il"/>
+ <link rel="author" title="Eyal Sela" href="mailto:eyal@post.isoc.org.il"/>
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-textarea-element"/>
+ <meta name="assert"
+ content="A newline in a textarea element, and in its raw value, should separate paragraphs for the purposes of the Unicode bidirectional algorithm."/>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the rightmost character in the first line below is a full stop and to the left of it is a Hebrew letter.</p></div>
+ <div class="test">
+ <textarea cols="70" rows="3">
+A Hebrew letter and a full stop: &#x05d0;.
+&#x05d0; this line begins with a Hebrew letter.
+ </textarea>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-placeholder-lineheight.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-placeholder-lineheight.html
new file mode 100644
index 0000000000..e7df07c97a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-placeholder-lineheight.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>textarea placeholder line-height</title>
+ <link rel="author" title="Daniel Libby" href="mailto:dlibby@microsoft.com">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style>
+ textarea {
+ margin: 0;
+ border: 0;
+ padding: 0;
+ }
+ </style>
+</head>
+
+<body>
+ <textarea rows=1 placeholder=foo style="border:0"></textarea>
+ <script>
+ let textarea = document.querySelector('textarea');
+ const lineHeight = 19.5;
+ textarea.style.lineHeight = lineHeight + "px";
+ test(
+ function () {
+ assert_equals(textarea.getBoundingClientRect().height, lineHeight);
+ }, "Bounding rect height for textarea must be the same as line-height");
+
+ test(
+ function () {
+ assert_equals(getComputedStyle(textarea).lineHeight, lineHeight + "px");
+ }, "ComputedStyle line-height for textarea must be the same as set value");
+ </script>
+</body>
+
+</html>
+
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-placeholder-manual.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-placeholder-manual.html
new file mode 100644
index 0000000000..d59a259415
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-placeholder-manual.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: textarea - placeholder attribute</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-textarea-placeholder">
+<meta name="flags" content="interact">
+<body>
+ <p>
+ Test passes if there is a "Placeholder Text" in the text area,
+ and if the "Placeholder Text" disappears after type in any character.
+ </p>
+ <textarea placeholder="Placeholder Text"></textarea>
+</body>
+
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-select-event-manual.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-select-event-manual.html
new file mode 100644
index 0000000000..f1679e2809
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-select-event-manual.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLTextAreaElement Test: select event</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<meta name="flags" content="interact">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<p>Select any numberic characters in the text area below</p>
+
+<form id="testForm" name="testForm">
+ <textarea id="testtextarea">0123456789</textarea>
+</form>
+
+<script>
+
+var textarea = document.getElementById("testtextarea");
+
+setup({explicit_done : true});
+setup({explicit_timeout : true});
+
+on_event(textarea, "select", function(evt) {
+ test(function() {
+ assert_greater_than(textarea.value.substring(textarea.selectionStart, textarea.selectionEnd).length, 0, "Check if select event captured when text selected");
+ });
+ done();
+});
+
+</script>
+
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-select-manual.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-select-manual.html
new file mode 100644
index 0000000000..4e98ba5093
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-select-manual.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLTextAreaElement Test: select()</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<meta name="flags" content="interact">
+
+<p>Test passes if content of the input area is selected</p>
+
+<textarea id="test_obj">1234567</textarea>
+<script>
+var textarea = document.querySelector("#test_obj");
+textarea.select();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-setcustomvalidity.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-setcustomvalidity.html
new file mode 100644
index 0000000000..922a1e73e6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-setcustomvalidity.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<title>textarea setCustomValidity</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<textarea id='textarea_test'></textarea>
+
+<script>
+
+test(() => {
+ let elem = document.getElementById("textarea_test");
+ assert_false(elem.validity.customError);
+ elem.setCustomValidity("custom error");
+ assert_true(elem.validity.customError);
+}, "textarea setCustomValidity is correct")
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-textLength.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-textLength.html
new file mode 100644
index 0000000000..d249278960
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-textLength.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<title>The textLengh IDL attribute</title>
+<meta content="charset=utf-16">
+<link rel="author" title="tigercosmos" href="mailto:phy.tiger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-textarea-textlength">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<textarea id="textarea"></textarea>
+<script>
+ var textarea = document.getElementById("textarea");
+
+ test(function () {
+ textarea.value= "Hello, World!";
+ assert_equals(textarea.textLength, 13);
+
+ textarea.value = "\u4f60\u597d\uff0c\u4e16\u754c\uff01"; //你好,世界!
+ assert_equals(textarea.textLength, 6);
+ }, "Textarea's 'testLength' should work for utf-16.");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-type.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-type.html
new file mode 100644
index 0000000000..ac80f93656
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-type.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<title>The type IDL attribute</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-textarea-type">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="test">
+<textarea></textarea>
+</div>
+<script>
+test(function() {
+ assert_equals(document.getElementById("test")
+ .getElementsByTagName("textarea")[0].type,
+ "textarea");
+}, "Textarea's type attribute should return 'textarea'");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-validity-clone.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-validity-clone.html
new file mode 100644
index 0000000000..23d90e714b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-validity-clone.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>HTML Test: &lt;textarea&gt; validity state is correct after a clone</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-textarea-element">
+<link rel="help" href="https://bugzil.la/1472169">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+ let form = document.createElement("form");
+ let textarea = document.createElement("textarea");
+ textarea.required = true;
+
+ textarea.appendChild(document.createTextNode("A"));
+ form.appendChild(textarea);
+
+ assert_true(textarea.validity.valid);
+
+ let formClone = form.cloneNode(true);
+ assert_equals(
+ formClone.querySelector('textarea').validity.valid,
+ textarea.validity.valid,
+ "Validity state should be preserved after a clone"
+ );
+}, "<textarea> validity state should be preserved after a clone");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/value-defaultValue-textContent-xhtml.xhtml b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/value-defaultValue-textContent-xhtml.xhtml
new file mode 100644
index 0000000000..9462e94935
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/value-defaultValue-textContent-xhtml.xhtml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>textarea element value/defaultValue/textContent functionality</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"/>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-textarea-value"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+"use strict";
+
+test(() => {
+
+ const textarea = document.createElement("textarea");
+
+ textarea.appendChild(document.createCDATASection("foo bar baz"));
+ assert_equals(textarea.defaultValue, "foo bar baz", "the defaultValue should reflect the textContent");
+ assert_equals(textarea.value, "foo bar baz",
+ "changing the child text content should change the raw value, and subsequently the api value");
+
+}, "defaultValue and value include CDATASection Text nodes");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/value-defaultValue-textContent.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/value-defaultValue-textContent.html
new file mode 100644
index 0000000000..a1a405fdbe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/value-defaultValue-textContent.html
@@ -0,0 +1,181 @@
+<!DOCTYPE HTML>
+<title>textarea element value/defaultValue/textContent functionality</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-textarea-value">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+
+ const textarea = document.createElement("textarea");
+
+ assert_equals(textarea.defaultValue, "", "defaultValue is empty string when it has no content");
+ assert_equals(textarea.value, "", "value is empty string when it has no content");
+
+}, "defaultValue and value are the empty string by default");
+
+test(() => {
+
+ const textarea = document.createElement("textarea");
+
+ textarea.textContent = "foo bar";
+ assert_equals(textarea.defaultValue, "foo bar", "the defaultValue should reflect the textContent");
+ assert_equals(textarea.value, "foo bar",
+ "changing the textContent should change the raw value, and subsequently the api value");
+
+}, "defaultValue and value are affected by setting textContent");
+
+test(() => {
+
+ const textarea = document.createElement("textarea");
+
+ textarea.textContent = "some text";
+ textarea.firstChild.nodeValue = "foo bar";
+ assert_equals(textarea.defaultValue, "foo bar", "the defaultValue should reflect the textContent");
+ assert_equals(textarea.value, "foo bar",
+ "changing the textContent should change the raw value, and subsequently the api value");
+
+}, "defaultValue and value are affected by setting nodeValue on a child text node");
+
+test(() => {
+
+ const textarea = document.createElement("textarea");
+
+ textarea.textContent = "some text";
+ textarea.firstChild.data = "foo bar";
+ assert_equals(textarea.defaultValue, "foo bar", "the defaultValue should reflect the textContent");
+ assert_equals(textarea.value, "foo bar",
+ "changing the textContent should change the raw value, and subsequently the api value");
+
+}, "defaultValue and value are affected by setting data on a child text node");
+
+test(() => {
+
+ const textarea = document.createElement("textarea");
+
+ textarea.textContent = "foo bar";
+ textarea.appendChild(document.createTextNode(" baz"));
+ assert_equals(textarea.defaultValue, "foo bar baz", "the defaultValue should reflect the textContent");
+ assert_equals(textarea.value, "foo bar baz",
+ "changing the textContent should change the raw value, and subsequently the api value");
+
+}, "defaultValue and value are affected by textContent in combination with appending a text node");
+
+test(() => {
+
+ const textarea = document.createElement("textarea");
+ textarea.textContent = "foo bar";
+
+ const frag = document.createDocumentFragment();
+ frag.appendChild(document.createTextNode(" baz"));
+ const el = document.createElement("span");
+ el.appendChild(document.createTextNode("qux?"));
+ frag.appendChild(el);
+ frag.appendChild(document.createTextNode(" fizz"));
+ textarea.appendChild(frag);
+
+ textarea.appendChild(document.createTextNode(" whee"));
+ assert_equals(textarea.defaultValue, "foo bar baz fizz whee", "the defaultValue should reflect the textContent");
+ assert_equals(textarea.value, "foo bar baz fizz whee",
+ "changing the textContent should change the raw value, and subsequently the api value");
+
+}, "defaultValue and value are affected by textContent in combination with appending a DocumentFragment");
+
+test(() => {
+
+ const textarea = document.createElement("textarea");
+ textarea.appendChild(document.createTextNode("foo bar"));
+
+ const child = document.createElement("span");
+ child.textContent = "baz";
+ textarea.appendChild(child);
+
+ assert_equals(textarea.textContent, "foo barbaz", "the textContent should have *all* the text content");
+ assert_equals(textarea.defaultValue, "foo bar", "the defaultValue should reflect the child text content");
+ assert_equals(textarea.value, "foo bar",
+ "changing the child text content should change the raw value, and subsequently the api value");
+
+}, "defaultValue and value reflect child text content, not textContent");
+
+test(() => {
+
+ const textarea = document.createElement("textarea");
+ textarea.appendChild(document.createTextNode("foo bar"));
+
+ const child = document.createElement("span");
+ child.textContent = "baz";
+ textarea.appendChild(child);
+
+ textarea.defaultValue = "foo";
+
+ assert_equals(textarea.childNodes.length, 1, "Only one child node should exist");
+ assert_equals(textarea.defaultValue, "foo", "the defaultValue should be the new text");
+ assert_equals(textarea.value, "foo", "the api value should be the new text");
+ assert_equals(textarea.textContent, "foo", "the textContent should be the new text");
+
+}, "Setting defaultValue wipes out any children, including elements (just like setting textContent)");
+
+test(() => {
+
+ const textarea = document.createElement("textarea");
+
+ textarea.textContent = "foo\r\nbar\rbaz\nqux";
+ assert_equals(textarea.defaultValue, "foo\r\nbar\rbaz\nqux", "the defaultValue should reflect the textContent");
+ assert_equals(textarea.value, "foo\nbar\nbaz\nqux", "The value property should normalize CRLF and CR to LF");
+
+}, "defaultValue and value treat CRLF differently");
+
+test(() => {
+
+ const textarea = document.createElement("textarea");
+
+ textarea.appendChild(document.createTextNode("foo\r"));
+ textarea.appendChild(document.createTextNode("\nbar\rbaz\nqux"));
+ assert_equals(textarea.defaultValue, "foo\r\nbar\rbaz\nqux", "the defaultValue should reflect the textContent");
+ assert_equals(textarea.value, "foo\nbar\nbaz\nqux", "The value property should normalize CRLF and CR to LF");
+
+}, "value normalizes CRLF even spread over multiple text nodes");
+
+test(() => {
+
+ const textarea = document.createElement("textarea");
+
+ textarea.textContent = "foo";
+ textarea.value = "baz";
+ assert_equals(textarea.defaultValue, "foo", "setting the value property should not affect the defaultValue");
+ assert_equals(textarea.textContent, "foo", "setting the value property should not affect the textContent");
+ assert_equals(textarea.value, "baz",
+ "on setting, the value property must set the element's raw & api value to the new value");
+
+ textarea.value = "foo\r\nbar\rbaz\nqux";
+ assert_equals(textarea.value, "foo\nbar\nbaz\nqux", "The API value should normalize CRLF and CR to LF");
+
+ textarea.value = null;
+ assert_equals(textarea.value, "", "setting the value property to null should result in an empty string");
+
+}, "tests for the value setter");
+
+test(() => {
+
+ const textarea = document.createElement("textarea");
+
+ textarea.defaultValue = "foo\0";
+ assert_equals(textarea.defaultValue, "foo\0", "defaultValue after setting defaultValue");
+ assert_equals(textarea.textContent, "foo\0", "textContent after setting defaultValue");
+ assert_equals(textarea.value, "foo\0", "value after setting defaultValue");
+
+ textarea.textContent = "bar\0";
+ assert_equals(textarea.defaultValue, "bar\0", "defaultValue after setting textContent");
+ assert_equals(textarea.textContent, "bar\0", "textContent after setting textContent");
+ assert_equals(textarea.value, "bar\0", "value after setting textContent");
+
+ textarea.value = "baz\0";
+ assert_equals(textarea.defaultValue, "bar\0", "defaultValue after setting value");
+ assert_equals(textarea.textContent, "bar\0", "textContent after setting value");
+ assert_equals(textarea.value, "baz\0", "value after setting value");
+
+}, "tests for U+0000 NULL");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-enumerated-ascii-case-insensitive-child.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-enumerated-ascii-case-insensitive-child.html
new file mode 100644
index 0000000000..92c9981a11
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-enumerated-ascii-case-insensitive-child.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+parent.postMessage(location.href, "*");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-enumerated-ascii-case-insensitive.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-enumerated-ascii-case-insensitive.html
new file mode 100644
index 0000000000..9bb16bb681
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-enumerated-ascii-case-insensitive.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/#attr-textarea-wrap">
+<link rel="help" href="https://html.spec.whatwg.org/#enumerated-attribute">
+<meta name="assert" content="textarea@wrap values are ASCII case-insensitive">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<form target="child" method="GET" action="wrap-enumerated-ascii-case-insensitive-child.html">
+ <textarea name="a" wrap="hard" cols="7">hello world</textarea>
+ <textarea name="b" wrap="HaRd" cols="7">hello world</textarea>
+ <textarea name="c" wrap="soft" cols="7">hello world</textarea>
+ <textarea name="d" wrap="SoFt" cols="7">hello world</textarea>
+ <textarea name="e" wrap="ſoft" cols="7">hello world</textarea>
+</form>
+<iframe name="child"></iframe>
+<script>
+// #dom-textarea-wrap reflects the content attribute, but it isn’t a nullable
+// DOMString, nor is it #limited-to-only-known-values, so we can’t just take
+// the shortcut of asserting the IDL attribute like most other attributes
+async_test(function() {
+ // we use a message rather than the iframe’s load event to avoid dealing with
+ // spurious load events that some browsers dispatch on the initial about:blank
+ addEventListener("message", this.step_func_done(event => {
+ const params = new URL(event.data).searchParams;
+
+ // #textarea-wrapping-transformation says that a UA-defined algorithm wraps
+ // values by inserting CRLF pairs, so "hello \r\nworld" and "hello w\r\norld"
+ // are two of many valid outcomes for cols=7
+ assert_true(params.get("a").includes("\r\n"), "lowercase “hard” valid");
+ assert_true(params.get("b").includes("\r\n"), "mixed case “hard” valid");
+ assert_false(params.get("c").includes("\r\n"), "lowercase “soft” valid");
+
+ // vacuous: the invalid value default is currently soft, so even if the UA
+ // treats this as invalid, the observable behaviour would still be correct
+ assert_false(params.get("d").includes("\r\n"), "mixed case “soft” valid");
+
+ // vacuous: the invalid value default is currently soft, so even if the UA
+ // treats this as valid, the observable behaviour would still be correct
+ assert_false(params.get("e").includes("\r\n"), "non-ASCII “soft” invalid");
+ }));
+
+ // we submit the form in GET mode to observe the values [#concept-fe-value] of
+ // the textareas, because IDL gives us the API value [#concept-fe-api-value],
+ // which isn’t subject to hard wrapping [#textarea-wrapping-transformation]
+ document.querySelector("form").submit();
+}, "keywords");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-reflect-1-ref.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-reflect-1-ref.html
new file mode 100644
index 0000000000..98a7f8a3af
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-reflect-1-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<title>Dynamic manipulation of textarea.wrap</title>
+<link rel=help href=https://html.spec.whatwg.org/multipage/#dom-textarea-wrap>
+<link rel=author title=Ms2ger href=mailto:ms2ger@gmail.com>
+<textarea wrap=soft cols=20>01234567890 01234567890 01234567890</textarea>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-reflect-1a.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-reflect-1a.html
new file mode 100644
index 0000000000..b3baa79d7a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-reflect-1a.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Dynamic manipulation of textarea.wrap</title>
+<link rel=match href=wrap-reflect-1-ref.html>
+<link rel=help href=https://html.spec.whatwg.org/multipage/#dom-textarea-wrap>
+<link rel=author title=Ms2ger href=mailto:ms2ger@gmail.com>
+<textarea wrap=off cols=20>01234567890 01234567890 01234567890</textarea>
+<script>
+document.getElementsByTagName("textarea")[0].wrap = "soft";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-reflect-1b.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-reflect-1b.html
new file mode 100644
index 0000000000..b0a9b460f0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrap-reflect-1b.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Dynamic manipulation of textarea.wrap</title>
+<link rel=match href=wrap-reflect-1-ref.html>
+<link rel=help href=https://html.spec.whatwg.org/multipage/#dom-textarea-wrap>
+<link rel=author title=Ms2ger href=mailto:ms2ger@gmail.com>
+<textarea wrap=off cols=20>01234567890 01234567890 01234567890</textarea>
+<script>
+document.getElementsByTagName("textarea")[0].setAttribute("wrap", "soft");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrapping-transformation.window.js b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrapping-transformation.window.js
new file mode 100644
index 0000000000..c5c28a4854
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/wrapping-transformation.window.js
@@ -0,0 +1,58 @@
+test((t) => {
+ const form = document.createElement("form");
+ const textarea = document.createElement("textarea");
+ textarea.name = "linebreakTest";
+ textarea.textContent = "a\nb\rc\r\nd\n\re";
+ form.appendChild(textarea);
+ document.body.appendChild(form);
+ t.add_cleanup(() => {
+ document.body.removeChild(form);
+ });
+
+ assert_equals(textarea.textContent, "a\nb\rc\r\nd\n\re");
+ assert_equals(textarea.value, "a\nb\nc\nd\n\ne");
+
+ const formData = new FormData(form);
+ assert_equals(
+ formData.get("linebreakTest"),
+ "a\nb\nc\nd\n\ne",
+ );
+}, "Textarea wrapping transformation: Newlines should be normalized to LF.");
+
+test((t) => {
+ const form = document.createElement("form");
+ const textarea = document.createElement("textarea");
+ textarea.name = "wrapTest";
+ textarea.cols = 10;
+ textarea.wrap = "hard";
+ textarea.textContent =
+ "Some text that is too long for the specified character width.";
+ form.appendChild(textarea);
+ document.body.appendChild(form);
+ t.add_cleanup(() => {
+ document.body.removeChild(form);
+ });
+
+ assert_true(
+ !textarea.textContent.includes("\n") &&
+ !textarea.textContent.includes("\r"),
+ "textContent shouldn't contain any newlines",
+ );
+ assert_true(
+ !textarea.textContent.includes("\n") &&
+ !textarea.textContent.includes("\r"),
+ "The API value shouldn't be line wrapped.",
+ );
+
+ const formData = new FormData(form);
+ const formDataValue = formData.get("wrapTest");
+
+ assert_true(
+ !formDataValue.includes("\r"),
+ "The wrapping done on the value must be LF, not CRLF.",
+ );
+ assert_true(
+ formDataValue.includes("\n"),
+ "The value must be wrapped.",
+ );
+}, "Textarea wrapping transformation: Wrapping happens with LF newlines.");