summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/html/semantics
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/html/semantics')
-rw-r--r--testing/web-platform/tests/html/semantics/disabled-elements/disabled-checkbox-click.html26
-rw-r--r--testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch-additional.tentative.html74
-rw-r--r--testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch.tentative.html57
-rw-r--r--testing/web-platform/tests/html/semantics/disabled-elements/disabledElement.html44
-rw-r--r--testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled-keyboard.tentative.html135
-rw-r--r--testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled.tentative.html264
-rw-r--r--testing/web-platform/tests/html/semantics/disabled-elements/fieldset-event-propagation.tentative.html55
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/conditionally-block-rendering-on-link-media-attr.html27
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/dynamic-render-blocking-link-stylesheet-does-not-block-script.html21
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/dynamic-render-blocking-style-element-does-not-block-script.html21
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/link-stylesheet-with-non-match-media-does-not-block-render.tentative.html21
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/script-created-link-stylesheet-does-not-block-script.html20
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/script-created-style-element-does-not-block-script.html20
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/style-element-media-match-block-script.html17
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/style-element-media-not-match-does-not-block-script.html17
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/stylesheet.py10
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/support/link-style.css3
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/support/utils.js20
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/styling/LinkStyle.html88
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/styling/support/alternate.css7
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/styling/support/emptytitle.css4
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/styling/support/normal.css5
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/styling/support/notitle.css4
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/styling/support/unmatch.css4
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base-data.html32
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base-javascript.html32
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_about_blank.html19
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_empty.html29
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_invalid.html12
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_specified.html33
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_unspecified.html30
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_multiple.html29
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_srcdoc.html19
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_target_does_not_affect_iframe_src_navigation.html10
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_target_does_not_affect_location_assignment.html10
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-base-element/example.html7
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-base-element/example2.html5
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/all3
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/all.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/document-without-browsing-context.html35
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-error-fired-before-scripting-unblocked.html25
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-error-events.html6
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-error-events.https.html6
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-event.html17
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-fired-before-scripting-unblocked.html24
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-multiple-error-events.html21
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-multiple-load-events.html21
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rel-attribute-ascii-case-insensitive-notref.html5
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rel-attribute-ascii-case-insensitive.html22
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rel-attribute.html44
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rellist.html25
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-style-error-01.html37
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-style-error-limited-quirks.html7
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-style-error-quirks.html7
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-type-attribute-ref.html3
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-type-attribute.html9
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/bad.css4
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/css.py7
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/empty-href.css3
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/good.css3
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/link-load-error-events.sub.js192
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/link-rel-attribute.css3
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/link-style-error.js47
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/neutral.css3
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/stylesheet.css3
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/style.css3
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-change-href-ref.html8
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-change-href.html13
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-empty-href-ref.html9
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-empty-href.html12
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-media-ref.html9
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-media.html17
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-not-removed-until-next-stylesheet-loads.html22
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-with-base-ref.html11
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-with-base.html11
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet.css3
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet.py9
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-attribute-changes.html35
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-empty-content-value.html14
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-first-valid-applies.html16
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-insert.html26
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-no-content-value.html14
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-normal-descendant-change.html20
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-remove-head.html17
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-remove.html19
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-single-value-in-body.html12
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-single-value-in-head.html10
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-single-value-in-shadow-tree.html22
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/support/compute-root-color-scheme.js28
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/http-equiv-and-name-1.html23
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/http-equiv-and-name-2.html13
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-1.html57
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-2.html55
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/dynamic-append.html31
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/not-in-shadow-tree.html38
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/parsing.html147
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/remove-from-document.html37
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/;url=foo1
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/__dir__.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/foo1
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/foo'bar1
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/refresh.py4
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/refresh.sub.html1
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/ufoo1
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/urfoo1
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/url foo1
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/urlfoo1
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/x;url=foo1
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-lower.html5
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-message.js1
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-mixed.html5
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-other.html5
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive.html32
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/the-lang-attribute-012.html51
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/historical.html14
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/html_style_in_comment-ref.html18
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/html_style_in_comment.xhtml18
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/mutations.window.js48
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style-error-01.html32
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style-load-after-mutate.html16
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_disabled.html39
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_events.html36
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_load_async.html25
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_load_event.html28
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_media.html40
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_media_change.html43
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_non_matching_media.html20
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_type_change.html39
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_type_html.html71
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_type_svg.svg75
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/support/css-red.txt1
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/update-style-block-ascii-case-insensitive-ref.html9
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-style-element/update-style-block-ascii-case-insensitive.html16
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-01.html25
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-02.xhtml30
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-03.html32
-rw-r--r--testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-04.xhtml37
-rw-r--r--testing/web-platform/tests/html/semantics/edits/the-del-element/del_effect.html19
-rw-r--r--testing/web-platform/tests/html/semantics/edits/the-ins-element/ins_effect.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/META.yml2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-html.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-img.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-js.html11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-mp4.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-not-found.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-type-only.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/bfcache/resources/common.js46
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference-test-data.html254
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference.html45
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_controls_present-manual.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_loop_base.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_loop_seek_to_eos.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_muted_overriding_volume-manual.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_muted_present-manual.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_volume_check.html58
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_volume_loudest-manual.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_volume_silent-manual.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html35
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy-attribute.https.sub.html34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy.https.sub.html39
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-default-feature-policy.https.sub.html39
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-disabled-by-feature-policy.https.sub.html39
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-disabled-by-feature-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-supported-by-feature-policy.html11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-with-broken-track.html34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/controlsList.tentative.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/error-codes/error.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplay.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplay_noautoplay.html34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplaythrough.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplaythrough_noautoplay.html34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadeddata.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadeddata_noautoplay.html34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadedmetadata.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadedmetadata_noautoplay.html34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadstart.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadstart_noautoplay.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_canplay_canplaythrough.html50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_canplay_playing.html50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_loadedmetadata_loadeddata.html50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_loadstart_progress.html50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_pause.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_pause_noautoplay.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_play.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_play_noautoplay.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_playing.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_playing_noautoplay.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_progress.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_progress_noautoplay.html34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_timeupdate.html34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_timeupdate_noautoplay.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_volumechange.html72
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/historical.html56
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/addTextTrack.html116
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/crossOrigin.html60
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/textTracks.html13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/default.html55
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/kind.html146
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/label.html83
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/readyState.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/src.html43
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/srclang.html82
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/track.html12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/activeCues.html101
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/addCue.html68
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/constants.html23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/cues.html100
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/kind.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/label.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/language.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/mode.html55
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/oncuechange.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/removeCue.html50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/constructor.html23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/endTime.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/id.html39
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/onenter.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/onexit.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/pauseOnExit.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/startTime.html39
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/track.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCueList/getCueById.html53
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCueList/getter.html49
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCueList/length.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/getTrackById.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/getter.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/length.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/onaddtrack.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/onremovetrack.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TrackEvent/constructor.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TrackEvent/createEvent.html15
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/autoplay-overrides-preload.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-events-networkState.html91
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-removes-queued-error-event.html44
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-insert-before.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-moved.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-remove-addEventListener.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-remove-no-listener.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-remove-onerror.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-currentSrc.html93
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-audio-constructor-no-src.html13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-audio-constructor.html12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-in-sync-event.html30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-fragment-into-document.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-into-document.html12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-into-iframe.html23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-parent-into-document.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-in-div.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-in-namespace.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-networkState.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-not-in-document.html13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-load.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-pause-networkState.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-pause.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-play.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-from-document-networkState.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-from-document.html15
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-src.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-in-namespace.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-networkState.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-not-in-document.html13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-control.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-insert-br.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-insert-source.html44
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-insert-text.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-remove-source-after.html56
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-remove-source.html43
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-remove-text.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-remove-source.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-remove-src.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-resumes-onload.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-source-media-env-change.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-source-media.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resources/delayed-broken-video.py5
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resources/media-min-width.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/stable-state-beforeunload-manual.html30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/stable-state-dialogs-manual.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/stable-state-print-manual.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/location-of-the-media-resource/currentSrc.html48
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/media_fragment_seek.html49
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/mime-types/canPlayType.html123
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/networkState_during_loadstart.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/networkState_during_progress.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/networkState_initial.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/offsets-into-the-media-resource/currentTime.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/offsets-into-the-media-resource/duration.html11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/paused_false_during_play.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/paused_true_during_pause.html48
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/pitch-detector.js58
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html57
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-move-to-other-document.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-move-within-document.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-remove-from-document-different-load.html44
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-remove-from-document-networkState.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-remove-from-document.html37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/play-in-detached-document.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/playbackRate.html53
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/preload_reflects_none_autoplay.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/preserves-pitch.html132
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/ready-states/autoplay-hidden.optional.html37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/ready-states/autoplay-with-slow-text-tracks.html46
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/ready-states/autoplay.html73
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_canplay.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_canplaythrough.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_loadeddata.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_loadedmetadata.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_playing.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_initial.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/seeking/seek-to-currentTime.html34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/seeking/seek-to-max-value.htm23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/seeking/seek-to-negative-time.htm23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/src_object_blob.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/src_reflects_attribute_not_source_elements.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cloneNode.html87
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/003.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/004.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/005.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/006.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/007.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/008.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/009.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/010.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/011.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/012.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/013.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/014.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/015.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/016.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/017.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/018.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/019.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/020.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/021.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/022.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/023.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/024.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/025.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/026.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/027.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/028.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/029.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/030.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/031.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/032.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/033.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/034.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/035.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/036.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/037.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/038.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/039.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/040.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/041.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/042.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/043.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/044.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/045.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/046.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/common.js144
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/cors-tester.py50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/remove-cookie.html6
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/set-cookie.html6
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/crashtests/track-element-src-aborted-load-onerror-crash.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning-bad.vtt20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning.vtt20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position-bad.vtt21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position.vtt28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-bad.vtt22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-ltr.vtt22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment.vtt22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/bom.vtt10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-fast.vtt13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-gaps.vtt18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-html.vtt18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions.vtt18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/class-bad.vtt17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/class.vtt14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id-error.vtt14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id.vtt18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id-error.vtt14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id.vtt11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-cuetext.vtt6
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-header.vtt6
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-note.vtt9
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align-bad.vtt18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align.vtt19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-bad.vtt17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size.vtt19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-chrono-order.vtt14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-no-separation.vtt11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-overlapping.vtt14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues.vtt17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/default-styles.vtt19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/degenerate-cues.vtt5
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/empty-cue.vtt11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/entities-wrong.vtt15
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/entities.vtt30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/interspersed-non-cue.vtt9
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/iso2022jp3.vtt10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/large-timestamp.vtt5
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position-bad.vtt30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position.vtt37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/markup-bad.vtt22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/markup.vtt22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata-area.vtt14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata.vtt38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/missed-cues.vtt31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/no-newline-at-eof.vtt6
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/no-timings.vtt13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/no-webvtt.vtt10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-bad.vtt39
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-ltr.vtt21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning.vtt21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/settings-bad-separation.vtt20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/settings.vtt18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/simple-captions.vtt17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/sorted-dispatch.vtt34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp-bad.vtt17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp.vtt14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour-error.vtt22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour.vtt14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour-errors.vtt22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour.vtt18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-whitespace.vtt51
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.de.vtt4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.en.vtt4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.fr.vtt4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.vtt4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/unsupported-markup.vtt23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/utf8.vtt10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-bad.vtt17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-ltr.vtt20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/valign.vtt20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/voice-bad.vtt17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/voice.vtt15
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/vp8-vorbis-webvtt.webmbin0 -> 143662 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-file.vtt9
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-rubbish.vtt10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/src-clear-cues.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/src-empty-string.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-active-cues.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-add-remove-cue.html92
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-add-track.html34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-addtrack-kind.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-api-texttracks.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-change-event.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-css-cue-pseudo-class.html12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-empty.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-inline.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable-fragment.html85
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable.html99
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-duration.html30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp-events.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-order.html83
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added-ref.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added.html37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed-ref.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed.html34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit-ref.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video-ref.html35
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-cuechange-dynamically-created-track-element.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-cuechange.html41
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-enter-exit.html44
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-enter-seeking.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-missed.html59
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-pause-on-exit.html35
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-seeking.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-sorted-before-dispatch.html48
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-data-url.html30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-default-attribute.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-delete-during-setup.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-disabled-addcue.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-disabled.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-dom-change.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-aborted-load.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change-error.html86
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change.html55
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-helpers.js83
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-id.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-insert-after-load.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-large-timestamp.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-load-error-readyState.html15
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-element-readyState.html15
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-src-readyState.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode-disabled.html34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode-not-changed-by-new-track.html74
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode-triggers-loading.html37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode.html76
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-node-add-remove.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-active-cue.html35
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-by-setting-innerHTML.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-insert-ready-state.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-quickly.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-track-inband.html79
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-track.html39
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-selection-metadata.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-selection-task-order.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-text-track-cue-list.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-texttracks.html44
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-positioning.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-text-line-position.html54
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-alignment.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-blank-lines.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-bom.html34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-class-markup.html56
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-identifiers.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-no-id.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-recovery.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size-align.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-degenerate-cues.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-empty-cue.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-entities.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-header-comment.html50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-interspersed-non-cue.html15
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-line-position.html58
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-magic-header.html57
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-markup.html90
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-newlines.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-no-timings.html17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines-ref.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-positioning.html35
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-settings.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timestamp.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-hour.html61
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-no-hours.html67
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-whitespace.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end-ref.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-unsupported-markup.html37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-utf8.html57
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-valign.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-voice.html54
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/vtt-cue-float-precision.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/user-interface/muted.html169
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_controls_present-manual.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_loop_base.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_muted_overriding_volume-manual.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_muted_present-manual.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_volume_check.html58
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_volume_loudest-manual.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_volume_silent-manual.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/media-elements/volume_nonfinite.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/resources/common.js45
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/resources/not-embeddable.html3
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/resources/not-embeddable.html.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/resources/should-load.html3
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/resources/should-not-load.html5
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-coords.html47
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-download-click.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-processing.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-shape.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-stringifier.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-area-element/resources/area-download-click.html5
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-area-element/support/hit-test.js42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio-appendChild-to-inactive-document-crash.html6
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio-play-in-inactive-document-crash.html8
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_001.htm18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_002.htm18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_constructor.html56
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_content-ref.htm13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/2d-getcontext-options.html47
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-001.html39
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-002.html39
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-003.tentative.html48
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-004.tentative.html47
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-005.html61
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.arguments.missing.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.casesensitive.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.emptystring.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.badname.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.badsuffix.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.nullsuffix.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.unicode.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/fallback.basic.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/fallback.multiple.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/fallback.nested.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/historical.html77
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/imagedata.html58
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.colour.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.colour.pngbin0 -> 117 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.clip.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.different.html30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.different.pngbin0 -> 107 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.gradient.html35
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.path.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.path.pngbin0 -> 117 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.pattern.html35
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.same.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.same.pngbin0 -> 117 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.transform.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.dataURI.html39
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.canvas.cross.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.canvas.redirect.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.image.cross.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.image.redirect.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.fillStyle.cross.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.fillStyle.redirect.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.strokeStyle.cross.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.strokeStyle.redirect.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.timing.cross.html41
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.timing.redirect.html41
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.create.cross.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.create.redirect.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.cross.cross.html39
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.cross.redirect.html39
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.fillStyle.sub.html50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.fillStyle.cross.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.fillStyle.redirect.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.strokeStyle.cross.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.strokeStyle.redirect.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.reset.cross.html35
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.reset.redirect.html35
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.default.html30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.default.pngbin0 -> 272 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.get.pngbin0 -> 125 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.idl.html50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.idl.set.zero.html30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setcontent.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setcontent.pngbin0 -> 125 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setidl.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setidl.pngbin0 -> 125 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setidlzero.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.removed.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.removed.pngbin0 -> 168 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.set.pngbin0 -> 125 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.style.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.style.pngbin0 -> 117 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob-cross-realm-callback-report-exception.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob.jpeg.html17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob.null.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob.png.html17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.arguments.1.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.arguments.2.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.arguments.3.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.bogustype.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.default.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.alpha.html44
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.alpha.pngbin0 -> 208 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.primarycolours.html47
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.primarycolours.pngbin0 -> 220 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.basic.html56
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.basic.pngbin0 -> 213 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.notnumber.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.outsiderange.html43
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpg.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.lowercase.ascii.html35
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.lowercase.unicode.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.nocontext.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.complexcolours.html47
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.complexcolours.pngbin0 -> 242 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.primarycolours.html47
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.primarycolours.pngbin0 -> 220 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.unrecognised.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.zeroheight.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.zerosize.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.zerowidth.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.delete.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.exists.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.extend.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.name.html27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.prototype.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.replace.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/document-getters-return-null-for-cross-origin.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-change-src.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-dimension.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-document-under-content-visibility-focus.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-document-under-content-visibility-gbcr.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-document.html17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-hidden-attribute-ref.html13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-hidden-attribute.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-iframe.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-ignored-in-media-element.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-in-object-fallback-2.html56
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-in-object-fallback-subdocument.html4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-in-object-fallback.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-named-attribute-detached-context-crash.html13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-network-error.sub.html54
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-01.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-02.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-03.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-04.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-ref.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-svg-navigation-resets-size.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/historical.html15
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-frame-element/document-getters-return-null-for-cross-origin.html13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/change_child.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/change_grandchild.html4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/change_parentage.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/content_document_changes_only_after_load_matures.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross-origin-to-whom-part-2.window.js59
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross-origin-to-whom.window.js37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross_origin_child.html12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross_origin_grandchild.html11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross_origin_parentage.sub.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/document-getters-return-null-for-cross-origin.html17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/historical.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/hittest-detached-iframe-crash.html9
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-allow.html66
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-allowfullscreen.html95
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-append-to-child-document.html17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-display-none-with-object.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-document-move-crash.html13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-first-load-canceled-second-load-blank.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-initially-empty-is-updated-ref.html6
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-initially-empty-is-updated.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-load-event.html43
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-eager.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-base-url-2.html49
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-base-url-3.html45
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-base-url.html45
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-script-disabled-iframe.html23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-far.html49
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-horizontal-far.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-horizontal.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-2.html57
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-3.html65
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-4.html58
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-5.html61
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested.html57
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller.html49
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-viewport-001.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-viewport-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-load-event.html47
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-multiple-queued-navigations.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-multiple-times.html69
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-referrerpolicy-change.sub.html45
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-to-eager.html55
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy.html110
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-modify-scrolling-attr-to-yes-ref.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-modify-scrolling-attr-to-yes.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-network-error.sub.html54
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-nosrc.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-synchronously-discard.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-with-base-ref.html9
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-with-base.html11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_harness.js27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_01.htm61
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_navigate_ancestor-1.sub.html12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_remove_src.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_script.html46
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation-1.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation-2.html23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation-3.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation_by_user_activation-manual.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation_by_user_activation_without_user_gesture.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_allow_downloads.tentative.html48
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_block_downloads.tentative.html73
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-1.html13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-4.html13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_ancestor-1.html12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_ancestor-2.html12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_descendants.html12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_history_go_back-2.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_history_go_back.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_history_go_forward.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_itself.html12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_other_frame_popup.sub.html64
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_allow_downloads.sub.tentative.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_block_downloads.sub.tentative.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-1.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-2.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_helper-1.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_helper-2.html28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_helper-3.html17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html15
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-2.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html15
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_window_open_download_allow_downloads.tentative.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_window_open_download_block_downloads.tentative.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_01.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_02.html23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_03.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_04.html23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/multiple-iframes-with-allow-scripts-crash.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/empty.html1
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/hello-world.html11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/iframe-loading-lazy-in-viewport.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/post-origin-to-opener.html3
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/sandbox-top-navigation-helper.js76
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/subframe.html4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/unload-reporter.html8
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/same_origin_child.html12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/same_origin_grandchild.html11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/same_origin_parentage.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-ascii-case-insensitive.html41
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-inherit-to-blank-document-unsandboxed-frame.html87
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-inherit-to-blank-document-unsandboxed.html100
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-inherit-to-blank-document-unsandboxed.html.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-toggle-in-inactive-document-crash.html9
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child-special-cases.tentative.sub.window.js47
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child.tentative.sub.window.js56
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-escalate-privileges.tentative.sub.window.js63
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-grandchild.tentative.sub.window.js50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_001.htm32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_002.htm25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_003-manual.htm32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_004.htm27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_005.htm33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_006-manual.htm37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_007-manual.htm37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_008-manual.htm37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_010-manual.htm37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_011.htm65
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_012.htm34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_013.htm36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_014.htm34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_015.htm34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_016.htm33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_017.htm34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_018.htm34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_019.htm34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_020-manual.htm34
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_021-manual.htm44
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_022-manual.htm37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_023.htm33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_024.htm32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_025.htm30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_026.htm32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_027.htm32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_028.htm33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_029.htm32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_030.htm31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_031.htm32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_032.htm32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/src-repeated-in-ancestor.html138
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc-anchor.html17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc-attribute-reset.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc_change_hash.html68
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc_process_attributes.html76
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/stash.py5
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/blank.htm1
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/document-with-embedded-svg.html9
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/download_stash.py32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-checks-contentDocument.html3
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-opens-modals.html23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-performs-top-navigation-on-popup.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-performs-top-navigation-without-user-gesture-failed.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-performs-top-navigation.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-send-message-to-the-opener.html8
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-tries-to-navigate-parent-and-sends-result-to-grandparent.html11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-tried-to-be-navigated-by-history.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-tried-to-be-navigated-by-its-child.html4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-trying-to-navigate-its-child.html11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-trying-to-navigate-itself.html11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-which-content-height-equals-400px.html4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-with-object.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_001.htm11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_002.htm21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_003.htm12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_004.htm11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_006.htm12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_007.htm12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_008.htm9
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_010.htm9
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_012.htm13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_020.htm28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_020a.htm13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_021.htm28
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_021a.htm13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_022.htm11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_023.htm15
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_024.htm13
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_026.htm12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_027.htm21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_028.htm20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_029.htm19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_031.htm19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_032.htm27
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_block_modals.js18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_download_helper.js37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/load-into-the-iframe.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/navigation-changed-iframe.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox.pdfbin0 -> 80990 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox.pdf.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox_allow_script.html8
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox_helper.js14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/standalone-fail.htm9
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/standalone-iframe-content.htm9
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/standalone-pass.htm9
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/svg.svg1
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/3.jpgbin0 -> 91072 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.py4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/Image-constructor.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adopt-from-image-document.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adoption.html91
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/already-loaded-image-sync-width.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-onload.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images.html17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/below-viewport-image-loading-lazy-load-event.html69
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/brokenimg.jpg4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/basic.html37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/error.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/currentSrc-blob-cache.html45
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/data-url.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-iframe.html54
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-image-document.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes-svg.tentative.html82
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes.html121
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-picture.html133
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-svg.tentative.html128
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach-svg.tentative.html23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode.html138
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-detached.html30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-until-move-to-empty-source.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/disconnected-image-loading-lazy.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-adopt-base-url.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url-ref.html5
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-destroyed-crash.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/iframed.sub.html78
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/viewport-change.html65
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/historical-progress-event.window.js16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-1.jpgbin0 -> 389245 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-base-url.html70
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change-ref.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change-ref.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-eager.html47
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-available.html30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url-2.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url.html51
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-below-viewport-dynamic.html45
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-crossorigin-change.sub.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-different-crossorigin.html63
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-empty-src.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-001.sub.html44
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-002.sub.html46
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-script-disabled-iframe.html23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-far.html49
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal-far.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-2.html57
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-3.html65
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-4.html58
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-5.html61
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested.html57
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller.html49
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-viewport-dynamic.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-document.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-into-script-disabled-iframe.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multicol.html44
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multiple-times.html60
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-negative-margin.html61
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-referrerpolicy-change.sub.html46
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-relevant-mutations.html83
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-srcset.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-subframe-detached-crash.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-to-eager.html55
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-use-list-of-available-images.html62
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-zero-intersection-area.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy.html112
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip-ref.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print-ref.html8
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print.html6
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image.pngbin0 -> 268 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-created-in-active-document-crash.html6
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-picture-ancestor.html50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-src-in-synthetic-document.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size-ref.html8
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img.complete.html200
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invalid-src.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invisible-image.html78
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-after.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-before.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-inside.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-manual.html78
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-does-not-coalesce-in-flight-requests.sub.tentative.html49
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-matching.https.html68
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/move-element-and-scroll.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/natural-size-orientation.html45
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/non-active-document.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/nonexistent-image.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-below-viewport-image-loading-lazy.html63
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-dimension-getter.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-image-loading-lazy.html49
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/null-image-source.html30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/picture-loading-lazy.html64
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations-lazy.html78
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations.html628
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/remove-element-and-scroll.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/blue-10.pngbin0 -> 76 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/cat.jpgbin0 -> 21474 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/green.pngbin0 -> 91 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-and-stash.py44
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-below-viewport.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-in-viewport.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image.pngbin0 -> 11493 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/newwindow.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/red.pngbin0 -> 510 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/referrer-checker-img.py14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print-ref.html4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print.html12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/scrolling-below-viewport-image-lazy-loading-in-iframe.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/implicit-sizes-ignores-width.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-display-none.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-quirks-mode.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-standards-mode.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-width-1000px.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/reference/sizes-auto-rendering-ref.html8
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-2.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-3.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-dynamic.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto.html153
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001-ref.html4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-002.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/parse-a-sizes-attribute.js29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/sizes-iframed.sub.html186
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/source-media-outside-doc.html50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/avoid-reload-on-resize.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/common.js25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/parse-a-srcset-attribute.html245
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.pngbin0 -> 268 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png.headers3
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/resized.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/select-an-image-source.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/srcset-media-dynamic.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/external-sheet.svg4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/red-bg.css2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/relevant-mutations.js15
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet-ref.html4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet.html6
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-media.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-src-complete.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/current-request-microtask.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/fail-to-resolve.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-source-set.html140
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/usemap-casing.html93
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/block-object-with-ruby-crash.html11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/document-getters-return-null-for-cross-origin.html17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/historical.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-attributes.html60
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-construct-in-document-with-null-browsing-context-crash.html11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-events.html81
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-fallback-failed-cross-origin-navigation.sub.html68
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-handler.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-ignored-in-media-element.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-in-display-none-load-event.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-in-object-fallback-2.html56
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-param-url-ref.html39
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-param-url.html41
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-remove-param-crash.html11
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-setcustomvalidity.html17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/test0.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/test1.html9
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-object-element/usemap-casing.html82
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/intrinsic_sizes.htm71
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/resize-during-playback.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-import-to-inactive-document-crash.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-poster-shown-preload-auto-ref.html5
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-poster-shown-preload-auto.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-tabindex.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_content-ref.htm12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_content_image.htm16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_content_text.htm16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_crash_empty_src.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_dynamic_poster-ref.htm5
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_dynamic_poster_absolute.htm12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_dynamic_poster_relative.htm12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_initially_paused-ref.html8
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_initially_paused.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_size_preserved_after_ended.html30
-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.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-only-if-applies.html80
-rw-r--r--testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-auto.html31
-rw-r--r--testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-inherited.html32
-rw-r--r--testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-manual.html29
-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/attributes-common-to-form-controls/resources/dirname-iframe.html4
-rw-r--r--testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/resources/dirname.js12
-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.html53
-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.tentative.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/constraints/input-maxlength-emoji.html15
-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.js21
-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-requestsubmit.html142
-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.js357
-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.js223
-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.js223
-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.html154
-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.html232
-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.html58
-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/disabled-003.html39
-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-shadow.html26
-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/auto-direction-dynamic.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/auto-direction-ref.html2
-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/change-to-empty-value.html31
-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-on-blur.html61
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/focus-dynamic-type-change.html55
-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-disabled-fieldset-dynamic.html38
-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-importNode-to-detached-document-crash.html11
-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-change-empty-crash.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-change-value.html33
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.js19
-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-type-number-rtl-invalid-crash.html11
-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-constraint-validation.html42
-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.html117
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/placeholder-update-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/placeholder-update.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-input-element/radio-disconnected-group-owner.html168
-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-keyboard-navigation-order.html70
-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.html245
-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.html56
-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-optgroup-element/optgroup-removal.window.js7
-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.html103
-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/resources/show-picker-child-iframe.html20
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/resources/stylable-select-styles.css18
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-add-optgroup.html29
-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-child-button-and-datalist-ref.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-child-button-and-datalist.tentative.html18
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/select-in-table-crash.html4
-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-parsing.tentative.html112
-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-select-element/show-picker-being-cv-hidden.html23
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-being-rendered.html21
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-cross-origin-iframe.tentative.html79
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-disabled.tentative.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-multiple.tentative.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-size.tentative.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-user-gesture.tentative.html24
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/button-type-selectlist-appearance-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/button-type-selectlist-appearance.tentative.html6
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-ask-for-reset.html119
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-closes-listbox.tentative.html51
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-type-appearance-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-type-appearance.tentative.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-type-behavior.tentative.html45
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-default-button-slot-ref.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-default-button-slot.tentative.html10
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-disabled.tentative.html28
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-events.tentative.html244
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-explicit-size-ref.tentative.html10
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-explicit-size.tentative.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-font-size-ref.tentative.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-font-size.tentative.html13
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-attribute.tentative.html231
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-elements.tentative.html25
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-state-restore.tentative.html39
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-submission.tentative.html57
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-keyboard-behavior.tentative.html182
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-keyboard.tentative.html142
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-labels.tentative.html29
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-listbox-element-ref.html17
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-listbox-element.tentative.html16
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-listbox-fallback-change-crash.tentative.html35
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-many-options.tentative.html140
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-end-aligned-ref.tentative.html19
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-end-aligned.tentative.html23
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-part-ref.html9
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-part.tentative.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-slot-ref.html13
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-slot.tentative.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-visible-overflow-ref.tentative.html10
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-visible-overflow.tentative.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-nested.tentative.html94
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-displayed-ref.tentative.html44
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-displayed.tentative.html67
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-not-displayed-ref.tentative.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-not-displayed.tentative.html25
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-focusable.tentative.html49
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-label-rendering-ref.html4
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-label-rendering.tentative.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-overflow-x-ref.tentative.html13
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-overflow-x.tentative.html16
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-parts-structure.tentative.html495
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-popover-position-with-zoom.tentative.html135
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-popover-position.tentative.html115
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-popover.tentative.html102
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-pseudo-light-dismiss-invalidation.tentative.html47
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-pseudo-open-closed.tentative.html61
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-required-attribute.tentative.html61
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-rtl-ref.tentative.html10
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-rtl.tentative.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-behavior-ref.html9
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-behavior.tentative.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-part-ref.html9
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-part.tentative.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-slot-ref.html13
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-slot.tentative.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selectedoption-element-cloning-ref.html4
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selectedoption-element-cloning.tentative.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selectedoption-element.tentative.html28
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-tab-navigation.tentative.html33
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-tabindex-order.tentative.html33
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-text-only-ref.html9
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-text-only.tentative.html9
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-user-select.tentative.html63
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-validity.tentative.html88
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-value-option.tentative.html20
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-value-selectedOption.tentative.html224
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-writingmode-lr.tentative.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-writingmode-rl.tentative.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-writingmode-tb-ref.html4
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/support/back.html2
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/support/fake-selectlist.js112
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/support/selectlist_button_icon.svg3
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-selectlist-element/tab-closes-listbox.tentative.html43
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/change-to-empty-value.html32
-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-splittext-with-range-crash.html24
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-splittext-with-range-simple-crash.html14
-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/textarea-validity-valueMissing-inside-datalist.html19
-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
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/META.yml2
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-dd-element/grouping-dd.html27
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-div-element/grouping-div.html28
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-dl-element/grouping-dl.html30
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-dt-element/grouping-dt.html28
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-figcaption-element/grouping-figcaption.html28
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-figure-element/grouping-figure.html29
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-hr-element/grouping-hr.html30
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-novalue-manual.html148
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-001-ref.html46
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-001.html47
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-002-ref.html32
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-002.html34
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-003-ref.html14
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-003.html18
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-004-ref.html23
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-004.html26
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-005-ref.html22
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-005.html25
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-006.html29
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-007.html31
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-display-list-item-ref.html40
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-display-list-item.html44
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu-ref.html46
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu.html58
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-mixed-ref.html52
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-mixed.html64
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-not-dir-ref.html52
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-not-dir.html58
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ol-ref.html46
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ol.html52
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-parent-ref.html40
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-parent.html45
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-skip-no-boxes-ref.html42
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-skip-no-boxes.html52
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ul-ref.html46
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ul.html58
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-not-being-rendered-ref.html24
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-not-being-rendered.html30
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li.html193
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-rev-reftest-001-ref.html50
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-rev-reftest-001.html56
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-001-ref.html53
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-001.html60
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-002-ref.html53
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-002.html57
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-001-ref.html52
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-001.html55
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-002-ref.html59
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-002.html62
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-003-ref.html76
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-003.html79
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol.html299
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/ol.start-reflection-1.html25
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/ol.start-reflection-2.html25
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1-ref.html6
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1a.html10
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1b.html15
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1c.html16
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1d.html12
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1e.html11
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-2-ref.html6
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-2.html7
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-p-element/grouping-p.html28
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/grouping-pre-reftest-001-ref.html22
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/grouping-pre-reftest-001.html23
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/grouping-pre.html28
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/pre-newline-bidi-ref.html19
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/pre-newline-bidi.html23
-rw-r--r--testing/web-platform/tests/html/semantics/grouping-content/the-ul-element/grouping-ul.html28
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/META.yml2
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/commands/common/accesskey.js36
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/first-input-after-legend-manual.html10
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/first-input-before-legend-manual.html13
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/first-input-inside-legend-manual.html12
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/focusable-legend-manual.html14
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/focusable-legend-sibling-manual.html17
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/input-outside-fieldset-manual.html17
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/label-sibling-manual.html18
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/no-fieldset-parent-manual.html18
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/contextmenu-historical.html99
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-ax-slot-recalc-crash.html28
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-details-element-fragment.html29
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-window-find-crash.html18
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/closed-details-layout-apis.tentative.html28
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary-ref.html5
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary.html25
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-cq-crash.html16
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-findstring-crash.html15
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-keyboard-activation.html52
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details.html47
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html9
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/modified-details-crash.html31
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/name-attribute.html469
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-details-crash.html26
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-top-layer-elements-in-details-crash.html17
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/support/empty-html-document.html2
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/toggleEvent.html183
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/WEB_FEATURES.yml3
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/abspos-dialog-layout.html175
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-descendant-selector-ref.html18
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-descendant-selector.html55
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-display-none-ref.html7
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-display-none.html24
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-style-change-ref.html19
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-style-change.html35
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-in-flow-ref.html16
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-in-flow.html39
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-inherits-ref.html16
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-inherits.html25
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-receives-element-events.html50
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-stacking-order-ref.html65
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-stacking-order.html81
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/centering-iframe.sub.html31
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/centering.html69
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/child-sequential-focus.html60
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/closed-dialog-does-not-block-mouse-events.html51
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/default-color.html35
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-audio-video-crash.html10
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-autofocus-just-once.html24
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-autofocus-multiple-times.html42
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-autofocus.html65
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-events.html53
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-preventDefault.html49
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-with-input.html58
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-with-select.html37
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-canceling.html107
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close-event-async.html33
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close-event.html47
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close-via-attribute.html59
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close.html77
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-enabled.html16
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focus-previous-outside.html82
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focus-shadow-double-nested.html53
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focus-shadow.html274
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusability.html58
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusing-steps-disconnected.html40
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusing-steps-inert.html48
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusing-steps-prevent-autofocus.html21
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-form-submission-unusual.html34
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-form-submission.html131
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-inert.html45
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-keydown-preventDefault.html45
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-no-throw-requested-state.html29
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-not-in-tree-crash.html5
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-2.html27
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-open.html43
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-overlay-re-add-during-transition.html32
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-overlay.html36
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-return-value.html54
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-showModal-inert-crash.html17
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-showModal-remove.html24
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-showModal.html188
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialogs-with-no-backdrop-ref.html5
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialogs-with-no-backdrop.html41
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dont-share-style-to-top-layer-ref.html16
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dont-share-style-to-top-layer.html18
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/element-removed-from-top-layer-has-original-position-ref.html18
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/element-removed-from-top-layer-has-original-position.html35
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-contain-ancestor.html27
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-fixed-position-cb-ref.html16
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-fo-ancestor.html29
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-transformed-ancestor.tentative.html27
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-will-change-ancestor.tentative.html27
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/focus-after-close.html229
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/focus-previous-iframe.tentative.html52
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/green-dialog-and-backdrop.html26
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-does-not-match-disabled-selector.html35
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-focus-in-frames.html73
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-inlines.html85
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-label-focus.html53
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-not-highlighted-ref.html36
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-not-highlighted.html33
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-uneditable.html55
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-unfocusable.html75
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-unselectable.html19
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest.html68
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inertness-with-modal-dialogs-and-iframes.html131
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-ancestor-is-inert.html101
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop-opacity-ref.html22
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop-opacity.html19
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop-ref.html42
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop.html21
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-blocks-mouse-events.html101
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-display-contents-ref.html18
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-display-contents.html28
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-generated-content-ref.html42
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-generated-content.html59
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-iframe-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-iframe.html18
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-object-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-object.html17
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer-ref.html18
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer.html26
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column-ref.html14
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column.html26
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-visibility-hidden.html39
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-scroll-height.html32
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-selection.html68
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-sibling-ref.html20
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-sibling.html25
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/multiple-centered-dialogs.html68
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/non-modal-dialog-does-not-block-mouse-events.html52
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/non-modal-dialog-layout.html102
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/pass-dialog-ref.html9
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/remove-dialog-should-unblock-document.html34
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/removed-element-is-removed-from-top-layer-ref.html30
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/removed-element-is-removed-from-top-layer.html44
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/common.js18
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/dialog.css14
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/inert-focus-in-frames-frame1.html24
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/inert-focus-in-frames-frame2.html1
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/submit.jpgbin0 -> 7782 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/show-modal-focusing-steps.html63
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/showmodal-in-shadow-crash.html16
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/showmodal-shadow-sibling-frame-crash.html32
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/simulated-click-inert.html33
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/submit-dialog-close-event.html34
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/synthetic-click-inert.html40
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-containing-block-ref.html22
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-containing-block.html39
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-display-none-ref.html19
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-display-none.html60
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-nesting-ref.html26
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-nesting.html66
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-clip.html29
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-filter.html30
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-mask.html30
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-opacity.html30
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-overflow-clip.html35
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-overflow-hidden.html34
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-overflow-scroll.html35
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-transform.html29
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position-ref.html22
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position-relative.html28
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position-static.html28
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position.html31
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-remove-popover-attribute-ref.html15
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-remove-popover-attribute.html18
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-correct-order-remove-readd-ref.html19
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-correct-order-remove-readd.html46
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-dynamic-ref.html19
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-dynamic.html55
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-ref.html40
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking.tentative.html56
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/activation-behavior.html134
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/anchor-with-inline-element.html77
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/anchor-without-link.html40
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/click-behavior-optional.tentative.html39
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/display-table-with-rt-crash.html9
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/summary-untrusted-key-event.html104
-rw-r--r--testing/web-platform/tests/html/semantics/interfaces.html74
-rw-r--r--testing/web-platform/tests/html/semantics/interfaces.js150
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/idlharness.tentative.html16
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invokeelement-interface.tentative.html93
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invokeevent-dispatch-shadow.tentative.html104
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invokeevent-interface.tentative.html166
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html119
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-fullscreen-behavior.tentative.html175
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-generic-eventtarget-crash.tentative.html18
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html285
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-on-details-behavior.tentative.html218
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-on-popover-behavior.tentative.html209
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html253
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/resources/invoker-utils.js12
-rw-r--r--testing/web-platform/tests/html/semantics/links/META.yml2
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-no-referrer-when-downgrade.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-no-referrer.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-origin-when-cross-origin.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-origin.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-same-origin.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-strict-origin-when-cross-origin.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-strict-origin.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-unsafe-url.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin.html19
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin.js40
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-no-referrer-when-downgrade.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-no-referrer.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-origin-when-cross-origin.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-origin.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-same-origin.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-strict-origin-when-cross-origin.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-strict-origin.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-unsafe-url.html20
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer.html19
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer.js40
-rw-r--r--testing/web-platform/tests/html/semantics/links/downloading-resources/resources/inspect-header.py18
-rw-r--r--testing/web-platform/tests/html/semantics/links/following-hyperlinks/activation-behavior.window.js50
-rw-r--r--testing/web-platform/tests/html/semantics/links/following-hyperlinks/active-document.window.js23
-rw-r--r--testing/web-platform/tests/html/semantics/links/hyperlink-auditing/headers.optional.html55
-rw-r--r--testing/web-platform/tests/html/semantics/links/hyperlink-auditing/resources/stash-headers.py27
-rw-r--r--testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_attribute-getter-setter.html65
-rw-r--r--testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_getter.html48
-rw-r--r--testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_noopener.html78
-rw-r--r--testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/non-parsable-url-getter-setter.window.js54
-rw-r--r--testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/non-special-opaque-path-url-getter-setter.window.js59
-rw-r--r--testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/non-special-url-getter-setter.window.js63
-rw-r--r--testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/noopener-popup.html19
-rw-r--r--testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/noopener-target-1.html4
-rw-r--r--testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/noopener-target-2.html8
-rw-r--r--testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/target_blank_implicit_noopener.html6
-rw-r--r--testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/target_blank_implicit_noopener.html58
-rw-r--r--testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/target_blank_implicit_noopener_base.html59
-rw-r--r--testing/web-platform/tests/html/semantics/links/linktypes/alternate-css-ref.html5
-rw-r--r--testing/web-platform/tests/html/semantics/links/linktypes/alternate-css.html7
-rw-r--r--testing/web-platform/tests/html/semantics/links/linktypes/alternate-import.css3
-rw-r--r--testing/web-platform/tests/html/semantics/links/linktypes/alternate.css5
-rw-r--r--testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-lower.css1
-rw-r--r--testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-mixed.css1
-rw-r--r--testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-other.css1
-rw-r--r--testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-ref.html9
-rw-r--r--testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive.css3
-rw-r--r--testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive.html14
-rw-r--r--testing/web-platform/tests/html/semantics/links/linktypes/original-id.json1
-rw-r--r--testing/web-platform/tests/html/semantics/links/linktypes/preferred.css3
-rw-r--r--testing/web-platform/tests/html/semantics/permission-element/no-end-tag-no-contents.html27
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/WEB_FEATURES.yml3
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/hide-other-popover-side-effects.html21
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/invoker-show-crash.html12
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/label-in-invoker.html23
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/light-dismiss-event-ordering.html82
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-anchor-change-display-ref.html24
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-anchor-change-display.tentative.html51
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-anchor-display-none.tentative.html34
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-anchor-display-ref.html29
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-anchor-display.tentative.html109
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-anchor-idl-property.tentative.html52
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-anchor-multicol-display.tentative.html62
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-anchor-nested-display-ref.html57
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-anchor-nested-display.tentative.html56
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-anchor-nesting.tentative.html56
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-anchor-scroll-display-ref.html32
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-anchor-scroll-display.tentative.html86
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-anchor-transition.tentative.tentative.html34
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-and-svg-ref.html11
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-and-svg.html18
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-appearance-ref.html18
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-appearance.html26
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-attribute-all-elements.html47
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-attribute-basic.html359
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-backdrop-appearance-ref.html45
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-backdrop-appearance.html47
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-beforetoggle-opening-event.html33
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-change-type.html42
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-close-request.html40
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-css-properties.tentative.html54
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-dialog-appearance-ref.html33
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-dialog-appearance.html27
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-dialog-crash.html26
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-document-open.html30
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-events.html216
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-focus-2.html174
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-focus-harness.html25
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-focus.html292
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-hidden-display-ref.html19
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-hidden-display.html39
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-hint-crash.tentative.html29
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-hide.tentative.html21
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-hover.tentative.html21
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-show.tentative.html21
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-toggle.tentative.html21
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-inside-display-none-ref.html5
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-inside-display-none.html19
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-invoker-reset.html34
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-invoking-attribute-hint.tentative.html19
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-invoking-attribute.html74
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-flat-tree-nested.html55
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-flat-tree.html46
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-hint.tentative.html107
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-on-scroll.html66
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-with-anchor.tentative.tentative.html90
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss.html607
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-manual-crash.html32
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-move-documents.html89
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-not-keyboard-focusable.html49
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-open-display-ref.html20
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-open-display.html27
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-open-in-beforetoggle.html66
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-open-overflow-display-2.html69
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-open-overflow-display-ref.html22
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-open-overflow-display.tentative.html37
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-overlay.html51
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-removal-2.html29
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-removal.html28
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-shadow-dom.html202
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-shadowhost-focus.html56
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-stacking-anchor-attribute.tentative.html104
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-stacking-context-ref.html29
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-stacking-context.html35
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-stacking.html131
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-target-action-hover.tentative.html180
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-target-element-disabled.html159
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-top-layer-combinations.html155
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-top-layer-interactions.html89
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-top-layer-nesting-anchor.tentative.html45
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-top-layer-nesting-hints.tentative.html46
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-top-layer-nesting.tentative.html45
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-types-with-hints.tentative.html179
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-types.html37
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-undefined-remove-crash.html12
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popovertarget-reflection.html53
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/resources/popover-hover-hide-common.js139
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/resources/popover-invoking-attribute.js122
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/resources/popover-styles.css17
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/resources/popover-top-layer-nesting.js108
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/resources/popover-utils.js176
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/togglePopover.html79
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/toggleevent-interface.html208
-rw-r--r--testing/web-platform/tests/html/semantics/rellist-feature-detection.html84
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/META.yml2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-noscript-element/non-html-noscript.html16
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/README.md16
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_001.htm18
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_002.htm31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_003.htm40
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_004.htm38
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_005.htm43
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_006.htm46
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_007.htm49
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_008.htm47
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_009.htm25
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_010.htm55
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_011.htm19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/charset-2.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/charset-bom.html20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/charset.html37
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/content-type-checking.html39
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/cors-crossorigin-requests.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/credentials.sub.html55
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/css-module-worker-test.html54
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/import-css-module-basic.html83
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/import-css-module-dynamic.html23
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/integrity.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/load-error-events.html67
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/referrer-policies.sub.html84
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/relative-urls.html18
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/atImported.css3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bad-import.css4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/basic-large.css7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/basic.css3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bom-utf-16be.cssbin0 -> 64 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bom-utf-16le.cssbin0 -> 64 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bom-utf-8.css1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/credentials-iframe.sub.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/cross-origin.py17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/crossorigin-import-parse-error-with-cors.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/crossorigin-import-with-cors.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/crossorigin-import-without-cors.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/css-module-without-assertion-iframe.html22
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/integrity-matches.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/integrity-mismatches.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/load-error-events.py14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/load-relative-url.css5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/malformed.css7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/parse-error.css2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/record-fetch.py20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/referrer-checker.py7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/utf-8.css3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/windows-1250.css3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/worker-dynamic-import.sub.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/worker.sub.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/script-element-css-src.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/charset-2.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/charset-bom.html20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/charset.html37
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/content-type-checking.html39
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/cors-crossorigin-requests.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/credentials.sub.html55
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/css-module-worker-test.html54
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-basic.html83
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-dynamic.html23
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/integrity.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/load-error-events.html67
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/referrer-policies.sub.html84
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/relative-urls.html18
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/atImported.css3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bad-import.css4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/basic-large.css7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/basic.css3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bom-utf-16be.cssbin0 -> 64 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bom-utf-16le.cssbin0 -> 64 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bom-utf-8.css1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/credentials-iframe.sub.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/cross-origin.py17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/crossorigin-import-parse-error-with-cors.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/crossorigin-import-with-cors.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/crossorigin-import-without-cors.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-without-attribute-iframe.html22
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/integrity-matches.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/integrity-mismatches.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/load-error-events.py14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/load-relative-url.css5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/malformed.css7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/parse-error.css2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/record-fetch.py20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/referrer-checker.py7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/utf-8.css3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/windows-1250.css3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker-dynamic-import.sub.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker.sub.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/script-element-css-src.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/data-url.html32
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/README.md7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/async-script-2.html40
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/async-script.html22
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/defer-script-xml.xhtml40
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/defer-script.html37
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/document-write.html67
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/async-script-1.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/defer-script-1.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/defer-script-2.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write-close.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-close.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-iframe.sub.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/helper.js17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/sync-script-1.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/sync-script-2.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/support/async-script.html44
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer.js4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/emptyish-script-elements.html75
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/001.html27
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/002.html27
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/003.html27
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/004.html25
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/005.html27
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/006.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/007.html27
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/008.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/009.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/010.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/011.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/012.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/013.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/014.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/015.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/015a.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/016.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/017.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/018.html35
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/019.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/020.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/021.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/022.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/023.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/024.html32
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/025.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/026.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/027.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/028.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/030.html39
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/031.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/032.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/033.html35
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/034.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/035.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/036.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/037.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/038.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/039.html39
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/040.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/041.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/042.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/043.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/044.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/045.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/046.html27
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/047.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/048.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/049.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/050.html50
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/051.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/052.html26
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/053.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/054.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/055.html32
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/056.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/057.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/058.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/059.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/060.html32
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/061.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/062.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/063.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/064.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/065.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/066.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/067.html38
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/068.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/069.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/070.html48
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/071.html55
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/072.html50
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/073.html52
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/074.html49
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/075.html42
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/076.html32
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/077.html41
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/078.html44
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/079.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/081.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/082.html35
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/083.html48
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/084.html47
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/085.html24
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/086.html24
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/087.html24
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/088.html24
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/089.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/090.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/091.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/092.html26
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/094.html23
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/095.html24
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/096.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/097.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/099.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/101.html35
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/102.html24
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/103.html39
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/104.html39
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/105.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-defer-import.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-defer-noimport.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-import.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-module-import.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-module-noimport.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-noimport.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-import-xhtml.xhtml20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-import.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-module-import.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-module-noimport.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-noimport-xhtml.xhtml20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-noimport.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/107-import.html21
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/107-noimport.html21
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/108.html25
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/109.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/110.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/111.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/112.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/113.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/114.html26
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/115.html26
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/116.html24
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/117.html25
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/118.html25
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/119.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/120.html17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/121.html17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/122.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/123.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/124.html25
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/125.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/126.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/127.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/128.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/129.html40
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/130.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/131.html22
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/132.html22
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/133.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/134.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/135.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/136.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/137.html21
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/138.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/139.html30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/140.html24
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/141.html26
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/142.html27
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/143.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/144.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/145.html24
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/146-href.html22
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/146.html23
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/147.html40
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/148.html40
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/149.html59
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-import-xhtml.xhtml21
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-import.html20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-noimport-xhtml.xhtml20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-noimport.html20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/css/background.css1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/css/import.css1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/non-external-no-import.html27
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/pages/helloworld-postMessage.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/pages/helloworld.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/check-style-sheet.js4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/count-script-tags.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/find-body.js4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/find-foo.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-1.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-10.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-11.js4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-2.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-3.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-4.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-5.js7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-6.js6
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-7.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-8.js4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-9.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/testlib/testlib.js43
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/external-script-utf8.js5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/external-script-windows1250.js5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/alpha/base.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/alpha/test.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/beta/test.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/empty-with-base.html27
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/empty.html32
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/failure.html25
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/unreachable.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/historical.html53
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/dynamic-import-with-assertion-argument.any.js18
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/empty-assertion-clause.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/empty-assertion-clause.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/empty-type-assertion.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/export-hello.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/hello.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/invalid-import-errors-order.html36
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/invalid-type-assertion-error.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/invalid-type-assertion.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/javascript-type-assertion.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/js-type-assertion.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/unsupported-assertion.html22
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/unsupported-assertion.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/dynamic-import-with-attributes-argument.any.js18
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/empty-attributes-clause.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/empty-attributes-clause.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/empty-type-attribute.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/export-hello.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/hello.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/invalid-import-errors-order.html36
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/invalid-type-attribute-error.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/invalid-type-attribute.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/javascript-type-attribute.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/js-type-attribute.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/unsupported-attribute.html22
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/unsupported-attribute.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/is-module-goal.mjs1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/is-script-goal.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/array.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/bom-utf-16be.jsonbin0 -> 40 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/bom-utf-16le.jsonbin0 -> 40 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/bom-utf-8.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/charset-2.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/charset-bom.any.js17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/charset.html37
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/cors-crossorigin-requests.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/credentials-iframe.sub.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/credentials.sub.html55
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/cross-origin.py16
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/crossorigin-import-parse-error-with-cors.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/crossorigin-import-with-cors.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/crossorigin-import-without-cors.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/data.json3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/false.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/integrity-matches.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/integrity-mismatches.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/integrity.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/invalid-content-type.any.js17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/json-module-service-worker-test.https.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/load-error-events.html67
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/load-error-events.py14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/module.html18
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/module.json3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/non-object.any.js14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/null.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/parse-error.html21
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/parse-error.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/referrer-checker.py6
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/referrer-policies.sub.html84
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/repeated-imports.any.js65
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/script-element-json-src.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/serviceworker-dynamic-import.js5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/serviceworker.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/string.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/true.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/utf-8.json4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/valid-content-type.html46
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/windows-1250.json4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/array.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/bom-utf-16be.jsonbin0 -> 40 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/bom-utf-16le.jsonbin0 -> 40 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/bom-utf-8.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/charset-2.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/charset-bom.any.js17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/charset.html37
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/cors-crossorigin-requests.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/credentials-iframe.sub.html33
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/credentials.sub.html55
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/cross-origin.py16
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/crossorigin-import-parse-error-with-cors.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/crossorigin-import-with-cors.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/crossorigin-import-without-cors.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/data.json3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/false.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/integrity-matches.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/integrity-mismatches.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/integrity.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/invalid-content-type.any.js17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/json-module-service-worker-test.https.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/load-error-events.html67
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/load-error-events.py14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/module.html18
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/module.json3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/non-object.any.js14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/null.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/parse-error.html21
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/parse-error.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/referrer-checker.py6
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/referrer-policies.sub.html84
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/repeated-imports.any.js65
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/script-element-json-src.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker-dynamic-import.js5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/string.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/true.json1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/utf-8.json4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.html46
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/windows-1250.json4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/load-error-events-1.html68
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/load-error-events-2.html22
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/load-error-events-3.html22
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/log.py13
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-window-onerror-module.html9
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-window-onerror.html9
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-workerglobalscope-onerror-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-workerglobalscope-onerror.html13
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-importScripts.any.js40
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-nothrow-importScripts.any.js11
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-nothrow-static-import.any.js5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-throw-importScripts.any.js22
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-throw-static-import.any.js5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-2.any.js5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-2.html8
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-3.any.js5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-3.html8
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-4.html18
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/checkpoint-after-error-event-worker-module.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/checkpoint-after-error-event-worker.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/checkpoint-after-error-event.js89
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-nothrow-setup.js5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-nothrow.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-throw-setup.js10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-throw.js4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-2-setup.js10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-2.1.mjs8
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-2.2.mjs7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-3-setup.js7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-3.1.mjs11
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-3.2.mjs5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-4.1.mjs8
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-4.2.mjs5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-setup.js30
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/resolve-then-throw.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/bad-module-specifier.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/charset-01.html52
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/charset-02.html26
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/charset-03.html37
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-1.html37
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-1a.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-1b.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-2.html37
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-2a.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-2b.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-3.html38
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-3a.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-3b.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/compilation-error-1.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/compilation-error-2.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/credentials.sub.html68
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-common.js8
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-different.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-missingheader.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-same.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-wrongheader.sub.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-different.sub.html11
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-missingheader.sub.html11
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-same.sub.html11
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-wrongheader.sub.html11
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-scripterror.js8
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin.html43
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/currentScript-null.html13
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/currentscript.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/custom-element-exception.html31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access-a.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable-a.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/duplicated-imports-1.html23
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/duplicated-imports-2.html23
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url-worker-importScripts.html7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url-worker.sub.html8
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url.sub.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/code-cache.js9
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/import.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/worker-importScripts.sub.js15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/beta/code-cache.js9
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/beta/import.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/beta/redirect.py19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url-workers.window.js60
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url.any.js66
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/code-cache-base-url.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/code-cache-nonce.html43
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/delay-load-event.html25
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials-setTimeout.sub.html11
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub.html11
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-fetch-error.sub.html61
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-script-error.html53
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js58
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/code-cache.js9
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/import.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/inline-event-handler.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/basic.any.js32
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/css-import-in-worker.any.js14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/empty-module.css4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/empty-module.js4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/serviceworker.any.js14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/ticker.js13
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/with-import-assertions.any.js15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/worklet-ref.https.html10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/worklet.https.html43
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/no-active-script-classic-manual.html55
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/no-active-script-module-manual.html55
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external-classic.html5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external-module.html5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external.js7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-inline-classic.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-inline-module.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/blob-url-worker.js11
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/code-cache-nonce-iframe.sub.html4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/code-cache-nonce.js4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/empty-iframe.html9
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/status-changing-script.py18
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/v8-code-cache-iframe.sub.html9
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/v8-code-cache.js74
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/Function.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/eval.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/inline-event-handlers-UA-code.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/no-active-script.js5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/reflected-inline-event-handlers.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/setTimeout.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-classic.html63
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-module.html64
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-classic.html73
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-module.html73
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-classic.html54
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-module.html54
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-classic.html104
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-module.html103
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-of-promise-result.html83
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-other-document.html79
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/v8-code-cache.html42
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-and-slow-dependency.html20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-1.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-1.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-2.html35
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-2.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-3.html35
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-3.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-common.js10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependent.html16
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependent.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependentmultiple.html24
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependentmultiple.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-root.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-wrongMimetype-import.js8
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-wrongMimetype.js7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling.html60
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-1.html35
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-2.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-3.html37
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-4.html37
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicordered2.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicordered3.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicordered4.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicunordered1.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicunordered2.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedordered2.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedordered4.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedunordered1.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedunordered2.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder.html106
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/export-default.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/export-something-nested.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/export-something.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/fetch-error-1.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/fetch-error-2.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/fetch-error-2.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-dependent.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-object.any.js20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-importmap.html57
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-multiple-scripts.html39
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve.any.js77
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-root.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.any.js38
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.html34
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/postmessage-worker.js12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-1.mjs1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-2.mjs1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-import-meta.mjs2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/store-import-meta.html5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-something-namespace.js5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-subgraph-404.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-a.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-b.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-cycle-a.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-cycle-b.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-cycle.js6
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-inc-a.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-inc-ab.js5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-inc-b.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-self-inner.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-self.js6
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports.html64
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inactive-context-import.html20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-async-execorder.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-async-onload.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-defer-onload.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-no-async-onload.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html35
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-2.html35
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html37
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4.html32
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4a.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4b.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4c.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4d.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5.html32
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5a.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5b.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5c.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5d.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5e.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6.html36
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6a.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6b.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6c.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6d.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7.html37
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7a.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7b.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7c.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7d.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7e.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7f.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-8.html27
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-matches-inner.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-matches.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-mismatches-inner.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-mismatches.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity.html40
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/late-namespace-request.html20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/late-star-export-request.html25
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/load-error-events-inline.html61
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/load-error-events.html61
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/missing-export-nested.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/missing-export.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-import-referrer.html48
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-import-referrer.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-in-xhtml.xhtml20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-vs-script-1.html17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-vs-script-2.html17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-a.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-b.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-c.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-d.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-e.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-f.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-g.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-h.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports.html29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-missing-export.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nomodule-attribute.html18
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-no-referrer.sub.html68
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-origin-when-cross-origin.sub.html82
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-origin.sub.html71
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-policy-for-descendants.sub.html122
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-same-origin.sub.html77
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-strict-policies.sub.html38
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-unsafe-url.sub.html81
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/404-but-js.asis4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/500-but-js.asis4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/credentials-iframe.sub.html50
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/delayed-modulescript.py7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-helper.sub.js67
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-iframe.sub.html51
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-setTimeout-iframe.sub.html56
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/fast-module.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-non-utf8-with-charset-header.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-non-utf8.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker-insecure.sub.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker-insecure.sub.js.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-remote-origin-referrer-checker.sub.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-utf8-with-charset-header.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-utf8.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/imports-404-but-js.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/imports-500-but-js.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/imports-b-cross-origin.sub.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/referrer-checker.py6
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/slow-module.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/script-for-event.html93
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/set-currentScript-on-window.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/single-evaluation-1.html20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/single-evaluation-2.html20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/slow-cycle.html11
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/slow-module-graph-a.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/slow-module-graph-b.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/specifier-error.html22
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/syntaxerror-nested.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/syntaxerror.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/this-nested.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/this.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw-error.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw-nested.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw2.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked.any.js11
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__microtask__check-get-sync.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__microtask__check-set-async.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__microtask__parent.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__task__check-get-sync.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__task__check-set-async.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__task__parent.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/type.html24
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents-during-evaluation.html59
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/README.md16
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-fetch-error-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-fetch-error-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-parse-error-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-parse-error-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-success-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-success-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-fetch-error-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-fetch-error-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-parse-error-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-parse-error-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-parse-error-inline-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-success-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-success-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-success-inline-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-fetch-error-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-fetch-error-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-inline-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-inline-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-empty-src-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-empty-src-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-inline-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-inline-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-fetch-error-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-fetch-error-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-inline-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-inline-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-empty-src-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-empty-src-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-inline-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-inline-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-fetch-error-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-fetch-error-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-parse-error-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-parse-error-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-success-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-success-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-fetch-error-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-fetch-error-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-parse-error-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-parse-error-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-parse-error-inline-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-external-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-external-module.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-inline-classic.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/README.md6
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-1.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-2.html15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-iframe.html4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/helper.js31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/in-order.html35
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/parser-blocking.html41
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-helper.js214
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-iframe.py102
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/slow-flag-setter.py29
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/tools/generate.py61
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/muted-errors-iframe.html2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/muted-errors.sub.html85
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-reflect.html75
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-async-classic-script.html63
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-external-module-script.html28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-classic-scripts.html56
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-module-script.html32
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-synchronously-loaded-classic-scripts.html41
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/promise-reject-and-remove.html10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/bom-utf-16be.jsbin0 -> 156 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/bom-utf-16le.jsbin0 -> 156 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/bom-utf-8.js2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/cocoa-module.js5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/cross-origin.py20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/exports-cocoa.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/flag-setter.js3
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/load-error-events-helpers.js47
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/load-error-events.py15
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/promise-reject-and-remove-iframe.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/script-type-and-language-js.js141
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/set-script-executed.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/syntax-error.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/throw.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-charset-01.html89
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-charset-02.html41
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-charset-03.html20
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-crossorigin-network.sub.html120
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-crossorigin.html39
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-defer-xhtml.xhtml31
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-defer.html26
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-for-event-xhtml.xhtml22
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-for-event.html93
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-noembed-noframes-iframe.xhtml36
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-executed-after-shutdown-child.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-executed-after-shutdown.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-executed-after-shutdown.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-found-not-executed-2.py4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-found-not-executed.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-found-not-executed.py4
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onerror-insertion-point-1.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onerror-insertion-point-2.html13
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onload-insertion-point.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onload-string.html17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-referrerpolicy-idl.html26
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-supports.html52
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text-modifications-csp.html52
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text-modifications.html40
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text-xhtml.xhtml28
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text.html72
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-empty.html48
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-js-svg.svg37
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-js-xhtml.xhtml42
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-js.html35
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-with-params.html41
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-whitespace.html27
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/scripting-enabled.html16
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/serve-json-then-js.py21
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/serve-with-content-type.py17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onerror-insertion-point-1-helper.html2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onerror-insertion-point-2-helper.html2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onload-insertion-point-helper.html2
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onload-insertion-point-helper.js1
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/node-document.html150
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/tag-name.xhtml16
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/template-child-nodes.html102
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-serializing-xhtml-documents/outerhtml.html71
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-001-ref.html6
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-001.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-002.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-003.html19
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-steps-to-clone-a-node/template-clone-children.html82
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-steps-to-clone-a-node/templates-copy-document-owner.html126
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-document-type.html83
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-test-001.html44
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-test-002.html67
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents.html172
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/innerhtml-on-templates/innerhtml.html105
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/end-template-tag-in-body.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/end-template-tag-in-head.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/frameset-end-tag.html10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/head-template-contents-div-no-end-tag.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/head-template-contents-table-no-end-tag.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/html-start-tag.html10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-child-nodes-div.xhtml14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-child-nodes-nested.xhtml16
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-attribute.html10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-body.html10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-div-no-end-tag.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-empty.html11
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-frameset.html10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-head.html10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-html.html10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-nested.html10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-table-no-end-tag.html14
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-text.html10
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-descendant-body.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-descendant-frameset.html12
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-descendant-head.html13
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/two-templates.html17
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/serializing-html-templates/outerhtml.html70
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/content-attribute.html114
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/node-document-changes.html192
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-as-a-descendant.html135
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-construction-in-inactive-document-crash.html5
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-hierarcy.html81
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-in-inactive-document-crash.html7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-move-to-inactive-document-crash.html7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-node-document.html59
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content.html63
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-descendant-body.html26
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-descendant-frameset.html62
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-descendant-head.html26
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-element-clone-into-inactive-document-crash.html7
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-set-inner-html-in-inactive-document-crash.html6
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-table-crash.html13
-rw-r--r--testing/web-platform/tests/html/semantics/sections/the-h1-h2-h3-h4-h5-and-h6-elements/original-id.json1
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/META.yml2
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/case-sensitivity/values.window.js91
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/active-disabled.html54
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/autofill.html11
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked-001-manual.html18
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked-indeterminate.window.js27
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked-type-change.html24
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked.html44
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/default.html64
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir-dynamic.html47
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir-html-input-dynamic-text.html21
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir.html99
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir01.html18
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/disabled.html80
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/enabled.html43
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/focus-autofocus.html24
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/focus-iframe.html5
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/focus.html51
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-radio-group-ref.html21
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-radio-group.html28
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-radio.html26
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-type-change.html24
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate.html37
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.js75
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/inrange-outofrange-type-change.html43
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/inrange-outofrange.html84
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/invalid-after-clone.html28
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/link.html21
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/placeholder-shown-type-change.html27
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/readwrite-readonly-type-change.html36
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/readwrite-readonly.html118
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/required-optional-hidden.html36
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/required-optional.html35
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/utils.js20
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/valid-invalid-fieldset-disconnected.html57
-rw-r--r--testing/web-platform/tests/html/semantics/selectors/pseudo-classes/valid-invalid.html146
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/META.yml2
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/attributes-common-to-td-and-th-elements/cellIndex.html50
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/historical.html25
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/html-table-section-element.js22
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/processing-model-1/col-span-limits.html59
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/processing-model-1/span-limits.html66
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-caption-element/caption_001.html70
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-table-element/caption-methods.html276
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-table-element/createTBody.html173
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-table-element/delete-caption.html94
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-table-element/insertRow-method-01.html24
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-table-element/insertRow-method-02.html34
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-table-element/insertRow-method-03.html32
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-table-element/remove-row.html64
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-table-element/tBodies.html40
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-table-element/tFoot.html65
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-table-element/tHead.html74
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-table-element/table-insertRow.html56
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-table-element/table-rows.html234
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-tbody-element/deleteRow.html61
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-tbody-element/insertRow.html56
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-tbody-element/rows.html15
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-tfoot-element/rows.html15
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-thead-element/rows.html15
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/cells.html28
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/deleteCell.html61
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/insertCell.html64
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/rowIndex.html77
-rw-r--r--testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/sectionRowIndex.html130
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/historical.html29
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-click-handler-with-null-browsing-context-crash.html21
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-404.py2
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-click-404.html25
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-click-redirect-to-javascript.html29
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-click.html33
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-stringifier.html16
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a.text-getter-01.html34
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a.text-setter-01.html41
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-download-404.html2
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-download-click.html2
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-download-redirect-to-javascript.html5
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-onclick-handler-iframe.html1
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-b-element/b-usage-notref.html6
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-b-element/b-usage.html8
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-auto-dir-default-ref.html36
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-auto-dir-default.html46
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-missing-pdf-ref.html44
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-missing-pdf.html56
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-nested-ref.html44
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-nested.html52
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-number-ref.html44
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-number.html53
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-separate-ref.html36
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-separate.html47
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-1-ref.html47
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-1.html58
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-2-ref.html47
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-2.html59
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-1-ref.html45
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-1.html54
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-2-ref.html45
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-2.html54
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-1-ref.html45
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-1.html54
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-2-ref.html45
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-2.html54
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-1-ref.html45
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-1.html54
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-2-ref.html45
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-2.html53
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-surrounding-run-ref.html44
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-surrounding-run.html56
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-wrapped-ref.html52
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-wrapped.html73
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-paragraph-level-container-ref.html36
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-paragraph-level-container.html46
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bdo-child.html17
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bdo-ltr.html15
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bdo-override.html18
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bidi-001-ref.html11
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bidi-001.html14
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi-in-inline-ancestors-ref.html38
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi-in-inline-ancestors.html59
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi-ref.html19
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi.html22
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-ruby-element/rt-without-ruby-crash.html11
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-ruby-element/ruby-usage-notref.html6
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-ruby-element/ruby-usage.html8
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-time-element/001.html68
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-wbr-element/wbr-element-ref.html10
-rw-r--r--testing/web-platform/tests/html/semantics/text-level-semantics/the-wbr-element/wbr-element.html12
-rw-r--r--testing/web-platform/tests/html/semantics/the-link-element/attr-link-fetchpriority.html26
-rw-r--r--testing/web-platform/tests/html/semantics/the-link-element/resources/stylesheet.css3
3091 files changed, 109515 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/disabled-checkbox-click.html b/testing/web-platform/tests/html/semantics/disabled-elements/disabled-checkbox-click.html
new file mode 100644
index 0000000000..805770c854
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/disabled-elements/disabled-checkbox-click.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<link rel="author" title="Aditya Keerthi" href="https://github.com/pxlcoder">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#enabling-and-disabling-form-controls:-the-disabled-attribute">
+<title>Test disabled checkbox does not change state when clicked</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>
+
+<input type="checkbox" disabled>
+<script>
+const input = document.querySelector("input");
+
+promise_test(async function() {
+ assert_false(input.checked);
+
+ await new test_driver.Actions()
+ .pointerMove(0, 0, { origin: input })
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ assert_false(input.checked);
+}, `Disabled checkbox does not change state when clicked`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch-additional.tentative.html b/testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch-additional.tentative.html
new file mode 100644
index 0000000000..8ef4e5ccc7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch-additional.tentative.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<meta name="timeout" content="long">
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/2368">
+<link rel=help href="https://github.com/whatwg/html/issues/5886">
+<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/testdriver-actions.js"></script>
+
+<!-- This test should be merged with disabled-event-dispatch.tentative.html after interop2023 is over. -->
+
+<div id=targetparent>
+ <button disabled>
+ hello world
+ <span style="border: 1px solid black">child</span>
+ </button>
+ <my-control disabled>
+ hello world
+ <span style="border: 1px solid black">child</span>
+ </my-control>
+</div>
+
+<script>
+customElements.define('my-control', class extends HTMLElement {
+ static get formAssociated() { return true; }
+});
+
+['dblclick', 'auxclick'].forEach(eventName => {
+ [true, false].forEach(clickChildElement => {
+ for (const target of targetparent.children) {
+ promise_test(async () => {
+ let parentReceivedEvent = false;
+ targetparent.addEventListener(eventName, () => parentReceivedEvent = true);
+
+ let targetReceivedEvent = false;
+ target.addEventListener(eventName, () => targetReceivedEvent = true);
+
+ let childReceivedEvent = false;
+ let targetchild = target.firstElementChild;
+ targetchild.addEventListener(eventName, () => childReceivedEvent = true);
+
+ const elementToClick = clickChildElement ? targetchild : target;
+ if (eventName === 'dblclick') {
+ await (new test_driver.Actions()
+ .pointerMove(1, 1, {origin: elementToClick})
+ .pointerDown()
+ .pointerUp()
+ .pointerDown()
+ .pointerUp())
+ .send();
+ } else if (eventName === 'auxclick') {
+ const actions = new test_driver.Actions();
+ await actions
+ .pointerMove(1, 1, {origin: elementToClick})
+ .pointerDown({button: actions.ButtonType.MIDDLE})
+ .pointerUp({button: actions.ButtonType.MIDDLE})
+ .send();
+ }
+
+
+ const shouldReceiveEvents = eventName.startsWith('pointer') || eventName === 'auxclick';
+ assert_equals(parentReceivedEvent, shouldReceiveEvents,
+ `parent element received ${eventName} events`);
+ assert_equals(targetReceivedEvent, shouldReceiveEvents,
+ `target element received ${eventName} events`);
+ assert_equals(childReceivedEvent, clickChildElement,
+ `child element received ${eventName} events`);
+ }, `Testing ${eventName} events when clicking ${clickChildElement ? 'child of ' : ''}disabled ${target.localName}.`);
+ }
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch.tentative.html b/testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch.tentative.html
new file mode 100644
index 0000000000..e2b8846fc3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch.tentative.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta name="timeout" content="long">
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/2368">
+<link rel=help href="https://github.com/whatwg/html/issues/5886">
+<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/testdriver-actions.js"></script>
+
+<div id=targetparent>
+ <button disabled>
+ hello world
+ <span style="border: 1px solid black">child</span>
+ </button>
+ <my-control disabled>
+ hello world
+ <span style="border: 1px solid black">child</span>
+ </my-control>
+</div>
+
+<script>
+customElements.define('my-control', class extends HTMLElement {
+ static get formAssociated() { return true; }
+});
+
+['mousedown', 'mouseup', 'pointerdown', 'pointerup', 'click'].forEach(eventName => {
+ [true, false].forEach(clickChildElement => {
+ for (const target of targetparent.children) {
+ promise_test(async () => {
+ let parentReceivedEvent = false;
+ targetparent.addEventListener(eventName, () => parentReceivedEvent = true);
+
+ let targetReceivedEvent = false;
+ target.addEventListener(eventName, () => targetReceivedEvent = true);
+
+ let childReceivedEvent = false;
+ let targetchild = target.firstElementChild;
+ targetchild.addEventListener(eventName, () => childReceivedEvent = true);
+
+ await test_driver.click(clickChildElement ? targetchild : target);
+
+ const parentShouldReceiveEvents = eventName.startsWith('pointer');
+ assert_equals(parentReceivedEvent, parentShouldReceiveEvents,
+ `parent element received ${eventName} events`);
+
+ const targetShouldReceiveEvents = eventName.startsWith('pointer');
+ assert_equals(targetReceivedEvent, targetShouldReceiveEvents,
+ `target element received ${eventName} events`);
+ assert_equals(childReceivedEvent, clickChildElement,
+ `child element received ${eventName} events`);
+ }, `Testing ${eventName} events when clicking ${clickChildElement ? 'child of ' : ''}disabled ${target.localName}.`);
+ }
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/disabledElement.html b/testing/web-platform/tests/html/semantics/disabled-elements/disabledElement.html
new file mode 100644
index 0000000000..03f57424d2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/disabled-elements/disabledElement.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Disabled elements</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#disabled-elements">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<button disabled>button</button>
+<input disabled>
+<select disabled>
+ <optgroup label="options" disabled>
+ <option value="option1" disabled>option1
+ <option value="option2">option2
+</select>
+<textarea disabled>textarea</textarea>
+<fieldset disabled>
+ <input type=radio name=c value=0 checked>
+ <input type=radio name=c value=1>
+</fieldset>
+<a href="http://www.w3.org/" disabled>w3</a>
+<span tabindex=0 disabled>foobar</span>
+
+<script>
+ test(function(){
+ assert_equals(document.activeElement, document.body);
+ }, "The body element must be the active element if no element is focused");
+
+ ["button", "input", "select", "optgroup", "option", "textarea", "input[type=radio]"].forEach(function(el) {
+ test(function() {
+ var element = document.querySelector(el);
+ element.focus();
+ assert_equals(document.activeElement, document.body, "activeElement after focus on a disabled <" + el + "> remains unchanged");
+ }, "A disabled <" + el + "> should not be focusable");
+ });
+
+ ["a", "span"].forEach(function(el) {
+ test(function() {
+ var element = document.querySelector(el);
+ element.focus();
+ assert_equals(document.activeElement, element, "focus on a <" + el + "> with a disabled attribute should make it the activeElement");
+ }, "A disabled <" + el + "> should be focusable");
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled-keyboard.tentative.html b/testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled-keyboard.tentative.html
new file mode 100644
index 0000000000..3aacf21f1d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled-keyboard.tentative.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>KeyboardEvent propagation on disabled form elements</title>
+<link rel="author" href="mailto:avandolder@mozilla.com">
+<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/testdriver-actions.js"></script>
+<script>
+ class CustomControl extends HTMLElement {
+ static get formAssociated() {return true;}
+
+ constructor() {
+ super();
+ this.internals = this.attachInternals();
+
+ this.attachShadow({mode: "open", delegatesFocus: true});
+ this.shadowRoot.append(
+ document.querySelector("template").content.cloneNode(true)
+ );
+ }
+
+ get target() {
+ return this.shadowRoot.getElementById("target");
+ }
+ }
+
+ customElements.define("custom-control", CustomControl)
+</script>
+
+<template>
+ <div tabindex="0" id="target">
+ <slot></slot>
+ </div>
+</template>
+
+<div tabindex="0" id="reset"></div>
+
+<form id="form">
+ <input> <!-- Sanity check with non-disabled control -->
+ <select disabled></select>
+ <textarea disabled></textarea>
+ <input disabled type="button">
+ <input disabled type="checkbox">
+ <input disabled type="color" value="#000000">
+ <input disabled type="date">
+ <input disabled type="datetime-local">
+ <input disabled type="email">
+ <input disabled type="file">
+ <input disabled type="image">
+ <input disabled type="month">
+ <input disabled type="number">
+ <input disabled type="password">
+ <input disabled type="radio">
+ <input disabled type="range" value="0">
+ <input disabled type="reset">
+ <input disabled type="search">
+ <input disabled type="submit">
+ <input disabled type="tel">
+ <input disabled type="text">
+ <input disabled type="time">
+ <input disabled type="url">
+ <input disabled type="week">
+
+ <fieldset disabled><span tabindex="0">Span</span></fieldset>
+ <button disabled><span tabindex="0">Span</span></button>
+ <custom-control disabled>Text</custom-control>
+</form>
+
+<script>
+ const keyEvents = ["keydown", "keyup"];
+
+ function setupTest(t, element, observingElement) {
+ const observedEvents = [];
+ const controller = new AbortController();
+ const {signal} = controller;
+ const listenerFn = t.step_func(event => {
+ observedEvents.push(event.type);
+ });
+ for (const event of keyEvents) {
+ observingElement.addEventListener(event, listenerFn, {signal});
+ }
+ t.add_cleanup(() => controller.abort());
+
+ const target = element;
+ return {target, observedEvents};
+ }
+
+ function fire_trusted_key_events(target) {
+ const actions = new test_driver.Actions();
+ return actions.keyDown("a").keyUp("a").send();
+ }
+
+ function fire_untrusted_key_events(target) {
+ target.dispatchEvent(new KeyboardEvent("keydown", {bubbles: true}));
+ target.dispatchEvent(new KeyboardEvent("keyup", {bubbles: true}));
+ }
+
+ const observingElement = document.getElementById("form");
+ const reset = document.getElementById("reset");
+
+ for (const element of observingElement.children) {
+ promise_test(async t => {
+ const {observedEvents} = setupTest(t, element, observingElement);
+
+ const target = element.firstElementChild ?? element.target ?? element;
+ await t.step_func(fire_untrusted_key_events)(target);
+ await new Promise(resolve => t.step_timeout(resolve, 0));
+
+ assert_array_equals(observedEvents, keyEvents, "Observed events");
+ }, `Untrusted key events on ${element.outerHTML}, observed from <${observingElement.localName}>`);
+
+ // Only test elements with children for trusted key events.
+ if (!element.firstElementChild && !element.target) {
+ continue;
+ }
+
+ promise_test(async t => {
+ const {observedEvents} = setupTest(t, element, observingElement);
+
+ const target = element.firstElementChild ?? element.target;
+
+ reset.focus();
+ assert_not_equals(document.activeElement, target, "Reset current focus");
+ target.focus();
+ assert_equals(document.activeElement, element.firstElementChild ?? element, "Focus the target element");
+
+ await t.step_func(fire_trusted_key_events)(target);
+ await new Promise(resolve => t.step_timeout(resolve, 0));
+
+ assert_array_equals(observedEvents, keyEvents, "Observed events");
+ }, `Trusted key events on ${element.outerHTML}, observed from <${observingElement.localName}>`);
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled.tentative.html b/testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled.tentative.html
new file mode 100644
index 0000000000..e3dcd43a17
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled.tentative.html
@@ -0,0 +1,264 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<meta name="timeout" content="long">
+<title>Event propagation on disabled form elements</title>
+<link rel="author" href="mailto:krosylight@mozilla.com">
+<link rel="help" href="https://github.com/whatwg/html/issues/2368">
+<link rel="help" href="https://github.com/whatwg/html/issues/5886">
+<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/testdriver-actions.js"></script>
+
+<div id="cases">
+ <input> <!-- Sanity check with non-disabled control -->
+ <select disabled></select>
+ <select disabled>
+ <!-- <option> can't be clicked as it doesn't have its own painting area -->
+ <option>foo</option>
+ </select>
+ <fieldset disabled>Text</fieldset>
+ <fieldset disabled><span class="target">Span</span></fieldset>
+ <button disabled>Text</button>
+ <button disabled><span class="target">Span</span></button>
+ <textarea disabled></textarea>
+ <input disabled type="button">
+ <input disabled type="checkbox">
+ <input disabled type="color" value="#000000">
+ <input disabled type="date">
+ <input disabled type="datetime-local">
+ <input disabled type="email">
+ <input disabled type="file">
+ <input disabled type="image">
+ <input disabled type="month">
+ <input disabled type="number">
+ <input disabled type="password">
+ <input disabled type="radio">
+ <!-- Native click will click the bar -->
+ <input disabled type="range" value="0">
+ <!-- Native click will click the slider -->
+ <input disabled type="range" value="50">
+ <input disabled type="reset">
+ <input disabled type="search">
+ <input disabled type="submit">
+ <input disabled type="tel">
+ <input disabled type="text">
+ <input disabled type="time">
+ <input disabled type="url">
+ <input disabled type="week">
+ <my-control disabled>Text</my-control>
+</div>
+
+<script>
+ customElements.define('my-control', class extends HTMLElement {
+ static get formAssociated() { return true; }
+ get disabled() { return this.hasAttribute("disabled"); }
+ });
+
+ /**
+ * @param {Element} element
+ */
+ function getEventFiringTarget(element) {
+ return element.querySelector(".target") || element;
+ }
+
+ const allEvents = ["pointermove", "mousemove", "pointerdown", "mousedown", "pointerup", "mouseup", "click"];
+
+ /**
+ * @param {*} t
+ * @param {Element} element
+ * @param {Element} observingElement
+ */
+ function setupTest(t, element, observingElement) {
+ /** @type {{type: string, composedPath: Node[]}[]} */
+ const observedEvents = [];
+ const controller = new AbortController();
+ const { signal } = controller;
+ const listenerFn = t.step_func(event => {
+ observedEvents.push({
+ type: event.type,
+ target: event.target,
+ isTrusted: event.isTrusted,
+ composedPath: event.composedPath().map(n => n.constructor.name),
+ });
+ });
+ for (const event of allEvents) {
+ observingElement.addEventListener(event, listenerFn, { signal });
+ }
+ t.add_cleanup(() => controller.abort());
+
+ const target = getEventFiringTarget(element);
+ return { target, observedEvents };
+ }
+
+ /**
+ * @param {Element} element
+ * @returns {boolean}
+ */
+ function isFormControl(element) {
+ if (["button", "input", "select", "textarea"].includes(element.localName)) {
+ return true;
+ }
+ return element.constructor.formAssociated;
+ }
+
+ function isDisabledFormControl(element) {
+ return isFormControl(element) && element.disabled;
+ }
+
+ /**
+ * @param {Element} target
+ * @param {*} observedEvent
+ */
+ function shouldNotBubble(target, observedEvent) {
+ return (
+ isDisabledFormControl(target) &&
+ observedEvent.isTrusted &&
+ ["mousedown", "mouseup", "click"].includes(observedEvent.type)
+ );
+ }
+
+ /**
+ * @param {Event} event
+ */
+ function getExpectedComposedPath(event) {
+ let target = event.target;
+ const result = [];
+ while (target) {
+ if (shouldNotBubble(target, event)) {
+ return result;
+ }
+ result.push(target.constructor.name);
+ target = target.parentNode;
+ }
+ result.push("Window");
+ return result;
+ }
+
+ /**
+ * @param {object} options
+ * @param {Element & { disabled: boolean }} options.element
+ * @param {Element} options.observingElement
+ * @param {string[]} options.expectedEvents
+ * @param {(target: Element) => (Promise<void> | void)} options.clickerFn
+ * @param {string} options.title
+ */
+ function promise_event_test({ element, observingElement, expectedEvents, nonDisabledExpectedEvents, clickerFn, title }) {
+ promise_test(async t => {
+ const { target, observedEvents } = setupTest(t, element, observingElement);
+
+ await t.step_func(clickerFn)(target);
+ await new Promise(resolve => t.step_timeout(resolve, 0));
+
+ const expected = isDisabledFormControl(element) ? expectedEvents : nonDisabledExpectedEvents;
+ assert_array_equals(observedEvents.map(e => e.type), expected, "Observed events");
+
+ for (const observed of observedEvents) {
+ assert_equals(observed.target, target, `${observed.type}.target`)
+ assert_array_equals(
+ observed.composedPath,
+ getExpectedComposedPath(observed),
+ `${observed.type}.composedPath`
+ );
+ }
+
+ }, `${title} on ${element.outerHTML}, observed from <${observingElement.localName}>`);
+ }
+
+ /**
+ * @param {object} options
+ * @param {Element & { disabled: boolean }} options.element
+ * @param {string[]} options.expectedEvents
+ * @param {(target: Element) => (Promise<void> | void)} options.clickerFn
+ * @param {string} options.title
+ */
+ function promise_event_test_hierarchy({ element, expectedEvents, nonDisabledExpectedEvents, clickerFn, title }) {
+ const targets = [element, document.body];
+ if (element.querySelector(".target")) {
+ targets.unshift(element.querySelector(".target"));
+ }
+ for (const observingElement of targets) {
+ promise_event_test({ element, observingElement, expectedEvents, nonDisabledExpectedEvents, clickerFn, title });
+ }
+ }
+
+ function trusted_click(target) {
+ // To workaround type=file clicking issue
+ // https://github.com/w3c/webdriver/issues/1666
+ return new test_driver.Actions()
+ .pointerMove(0, 0, { origin: target })
+ .pointerDown()
+ .pointerUp()
+ .send();
+ }
+
+ const mouseEvents = ["mousemove", "mousedown", "mouseup", "click"];
+ const pointerEvents = ["pointermove", "pointerdown", "pointerup"];
+
+ // Events except mousedown/up/click
+ const allowedEvents = ["pointermove", "mousemove", "pointerdown", "pointerup"];
+
+ const elements = document.getElementById("cases").children;
+ for (const element of elements) {
+ // Observe on a child element of the control, if exists
+ const target = element.querySelector(".target");
+ if (target) {
+ promise_event_test({
+ element,
+ observingElement: target,
+ expectedEvents: allEvents,
+ nonDisabledExpectedEvents: allEvents,
+ clickerFn: trusted_click,
+ title: "Trusted click"
+ });
+ }
+
+ // Observe on the control itself
+ promise_event_test({
+ element,
+ observingElement: element,
+ expectedEvents: allowedEvents,
+ nonDisabledExpectedEvents: allEvents,
+ clickerFn: trusted_click,
+ title: "Trusted click"
+ });
+
+ // Observe on document.body
+ promise_event_test({
+ element,
+ observingElement: document.body,
+ expectedEvents: allowedEvents,
+ nonDisabledExpectedEvents: allEvents,
+ clickerFn: trusted_click,
+ title: "Trusted click"
+ });
+
+ const eventFirePair = [
+ [MouseEvent, mouseEvents],
+ [PointerEvent, pointerEvents]
+ ];
+
+ for (const [eventInterface, events] of eventFirePair) {
+ promise_event_test_hierarchy({
+ element,
+ expectedEvents: events,
+ nonDisabledExpectedEvents: events,
+ clickerFn: target => {
+ for (const event of events) {
+ target.dispatchEvent(new eventInterface(event, { bubbles: true }))
+ }
+ },
+ title: `Dispatch new ${eventInterface.name}()`
+ })
+ }
+
+ promise_event_test_hierarchy({
+ element,
+ expectedEvents: getEventFiringTarget(element) === element ? [] : ["click"],
+ nonDisabledExpectedEvents: ["click"],
+ clickerFn: target => target.click(),
+ title: `click()`
+ })
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/fieldset-event-propagation.tentative.html b/testing/web-platform/tests/html/semantics/disabled-elements/fieldset-event-propagation.tentative.html
new file mode 100644
index 0000000000..6d1a39c1de
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/disabled-elements/fieldset-event-propagation.tentative.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-disabled">
+<link rel=help href="https://github.com/whatwg/html/issues/5886#issuecomment-1460425364">
+<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/testdriver-actions.js"></script>
+
+<div id=target1parent>
+ <fieldset disabled id=target1fieldset>
+ <div id=target1child>hello world</div>
+ </fieldset>
+</div>
+
+<div id=target2parent>
+ <fieldset disabled id=target2fieldset>hello world</fieldset>
+</div>
+
+<script>
+ const clickers = {
+ "native click": target => test_driver.click(target),
+ "click()": target => target.click(),
+ };
+
+ for (const [clickerName, clicker] of Object.entries(clickers)) {
+ promise_test(async () => {
+ let target1parentClicked = false;
+ let target1childClicked = false;
+ let target1fieldsetClicked = false;
+ target1parent.onclick = () => target1parentClicked = true;
+ target1child.onclick = () => target1childClicked = true;
+ target1fieldset.onclick = () => target1fieldsetClicked = true;
+
+ await clicker(target1child);
+
+ assert_true(target1parentClicked, 'The parent of the fieldset should receive a click event.');
+ assert_true(target1childClicked, 'The child of the fieldset should receive a click event.');
+ assert_true(target1fieldsetClicked, 'The fieldset element should receive a click event.');
+ }, `Disabled fieldset elements should not prevent click event propagation from ${clickerName}`);
+
+ promise_test(async () => {
+ let target2parentClicked = false;
+ let target2fieldsetClicked = false;
+ target2parent.onclick = () => target2parentClicked = true;
+ target2fieldset.onclick = () => target2fieldsetClicked = true;
+
+ await clicker(target2fieldset);
+
+ assert_true(target2parentClicked, 'The parent of the fieldset should receive a click event.');
+ assert_true(target2fieldsetClicked, 'The fieldset element should receive a click event.');
+ }, `Disabled fieldset elements should not block click events from ${clickerName}.`);
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/conditionally-block-rendering-on-link-media-attr.html b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/conditionally-block-rendering-on-link-media-attr.html
new file mode 100644
index 0000000000..d21df46d30
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/conditionally-block-rendering-on-link-media-attr.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="support/utils.js"></script>
+
+<link rel=stylesheet href=stylesheet.py>
+<link rel=stylesheet media="screen and (max-width:10px)" href=stylesheet.py?stylesNotMatchingEnvironment&delay=2>
+<h1>Dominic Farolino</h1>
+<script>
+ test(() => {
+ const h1 = document.querySelector('h1');
+ const computedColor = getComputedStyle(h1).color;
+ const expectedColor = "rgb(128, 0, 128)";
+
+ assert_equals(computedColor, expectedColor);
+ assert_true(styleExists("h1 { color: purple; }")); // first style sheet
+ assert_false(styleExists("h1 { color: brown; }")); // second style sheet (should not be loaded yet)
+ }, "Only the style sheet loaded via a link element whose media attribute matches the environment should block following script execution");
+
+ const secondStylesheetTest = async_test("Both style sheets loaded via the link elements should be registered as style sheets for the document after 2 seconds");
+ secondStylesheetTest.step_timeout(() => {
+ assert_true(styleExists("h1 { color: purple; }")); // first style sheet
+ assert_true(styleExists("h1 { color: brown; }")); // second style sheet (loaded now!)
+ secondStylesheetTest.done();
+ }, 3000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/dynamic-render-blocking-link-stylesheet-does-not-block-script.html b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/dynamic-render-blocking-link-stylesheet-does-not-block-script.html
new file mode 100644
index 0000000000..bcc98050ac
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/dynamic-render-blocking-link-stylesheet-does-not-block-script.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Script-created render-blocking link stylesheet is not script-blocking</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/utils.js"></script>
+<script>
+const link = document.createElement('link');
+link.rel = 'stylesheet';
+link.href = 'stylesheet.py?delay=1';
+link.blocking = 'render';
+document.head.appendChild(link);
+</script>
+<h1>Some text</h1>
+<script>
+test(() => {
+ assert_false(styleExists("h1 { color: purple; }"),
+ 'stylesheet should still be pending');
+ const h1 = document.querySelector('h1');
+ assert_equals(getComputedStyle(h1).color, 'rgb(0, 0, 0)');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/dynamic-render-blocking-style-element-does-not-block-script.html b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/dynamic-render-blocking-style-element-does-not-block-script.html
new file mode 100644
index 0000000000..9a8c4b466b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/dynamic-render-blocking-style-element-does-not-block-script.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Script-created render-blocking style element is not script-blocking</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/utils.js"></script>
+<script>
+const style = document.createElement('style');
+const sheet = document.createTextNode('@import url(stylesheet.py?delay=1);');
+style.appendChild(sheet);
+style.blocking = 'render';
+document.head.appendChild(style);
+</script>
+<h1>Some text</h1>
+<script>
+test(() => {
+ assert_false(styleExists("h1 { color: purple; }"),
+ 'stylesheet should still be pending');
+ const h1 = document.querySelector('h1');
+ assert_equals(getComputedStyle(h1).color, 'rgb(0, 0, 0)');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/link-stylesheet-with-non-match-media-does-not-block-render.tentative.html b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/link-stylesheet-with-non-match-media-does-not-block-render.tentative.html
new file mode 100644
index 0000000000..7a881bcc3d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/link-stylesheet-with-non-match-media-does-not-block-render.tentative.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>
+ Delayed Stylesheet imported using link tag should not block rendering
+ or JS execution when media doesn't match.
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/utils.js"></script>
+<link rel="stylesheet" href="support/link-style.css?pipe=trickle(d1)"
+ media="print" onload="this.media='all'">
+<h1>
+ This text is black in color till stylesheet is fetched.
+</h1>
+<script>
+test(() => {
+ assert_false(styleExists("h1 { color: purple; }"),
+ 'Stylesheet should still be pending due to delay');
+ const h1 = document.querySelector('h1');
+ assert_equals(getComputedStyle(h1).color, 'rgb(0, 0, 0)');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/script-created-link-stylesheet-does-not-block-script.html b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/script-created-link-stylesheet-does-not-block-script.html
new file mode 100644
index 0000000000..2c27bd32f9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/script-created-link-stylesheet-does-not-block-script.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Script-created link stylesheet is not script-blocking</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/utils.js"></script>
+<script>
+const link = document.createElement('link');
+link.rel = 'stylesheet';
+link.href = 'stylesheet.py?delay=1';
+document.head.appendChild(link);
+</script>
+<h1>Some text</h1>
+<script>
+test(() => {
+ assert_false(styleExists("h1 { color: purple; }"),
+ 'stylesheet should still be pending');
+ const h1 = document.querySelector('h1');
+ assert_equals(getComputedStyle(h1).color, 'rgb(0, 0, 0)');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/script-created-style-element-does-not-block-script.html b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/script-created-style-element-does-not-block-script.html
new file mode 100644
index 0000000000..f04c3f668f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/script-created-style-element-does-not-block-script.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Script-created style element is not script-blocking</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/utils.js"></script>
+<script>
+const style = document.createElement('style');
+const sheet = document.createTextNode('@import url(stylesheet.py?delay=1);');
+style.appendChild(sheet);
+document.head.appendChild(style);
+</script>
+<h1>Some text</h1>
+<script>
+test(() => {
+ assert_false(styleExists("h1 { color: purple; }"),
+ 'stylesheet should still be pending');
+ const h1 = document.querySelector('h1');
+ assert_equals(getComputedStyle(h1).color, 'rgb(0, 0, 0)');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/style-element-media-match-block-script.html b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/style-element-media-match-block-script.html
new file mode 100644
index 0000000000..17adfc1728
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/style-element-media-match-block-script.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Style element is script-blocking when media matches</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/utils.js"></script>
+<style>
+@import url('stylesheet.py?delay=1');
+</style>
+<h1>Some text</h1>
+<script>
+test(() => {
+ assert_true(styleExists("h1 { color: purple; }"),
+ 'script should be blocked until the stylesheet is loaded');
+ const h1 = document.querySelector('h1');
+ assert_equals(getComputedStyle(h1).color, 'rgb(128, 0, 128)');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/style-element-media-not-match-does-not-block-script.html b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/style-element-media-not-match-does-not-block-script.html
new file mode 100644
index 0000000000..c05b6ed945
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/style-element-media-not-match-does-not-block-script.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Style element is not script-blocking when media doesn't match</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/utils.js"></script>
+<style media="print">
+@import url('stylesheet.py?delay=1');
+</style>
+<h1>Some text</h1>
+<script>
+test(() => {
+ assert_false(styleExists("h1 { color: purple; }"),
+ 'stylesheet should still be pending');
+ const h1 = document.querySelector('h1');
+ assert_equals(getComputedStyle(h1).color, 'rgb(0, 0, 0)');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/stylesheet.py b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/stylesheet.py
new file mode 100644
index 0000000000..d5ae5b9cca
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/stylesheet.py
@@ -0,0 +1,10 @@
+from time import sleep
+def main(request, response):
+ if b"delay" in request.GET:
+ delay = int(request.GET[b"delay"])
+ sleep(delay)
+
+ if b"stylesNotMatchingEnvironment" in request.GET:
+ return u'h1 {color: brown;}'
+ else:
+ return u'h1 {color: purple;}'
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/support/link-style.css b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/support/link-style.css
new file mode 100644
index 0000000000..1024df8792
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/support/link-style.css
@@ -0,0 +1,3 @@
+h1 {
+ color: purple;
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/support/utils.js b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/support/utils.js
new file mode 100644
index 0000000000..02d3a095cd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/interactions-of-styling-and-scripting/support/utils.js
@@ -0,0 +1,20 @@
+function styleExistsInSheet(styleText, sheet) {
+ for (let rule of sheet.cssRules) {
+ if (styleText == rule.cssText)
+ return true;
+ if (rule instanceof CSSImportRule) {
+ if (rule.styleSheet && styleExistsInSheet(styleText, rule.styleSheet))
+ return true;
+ }
+ }
+ return false;
+}
+
+function styleExists(styleText) {
+ for (let sheet of document.styleSheets) {
+ if (styleExistsInSheet(styleText, sheet))
+ return true;
+ }
+ return false;
+}
+
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/styling/LinkStyle.html b/testing/web-platform/tests/html/semantics/document-metadata/styling/LinkStyle.html
new file mode 100644
index 0000000000..d1bb433520
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/styling/LinkStyle.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>HTML Test: Styling</title>
+ <link rel="author" title="Intel" href="http://www.intel.com/">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#styling">
+ <link id="style1" rel="text" title="Intel" href="./support/unmatch.css">
+ <link id="style2" rel="alternate stylesheet" type="text/css" title="" href="./support/emptytitle.css">
+ <link id="style3" rel="alternate stylesheet" type="text/css" href="./support/notitle.css">
+ <link id="style5" rel="stylesheet" type="text/css" href="./support/normal.css">
+ <link id="style6" rel="alternate stylesheet" type="text/css" href="./support/normal.css" title="./support/alternate.css">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style id="style4" type="text/html">
+ #test {
+ height: 100px;
+ width: 100px;
+ }
+ </style>
+ <style id="style7" type="text/css" media="all" title="./support/alternate.css">
+ #test {
+ background-color: green;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="log"></div>
+ <div id="test" style="display:none">STYLING TEST</div>
+
+ <script>
+ /**
+ * Browsers may incorrectly issue requests for these resources and defer
+ * definition of the `sheet` attribute until after loading is complete.
+ * In such cases, synchronous assertions regarding the absence of
+ * attributes will spuriously pass.
+ *
+ * In order to account for this incorrect behavior (exhibited at the time
+ * of this writing most notably by the Chromium browser), defer the
+ * assertions until the "load" event has been triggered.
+ */
+ async_test(function(t) {
+ window.addEventListener("load", t.step_func(function() {
+ var style = null,
+ i;
+ for (i = 1; i < 5; i++) {
+ style = document.getElementById("style" + i);
+ assert_equals(style.sheet, null, "The sheet attribute of style" + i + " should be null.");
+ assert_false(style.disabled, "The disabled attribute of style" + i + " should be false.");
+ }
+ t.done();
+ }));
+ }, "The LinkStyle interface's sheet attribute must return null; the disabled attribute must be false");
+
+ test(function() {
+ var style = document.createElement("style"),
+ link = document.createElement("link");
+ assert_equals(style.sheet, null, "The sheet attribute of the style element not in a document should be null.");
+ assert_equals(link.sheet, null, "The sheet attribute of the link element not in a document should be null.");
+ }, "The LinkStyle interface's sheet attribute must return null if the corresponding element is not in a Document");
+
+ async_test(function(t) {
+ window.addEventListener("load", t.step_func(function() {
+ var style = null,
+ i;
+ for (i = 5; i < 8; i++) {
+ style = document.getElementById("style" + i);
+ assert_true(style.sheet instanceof StyleSheet, "The sheet attribute of style" + i + " should be a StyleSheet object.");
+ assert_equals(style.disabled, style.sheet.disabled, "The disabled attribute of style" + i + " should equal to the same attribute of StyleSheet.");
+ }
+ t.done();
+ }));
+ }, "The LinkStyle interface's sheet attribute must return StyleSheet object; the disabled attribute must be same as the StyleSheet's disabled attribute");
+
+ test(function() {
+ assert_equals(document.getElementById("style2").title, "", "The title attribute of style2 is incorrect.");
+ assert_equals(document.getElementById("style5").title, "", "The title attribute of style5 is incorrect.");
+ assert_equals(document.getElementById("style6").title, "./support/alternate.css", "The title attribute of style6 is incorrect.");
+ assert_equals(document.getElementById("style7").title, "./support/alternate.css", "The title attribute of style7 is incorrect.");
+ }, "The title must be the same as the value of the element's title content attribute");
+
+ test(function() {
+ assert_equals(document.getElementById("style5").media, "", "The media attribute of style5 is incorrect.");
+ assert_equals(document.getElementById("style7").media, "all", "The media attribute of style7 is incorrect.");
+ }, "The media must be the same as the value of the element's media content attribute, or the empty string if it is omitted");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/styling/support/alternate.css b/testing/web-platform/tests/html/semantics/document-metadata/styling/support/alternate.css
new file mode 100644
index 0000000000..b8deb07b0a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/styling/support/alternate.css
@@ -0,0 +1,7 @@
+#test {
+ color: yellow;
+ background-color: blue;
+ width: 100px;
+ height: 50px;
+ font-size: .5em;
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/styling/support/emptytitle.css b/testing/web-platform/tests/html/semantics/document-metadata/styling/support/emptytitle.css
new file mode 100644
index 0000000000..e62fe701b3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/styling/support/emptytitle.css
@@ -0,0 +1,4 @@
+#test {
+ width: 100px;
+ height: 100px;
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/styling/support/normal.css b/testing/web-platform/tests/html/semantics/document-metadata/styling/support/normal.css
new file mode 100644
index 0000000000..a803c22112
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/styling/support/normal.css
@@ -0,0 +1,5 @@
+#test {
+ width: 100px;
+ height: 50px;
+ font-size: 10px;
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/styling/support/notitle.css b/testing/web-platform/tests/html/semantics/document-metadata/styling/support/notitle.css
new file mode 100644
index 0000000000..e62fe701b3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/styling/support/notitle.css
@@ -0,0 +1,4 @@
+#test {
+ width: 100px;
+ height: 100px;
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/styling/support/unmatch.css b/testing/web-platform/tests/html/semantics/document-metadata/styling/support/unmatch.css
new file mode 100644
index 0000000000..e62fe701b3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/styling/support/unmatch.css
@@ -0,0 +1,4 @@
+#test {
+ width: 100px;
+ height: 100px;
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base-data.html b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base-data.html
new file mode 100644
index 0000000000..4905dd84ca
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base-data.html
@@ -0,0 +1,32 @@
+<!-- Please update base-javascript.html together with this -->
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>&lt;base> and data: URLs</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<base href="data:/,test">
+<base href="https://example.com/">
+<div id=log></div>
+<script>
+test(() => {
+ const link = document.createElement("a");
+ link.href = "blah";
+ assert_equals(link.href, new URL("blah", document.URL).href);
+}, "First <base> has a data: URL so fallback is used");
+
+test(() => {
+ document.querySelector("base").remove();
+ const link = document.createElement("a");
+ link.href = "blah";
+ assert_equals(link.href, new URL("blah", "https://example.com/").href);
+}, "First <base> is removed so second is used");
+
+test(() => {
+ const base = document.createElement("base");
+ base.href = "data:/,more-test";
+ document.head.prepend(base);
+ const link = document.createElement("a");
+ link.href = "blah";
+ assert_equals(link.href, new URL("blah", document.URL).href);
+}, "Dynamically inserted first <base> has a data: URL so fallback is used");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base-javascript.html b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base-javascript.html
new file mode 100644
index 0000000000..65d9c84fcf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base-javascript.html
@@ -0,0 +1,32 @@
+<!-- Please update base-data.html together with this -->
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>&lt;base> and javascript: URLs</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<base href="javascript:/,test">
+<base href="https://example.com/">
+<div id=log></div>
+<script>
+test(() => {
+ const link = document.createElement("a");
+ link.href = "blah";
+ assert_equals(link.href, new URL("blah", document.URL).href);
+}, "First <base> has a javascript: URL so fallback is used");
+
+test(() => {
+ document.querySelector("base").remove();
+ const link = document.createElement("a");
+ link.href = "blah";
+ assert_equals(link.href, new URL("blah", "https://example.com/").href);
+}, "First <base> is removed so second is used");
+
+test(() => {
+ const base = document.createElement("base");
+ base.href = "javascript:/,more-test";
+ document.head.prepend(base);
+ const link = document.createElement("a");
+ link.href = "blah";
+ assert_equals(link.href, new URL("blah", document.URL).href);
+}, "Dynamically inserted first <base> has a javascript: URL so fallback is used");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_about_blank.html b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_about_blank.html
new file mode 100644
index 0000000000..54c4794549
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_about_blank.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>base element in about:blank document should resolve against its fallback base URI</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe></iframe>
+<script>
+var t = async_test();
+addEventListener("load", t.step_func_done(function() {
+ var doc = frames[0].document;
+ var b = doc.createElement("base");
+ b.setAttribute("href", "test");
+ var newBaseValue = location.href.replace(/\/[^/]*$/, "/") + "test";
+ assert_equals(b.href, newBaseValue);
+ assert_equals(doc.baseURI, location.href);
+ doc.head.appendChild(b);
+ assert_equals(doc.baseURI, newBaseValue);
+}));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_empty.html b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_empty.html
new file mode 100644
index 0000000000..7737556a1a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_empty.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: base_href_empty</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-base-element">
+<base id="base" href="" target="_blank">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<img id="test" src="/images/blue-100x100.png" style="display:none">
+
+<script>
+ var testElement,
+ baseElement;
+
+ setup(function() {
+ testElement = document.getElementById("test");
+ baseElement = document.getElementById("base");
+ });
+
+ test(function() {
+ assert_equals(baseElement.href, document.location.href, "The href of base element is incorrect.");
+ }, "The value of the href attribute must be the document's address if it is empty");
+
+ test(function() {
+ var exp = testElement.src.substring(0, testElement.src.lastIndexOf("/images/blue-100x100.png") + 1);
+ assert_true(baseElement.href.indexOf(exp) != -1, "The src of img element is incorrect.");
+ }, "The src attribute of the img element must relative to document's address");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_invalid.html b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_invalid.html
new file mode 100644
index 0000000000..6d12d29e8a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_invalid.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>base element with unparseable href should have .href getter return attr value</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+test(function() {
+ var b = document.createElement("base");
+ b.setAttribute("href", "//test:test");
+ assert_equals(b.href, "//test:test");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_specified.html b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_specified.html
new file mode 100644
index 0000000000..83e71387a0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_specified.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: base_href_specified</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-base-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<base id="base">
+<div id="log"></div>
+<img id="test" src="test.ico" style="display:none">
+
+<script>
+ var testElement;
+ var baseElement;
+
+ var otherOrigin = get_host_info().HTTP_REMOTE_ORIGIN;
+
+ setup(function() {
+ testElement = document.getElementById("test");
+ baseElement = document.getElementById("base");
+
+ baseElement.setAttribute("href", otherOrigin);
+ });
+
+ test(function() {
+ assert_equals(baseElement.href, otherOrigin + "/", "The href attribute of the base element is incorrect.");
+ }, "The href attribute of the base element is specified");
+
+ test(function() {
+ assert_equals(testElement.src, otherOrigin + "/test.ico", "The src attribute of the img element is incorrect.");
+ }, "The src attribute of the img element must relative to the href attribute of the base element");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_unspecified.html b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_unspecified.html
new file mode 100644
index 0000000000..cf883f7239
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_href_unspecified.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: base_href_unspecified</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-base-element">
+<base id="base" target="_blank">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+<img id="test" src="/images/blue-100x100.png" style="display:none">
+
+<script>
+ var testElement,
+ baseElement;
+
+ setup(function () {
+ testElement = document.getElementById("test");
+ baseElement = document.getElementById("base");
+ });
+
+ test(function() {
+ assert_equals(baseElement.href, document.location.href, "Return the document base URL if the base element has no href content attribute.");
+ }, "The value of the href attribute must be the document's address if it is unspecified");
+
+ test(function() {
+ var exp = testElement.src.substring(0, testElement.src.lastIndexOf("/images/blue-100x100.png") + 1);
+ assert_true(baseElement.href.indexOf(exp) != -1, "The src attribute of the img element is incorrect.");
+ }, "The src attribute of the img element must relative to document's address");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_multiple.html b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_multiple.html
new file mode 100644
index 0000000000..4b7c0d213c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_multiple.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: base_multiple</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-base-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+ <div id="log"></div>
+ <iframe id="test1" src="example.html" style="width:0;height:0" frameborder="0"></iframe>
+ <iframe id="test2" src="example.html" name="targetWin" style="width:0;height:0" frameborder="0"></iframe>
+ <script>
+ async_test(function() {
+ window.onload = this.step_func(function() {
+ var fr1 = document.getElementById("test1");
+ fr1.addEventListener("load", this.unreached_func("loaded in the wrong iframe"));
+
+ var fr2 = document.getElementById("test2");
+ fr2.addEventListener("load", this.step_func_done(function () {
+ var doc2 = fr2.contentDocument;
+ assert_not_equals(doc2.location.href.indexOf("example2.html"), -1, "The target attribute does not impact the a element.");
+ assert_equals(doc2.getElementById("d1").innerHTML, "PASS", "The opend page should be the example2.html.");
+ }), true);
+
+ fr1.contentDocument.getElementById("a1").click();
+ });
+ }, "The attributes of the a element must be affected by the first base element");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_srcdoc.html b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_srcdoc.html
new file mode 100644
index 0000000000..eea1efe51d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_srcdoc.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>base element in srcdoc document should resolve against its fallback base URI</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe srcdoc=""></iframe>
+<script>
+var t = async_test();
+addEventListener("load", t.step_func_done(function() {
+ var doc = frames[0].document;
+ var b = doc.createElement("base");
+ b.setAttribute("href", "test");
+ var newBaseValue = location.href.replace(/\/[^/]*$/, "/") + "test";
+ assert_equals(b.href, newBaseValue);
+ assert_equals(doc.baseURI, location.href);
+ doc.head.appendChild(b);
+ assert_equals(doc.baseURI, newBaseValue);
+}));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_target_does_not_affect_iframe_src_navigation.html b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_target_does_not_affect_iframe_src_navigation.html
new file mode 100644
index 0000000000..b432698f21
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_target_does_not_affect_iframe_src_navigation.html
@@ -0,0 +1,10 @@
+<base id="base" target="_blank">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="i" src="about:blank"></iframe>
+<script>
+async_test(function(t) {
+ window.onmessage = () => t.done();
+ i.src = "data:text/html,This should navigate the iframe<script>top.postMessage('done', '*');</sc" + "ript>";
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_target_does_not_affect_location_assignment.html b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_target_does_not_affect_location_assignment.html
new file mode 100644
index 0000000000..2914f1f77f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/base_target_does_not_affect_location_assignment.html
@@ -0,0 +1,10 @@
+<base id="base" target="_blank">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="i" src="about:blank"></iframe>
+<script>
+async_test(function(t) {
+ window.onmessage = () => t.done();
+ i.contentWindow.location = "data:text/html,This should navigate the iframe<script>top.postMessage('done', '*');</sc" + "ript>";
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/example.html b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/example.html
new file mode 100644
index 0000000000..49dc772f91
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/example.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Example</title>
+<base target="targetWin" href="">
+<base target="_self" href="http://www.example.com/">
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<a id="a1" href="example2.html" target="">click me</a>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/example2.html b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/example2.html
new file mode 100644
index 0000000000..0e57cb9c5c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-base-element/example2.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Example</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<div id="d1">PASS</div>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/all b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/all
new file mode 100644
index 0000000000..60f1eab971
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/all
@@ -0,0 +1,3 @@
+body {
+ color: red;
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/all.headers b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/all.headers
new file mode 100644
index 0000000000..74e07a14e7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/all.headers
@@ -0,0 +1 @@
+Content-Type: text/css
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/document-without-browsing-context.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/document-without-browsing-context.html
new file mode 100644
index 0000000000..127b253f59
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/document-without-browsing-context.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Documents without browsing contexts should not load stylesheets</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<body>
+<script>
+ function count(id, t) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', 'stylesheet.py?count=1&id=' + id);
+ xhr.onload = t.step_func_done(function() {
+ assert_equals(xhr.responseText, "1");
+ });
+ xhr.onerror = t.unreached_func();
+ xhr.send();
+ }
+
+ async_test(function(t) {
+ var id = token();
+ var doc = (new DOMParser()).parseFromString('<link rel="stylesheet" href="stylesheet.py?id=' + id + '"></link>', 'text/html');
+ var link = doc.querySelector('link');
+ document.head.appendChild(link);
+ t.step_timeout(function() { count(id, t) }, 500);
+ }, 'Create a document, adopt the node');
+
+ async_test(function(t) {
+ var id = token();
+ var d = document.createElement('div');
+ document.body.appendChild(d);
+ d.innerHTML = '<link rel="stylesheet" href="stylesheet.py?id=' + id + '"></link>';
+ t.step_timeout(function() { count(id, t) }, 500);
+ }, 'Create a stylesheet in innerHTML document');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-error-fired-before-scripting-unblocked.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-error-fired-before-scripting-unblocked.html
new file mode 100644
index 0000000000..188e4ba5ab
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-error-fired-before-scripting-unblocked.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var saw_link_onerror = false;
+var t = async_test("Check if the stylesheet's error event is fired before the " +
+ "pending parsing-blocking script is unblocked");
+</script>
+<link href="nonexistent.css" rel="stylesheet" id="style_test"
+ onload="t.unreached_func('Sheet should fail to load')"
+ onerror="t.step(function() { saw_link_onerror = true; })">
+<script>
+ t.step(function() {
+ assert_true(saw_link_onerror, "The pending parsing-blocking script should " +
+ "only run after the last element that " +
+ "contributes a script-blocking style " +
+ "sheet's error event is fired if the sheet " +
+ "fails to load.");
+ });
+ t.done();
+</script>
+</head>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-error-events.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-error-events.html
new file mode 100644
index 0000000000..e4f617d458
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-error-events.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="resources/link-load-error-events.sub.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-error-events.https.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-error-events.https.html
new file mode 100644
index 0000000000..e4f617d458
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-error-events.https.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="resources/link-load-error-events.sub.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-event.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-event.html
new file mode 100644
index 0000000000..e95fff7988
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-event.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="author" title="Josh Matthews" href="mailto:josh@joshmatthews.net">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-link-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var saw_link_onload = false;
+var t = async_test("Check if the stylesheet's load event blocks the document load event");
+window.addEventListener('load', t.step_func_done(function() {
+ assert_true(saw_link_onload);
+}));
+</script>
+<link href="style.css?pipe=trickle(d3)" rel="stylesheet" id="style_test"
+ onload="t.step(function() { saw_link_onload = true; })"
+ onerror="t.unreached_func('Sheet should load OK')">
+</head>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-fired-before-scripting-unblocked.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-fired-before-scripting-unblocked.html
new file mode 100644
index 0000000000..a809cc44b1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-load-fired-before-scripting-unblocked.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var saw_link_onload = false;
+var t = async_test("Check if the stylesheet's load event is fired before the " +
+ "pending parsing-blocking script is unblocked");
+</script>
+<link href="style.css?pipe=trickle(d3)" rel="stylesheet" id="style_test"
+ onload="t.step(function() { saw_link_onload = true; })"
+ onerror="t.unreached_func('Sheet should load OK')">
+<script>
+ t.step(function() {
+ assert_true(saw_link_onload, "The pending parsing-blocking script should " +
+ "only run after the last element that " +
+ "contributes a script-blocking style " +
+ "sheet's load event is fired.");
+ });
+ t.done();
+</script>
+</head>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-multiple-error-events.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-multiple-error-events.html
new file mode 100644
index 0000000000..9d112e88d1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-multiple-error-events.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-link-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link id=style_link rel=stylesheet>
+<script>
+ async_test(t => {
+ const link = document.querySelector('#style_link');
+ link.onload = t.unreached_func('Sheet should fail to load');
+ link.onerror = t.step_func(() => {
+ link.onerror = t.step_func_done(() => {});
+ link.href = 'nonexistent.css?second';
+ });
+
+ link.href = 'nonexistent.css?first';
+ }, "Check if the <link>'s error event fires for each stylesheet it fails to load");
+
+ </script>
+</head>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-multiple-load-events.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-multiple-load-events.html
new file mode 100644
index 0000000000..b5550bb382
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-multiple-load-events.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-link-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link id=style_link rel=stylesheet>
+<script>
+ async_test(t => {
+ const link = document.querySelector('#style_link');
+ link.onerror = t.unreached_func('Sheet should load successfully');
+ link.onload = t.step_func(() => {
+ link.onload = t.step_func_done(() => {});
+ link.href = 'style.css?second';
+ });
+
+ link.href = 'style.css?first';
+
+ }, "Check if the <link>'s load event fires for each stylesheet it loads");
+ </script>
+</head>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rel-attribute-ascii-case-insensitive-notref.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rel-attribute-ascii-case-insensitive-notref.html
new file mode 100644
index 0000000000..04e3a7cb02
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rel-attribute-ascii-case-insensitive-notref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>link element rel is ASCII case-insensitive (mismatch reference)</title>
+<link rel="stylesheet" href="stylesheet.css">
+<p>Test passes if background is not red.</p>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rel-attribute-ascii-case-insensitive.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rel-attribute-ascii-case-insensitive.html
new file mode 100644
index 0000000000..5ee55f7d2e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rel-attribute-ascii-case-insensitive.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>link element rel is ASCII case-insensitive</title>
+<link rel="help" href="https://html.spec.whatwg.org/#the-link-element">
+<link rel="help" href="https://html.spec.whatwg.org/#attr-link-rel">
+<link rel="help" href="https://html.spec.whatwg.org/#linkTypes">
+<meta name="assert" content="link element's rel attribute is ASCII case-insensitive.">
+<link rel="mismatch" href="link-rel-attribute-ascii-case-insensitive-notref.html">
+
+<!-- Load sheet with a red background (rel attribute value is case-sensitive
+ equal to "stylesheet") -->
+<link rel="stylesheet" href="stylesheet.css">
+
+<!-- Load sheet with white background (rel attribute value is ASCII
+ case-insensitive equal to "stylesheet") -->
+<link rel="StyLeShEeT" href="style.css">
+
+<!-- Do not load sheet with a red background (rel attribute value is
+ case-insensitive equal to "stylesheet") -->
+<link rel="ſtyleſheet" href="stylesheet.css">
+
+<p>Test passes if background is not red.</p>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rel-attribute.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rel-attribute.html
new file mode 100644
index 0000000000..14d06227ac
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rel-attribute.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<script src = "/resources/testharness.js"></script>
+<script src = "/resources/testharnessreport.js"></script>
+
+<link id="light-link" rel="stylesheet" href="resources/link-rel-attribute.css">
+<div id="light-div" class="green">I"m green when light DOM link is on</div>
+
+<div id="host">
+ I"m green when Shadow DOM link is on
+ <template id="shadow-dom">
+ <link id="shadow-link" rel="stylesheet" href="resources/link-rel-attribute.css">
+ <div id="shadow-div" class="green">
+ <slot></slot>
+ </div>
+ </template>
+</div>
+
+<script>
+
+function testLinkRelModification(testDiv, testLink) {
+ assert_equals(getComputedStyle(testDiv).color, "rgb(0, 128, 0)");
+ testLink.setAttribute("rel", "no-stylesheet");
+ assert_equals(getComputedStyle(testDiv).color, "rgb(0, 0, 0)");
+ testLink.setAttribute("rel", "stylesheet");
+ assert_equals(getComputedStyle(testDiv).color, "rgb(0, 128, 0)");
+ testLink.removeAttribute("rel");
+ assert_equals(getComputedStyle(testDiv).color, "rgb(0, 0, 0)");
+}
+
+test (() => {
+ testLinkRelModification(document.querySelector("#light-div"),
+ document.querySelector("#light-link"));
+}, "Removing stylesheet from link rel attribute should remove the stylesheet for light DOM");
+
+test (() => {
+ var host = document.querySelector("#host");
+ var shadow = host.attachShadow({ mode: "open" });
+ var tmpl = document.querySelector("template#shadow-dom");
+ var clone = document.importNode(tmpl.content, true);
+ shadow.appendChild(clone);
+ testLinkRelModification(shadow.querySelector("#shadow-div"),
+ shadow.querySelector("#shadow-link"));
+}, "Removing stylesheet from link rel attribute should remove the stylesheet for shadow DOM");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rellist.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rellist.html
new file mode 100644
index 0000000000..8647426755
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-rellist.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>link.relList: non-string contains</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-link-element">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#domtokenlist">
+<link rel="help" href="https://webidl.spec.whatwg.org/#ecmascript-binding">
+<link rel="help" href="http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf#page=57">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link id="link" rel="undefined null 0 NaN Infinity">
+<div id="log"></div>
+<script>
+test(function() {
+ var list = document.getElementById("link").relList;
+ assert_equals(list.contains(undefined), true); //"undefined"
+ assert_equals(list.contains(null), true); //"null"
+ assert_equals(list.contains(-0), true); //"0"
+ assert_equals(list.contains(+0), true); //"0"
+ assert_equals(list.contains(NaN), true); //"NaN"
+ assert_equals(list.contains(+Infinity), true); //"Infinity"
+ assert_equals(list.contains(-Infinity), false); //"-Infinity"
+ assert_equals(list.supports("stylesheet"), true);
+ assert_equals(list.supports("nosuchrelvalueever"), false);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-style-error-01.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-style-error-01.html
new file mode 100644
index 0000000000..575324d761
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-style-error-01.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>link: error events</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-link-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=/common/get-host-info.sub.js></script>
+<div id="log"></div>
+<div id="test">
+<script>
+var t404 = async_test("Should get an error event for a 404 error.")
+t404.step(function() {
+ var elt = document.createElement("link");
+ elt.onerror = t404.step_func(function() {
+ assert_true(true, "Got error event for 404 error.")
+ t404.step_timeout(function() { t404.done() }, 0);
+ })
+ elt.onload = t404.unreached_func("load event should not be fired");
+ elt.rel = "stylesheet";
+ elt.href = "nonexistent_stylesheet.css";
+ document.getElementsByTagName("head")[0].appendChild(elt);
+})
+
+var tUnsupported = async_test("Should get an error event for an unsupported URL.")
+tUnsupported.step(function() {
+ var elt = document.createElement("link");
+ elt.onerror = tUnsupported.step_func(function() {
+ assert_true(true, "Got error event for unsupported URL.")
+ tUnsupported.step_timeout(function() { tUnsupported.done() }, 0);
+ })
+ elt.onload = tUnsupported.unreached_func("load event should not be fired");
+ elt.rel = "stylesheet";
+ elt.href = "nonexistent:stylesheet.css";
+ document.getElementsByTagName("head")[0].appendChild(elt);
+});
+</script>
+<script src=resources/link-style-error.js></script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-style-error-limited-quirks.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-style-error-limited-quirks.html
new file mode 100644
index 0000000000..d3c520ba75
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-style-error-limited-quirks.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//" "">
+<title>link: error events in limited quirks mode</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=/common/get-host-info.sub.js></script>
+<div id="log"></div>
+<script src=resources/link-style-error.js></script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-style-error-quirks.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-style-error-quirks.html
new file mode 100644
index 0000000000..ae2efa415e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-style-error-quirks.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML PUBLIC "-//Sun Microsystems Corp.//DTD HotJava Strict HTML//" "">
+<title>link: error events in quirks mode</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=/common/get-host-info.sub.js></script>
+<div id="log"></div>
+<script src=resources/link-style-error.js></script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-type-attribute-ref.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-type-attribute-ref.html
new file mode 100644
index 0000000000..f32472105d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-type-attribute-ref.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<p>You should see a green rectangle below</p>
+<div style="width:100px;height:100px;background-color:green"></div>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-type-attribute.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-type-attribute.html
new file mode 100644
index 0000000000..80acb9f3dc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/link-type-attribute.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<link rel=match href=link-type-attribute-ref.html>
+<link rel="stylesheet" type="application/javascript" href="data:text/css,div { background-color: red !important; }">
+<link rel="stylesheet" type="ABCtext/css" href="data:text/css,div { background-color: red !important; }">
+<link rel="stylesheet" type="text/cssDEF" href="data:text/css,div { background-color: red !important; }">
+<link rel="stylesheet" type="text/invalid" href="data:text/css,div { background-color: red !important; }">
+<link rel="stylesheet" type="invalid" href="data:text/css,div { background-color: red !important; }">
+<p>You should see a green rectangle below</p>
+<div style="width:100px;height:100px;background-color:green"></div>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/bad.css b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/bad.css
new file mode 100644
index 0000000000..4e1fe36165
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/bad.css
@@ -0,0 +1,4 @@
+p {
+ background-color: red;
+ color: black;
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/css.py b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/css.py
new file mode 100644
index 0000000000..1a11c1d5b3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/css.py
@@ -0,0 +1,7 @@
+def main(request, response):
+ response.add_required_headers = False
+ if b"content_type" in request.GET:
+ response.writer.write_header(b"Content-Type", request.GET.first(b"content_type"))
+ if b"nosniff" in request.GET:
+ response.writer.write_header(b"x-content-type-options", b"nosniff")
+ response.writer.write_content(u"body { background:red }")
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/empty-href.css b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/empty-href.css
new file mode 100644
index 0000000000..60f1eab971
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/empty-href.css
@@ -0,0 +1,3 @@
+body {
+ color: red;
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/good.css b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/good.css
new file mode 100644
index 0000000000..1da5e2b8cf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/good.css
@@ -0,0 +1,3 @@
+p {
+ color: green;
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/link-load-error-events.sub.js b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/link-load-error-events.sub.js
new file mode 100644
index 0000000000..33c8709579
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/link-load-error-events.sub.js
@@ -0,0 +1,192 @@
+/**
+ * This is the guts of the load/error event tests for <link rel="stylesheet">.
+ *
+ * We have a list of tests each of which is an object containing: href value,
+ * expected load success boolean, test description. Href values are set up in
+ * such a way that we guarantee that all stylesheet URLs are unique. This
+ * avoids issues around caching of sheets based on URL.
+ */
+
+// Our URLs are random, so we don't use them in error messages by
+// default, but enable doing it if someone wants to debug things.
+const DEBUG_URLS = false;
+
+var isHttps = location.protocol == "https:";
+
+var tests = [
+ // Basic tests
+ {
+ href: existingSheet(),
+ success: true,
+ description: "Basic load of stylesheet",
+ },
+ {
+ href: nonexistentSheet(),
+ success: false,
+ description: "Attempted load of nonexistent stylesheet",
+ },
+ {
+ href: `data:text/css,@import url("${existingSheet()}")`,
+ success: true,
+ description: "Import of stylesheet",
+ },
+ {
+ href: `data:text/css,@import url("${nonexistentSheet()}")`,
+ success: false,
+ description: "Import of nonexistent stylesheet",
+ },
+ {
+ href: `data:text/css,@import url("data:text/css,@import url('${existingSheet()}')")`,
+ success: true,
+ description: "Import of import of stylesheet",
+ },
+ {
+ href: `data:text/css,@import url("data:text/css,@import url('${nonexistentSheet()}')")`,
+ success: false,
+ description: "Import of import of nonexistent stylesheet",
+ },
+
+ // Non-CSS-response tests.
+ {
+ href: makeUnique(""),
+ success: false,
+ description: "Load of non-CSS stylesheet",
+ },
+ {
+ href: `data:text/css,@import url("${makeUnique("")}")`,
+ success: false,
+ description: "Import of non-CSS stylesheet",
+ },
+ {
+ href: `data:text/css,@import url("data:text/css,@import url('${makeUnique("")}')")`,
+ success: false,
+ description: "Import of import of non-CSS stylesheet",
+ },
+
+ // http:// tests, to test what happens with mixed content blocking.
+ {
+ href: httpSheet(),
+ success: !isHttps,
+ description: "Load of http:// stylesheet",
+ },
+ {
+ href: `data:text/css,@import url("${httpSheet()}")`,
+ success: !isHttps,
+ description: "Import of http:// stylesheet",
+ },
+ {
+ href: `data:text/css,@import url("data:text/css,@import url('${httpSheet()}')")`,
+ success: !isHttps,
+ description: "Import of import of http:// stylesheet",
+ },
+
+ // https:// tests just as a control
+ {
+ href: httpsSheet(),
+ success: true,
+ description: "Load of https:// stylesheet",
+ },
+ {
+ href: `data:text/css,@import url("${httpsSheet()}")`,
+ success: true,
+ description: "Import of https:// stylesheet",
+ },
+ {
+ href: `data:text/css,@import url("data:text/css,@import url('${httpsSheet()}')")`,
+ success: true,
+ description: "Import of import of https:// stylesheet",
+ },
+
+ // Tests with multiple imports some of which are slow and some are fast.
+ {
+ href: `data:text/css,@import url("${slowResponse(existingSheet())}"); @import url("${nonexistentSheet()}");`,
+ success: false,
+ description: "Slow successful import, fast failing import",
+ },
+ {
+ href: `data:text/css,@import url("${existingSheet()}"); @import url("${slowResponse(nonexistentSheet())}");`,
+ success: false,
+ description: "Fast successful import, slow failing import",
+ }
+];
+
+// Note: Here we really do need to use "let" at least for the href,
+// because we lazily evaluate it in the unreached cases.
+for (var test of tests) {
+ let {href, success, description} = test;
+ var t = async_test(description);
+ var link = document.createElement("link");
+ link.rel = "stylesheet";
+ hrefString = DEBUG_URLS ? `: ${href}` : "";
+ if (success) {
+ link.onload = t.step_func_done(() => {});
+ link.onerror = t.step_func_done(() => assert_unreached(`error fired when load expected${hrefString}`) );
+ } else {
+ link.onerror = t.step_func_done(() => {});
+ link.onload = t.step_func_done(() => assert_unreached(`load fired when error expected${hrefString}`) );
+ }
+ link.href = href;
+ document.head.appendChild(link);
+}
+
+/* Utility function */
+function makeUnique(url) {
+ // Make sure we copy here, even if the thing coming in is a URL, so we don't
+ // mutate our caller's data.
+ url = new URL(url, location.href);
+ // We want to generate a unique URI to avoid the various caches browsers have
+ // for stylesheets. We don't want to just use a counter, because that would
+ // not be robust to the test being reloaded or othewise run multiple times
+ // without a browser restart. We don't want to use timstamps, because those
+ // are not likely to be unique across calls to this function, especially given
+ // the degraded timer resolution browsers have due to Spectre.
+ //
+ // So just fall back on Math.random() and assume it can't duplicate values.
+ url.searchParams.append("r", Math.random());
+ return url;
+}
+
+function existingSheet() {
+ return makeUnique("resources/good.css");
+}
+
+/**
+ * Function the add values to the "pipe" search param. See
+ * http://wptserve.readthedocs.io/en/latest/pipes.html for why one would do
+ * this. Because this param uses a weird '|'-separated syntax instead of just
+ * using multiple params with the same name, we need some manual code to munge
+ * the value properly.
+ */
+function addPipe(url, pipeVal) {
+ url = new URL(url, location.href);
+ var params = url.searchParams;
+ var oldVal = params.get("pipe");
+ if (oldVal) {
+ params.set("pipe", oldVal + "|" + pipeVal);
+ } else {
+ params.set("pipe", pipeVal);
+ }
+ return url;
+}
+
+function nonexistentSheet() {
+ return addPipe(existingSheet(), "status(404)");
+}
+
+function httpSheet() {
+ var url = existingSheet();
+ url.protocol = "http";
+ url.port = {{ports[http][0]}};
+ return url;
+}
+
+function httpsSheet() {
+ var url = existingSheet();
+ url.protocol = "https";
+ url.port = {{ports[https][0]}};
+ return url;
+}
+
+function slowResponse(url) {
+ return addPipe(url, "trickle(d1)");
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/link-rel-attribute.css b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/link-rel-attribute.css
new file mode 100644
index 0000000000..fa95e11ba9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/link-rel-attribute.css
@@ -0,0 +1,3 @@
+.green {
+ color: green;
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/link-style-error.js b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/link-style-error.js
new file mode 100644
index 0000000000..d1fa5ac2d6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/link-style-error.js
@@ -0,0 +1,47 @@
+["<link>", "@import"].forEach(linkType => {
+ [
+ ["same-origin", "resources/css.py"],
+ ["cross-origin", get_host_info().HTTP_REMOTE_ORIGIN + "/html/semantics/document-metadata/the-link-element/resources/css.py"]
+ ].forEach(originType => {
+ ["no Content-Type", "wrong Content-Type", "broken Content-Type"].forEach(contentType => {
+ ["no nosniff", "nosniff"].forEach(nosniff => {
+ async_test(t => {
+ const l = document.createElement("link");
+ t.add_cleanup(() => l.remove());
+ if (nosniff === "nosniff" || contentType === "wrong Content-Type" && (document.compatMode === "CSS1Compat" || originType[0] === "cross-origin")) {
+ l.onerror = t.step_func_done();
+ l.onload = t.unreached_func("error event should have fired");
+ } else {
+ l.onload = t.step_func_done();
+ l.onerror = t.unreached_func("load event should have fired");
+ }
+ l.rel = "stylesheet";
+ let query = [];
+ if (contentType === "broken Content-Type") {
+ query.push("content_type=oops");
+ } else if (contentType === "wrong Content-Type") {
+ query.push("content_type=text/plain")
+ }
+ if (nosniff === "nosniff") {
+ query.push("nosniff");
+ }
+ let stringQuery = "";
+ query.forEach(val => {
+ if (stringQuery === "") {
+ stringQuery += "?" + val;
+ } else {
+ stringQuery += "&" + val;
+ }
+ });
+ const link = new URL(originType[1] + stringQuery, location).href;
+ if (linkType === "<link>") {
+ l.href = link;
+ } else {
+ l.href = "data:text/css,@import url(" + link + ");";
+ }
+ document.head.appendChild(l);
+ }, "Stylesheet loading using " + linkType + " with " + contentType + ", " + originType[0] + ", and " + nosniff);
+ });
+ });
+ });
+});
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/neutral.css b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/neutral.css
new file mode 100644
index 0000000000..796c55c42f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/neutral.css
@@ -0,0 +1,3 @@
+body {
+ background-color: gray;
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/stylesheet.css b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/stylesheet.css
new file mode 100644
index 0000000000..e1b2552ffe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/resources/stylesheet.css
@@ -0,0 +1,3 @@
+body {
+ background-color: green;
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/style.css b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/style.css
new file mode 100644
index 0000000000..d48115e565
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/style.css
@@ -0,0 +1,3 @@
+body {
+ background-color: white;
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-change-href-ref.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-change-href-ref.html
new file mode 100644
index 0000000000..9ae6e36655
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-change-href-ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset="utf-8">
+<style>
+ p {
+ color: green;
+ }
+</style>
+<p>This text should be green on a white background
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-change-href.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-change-href.html
new file mode 100644
index 0000000000..6a3f18de98
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-change-href.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Obtaining a new stylesheet removes styles from the previous stylesheet.</title>
+<link rel=match href=stylesheet-change-href-ref.html>
+<script>
+ function changeHref() {
+ var elem = document.getElementById('stylesheet');
+ elem.href = 'resources/good.css';
+ elem.onload = null;
+ }
+</script>
+<link id=stylesheet rel=stylesheet href="resources/bad.css" onload="changeHref()">
+<p>This text should be green on a white background
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-empty-href-ref.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-empty-href-ref.html
new file mode 100644
index 0000000000..63b75d0ae2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-empty-href-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test</title>
+<style>
+body {
+ color: green;
+}
+</style>
+<p>This text should be green.
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-empty-href.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-empty-href.html
new file mode 100644
index 0000000000..16b14efacc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-empty-href.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test</title>
+<link rel=match href=stylesheet-empty-href-ref.html>
+<style>
+body {
+ color: green;
+}
+</style>
+<base href=resources/empty-href.css>
+<link rel=stylesheet href>
+<p>This text should be green.
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-media-ref.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-media-ref.html
new file mode 100644
index 0000000000..63b75d0ae2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-media-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test</title>
+<style>
+body {
+ color: green;
+}
+</style>
+<p>This text should be green.
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-media.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-media.html
new file mode 100644
index 0000000000..9a72924cf4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-media.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test</title>
+<link rel=match href=stylesheet-media-ref.html>
+<style>
+body {
+ color: green;
+}
+</style>
+<link rel=stylesheet id=link>
+<script>
+// This tests for a bug in Servo, where it would treat the media attribute as
+// if it was the href attribute.
+var link = document.getElementById("link");
+link.setAttribute("media", "all")
+</script>
+<p>This text should be green.
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-not-removed-until-next-stylesheet-loads.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-not-removed-until-next-stylesheet-loads.html
new file mode 100644
index 0000000000..ab8ee727f2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-not-removed-until-next-stylesheet-loads.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link href="style.css" rel="stylesheet" id="style_test">
+<script>
+ test(function() {
+ assert_true(document.styleSheets.length === 1 &&
+ document.styleSheets[0].href.includes("style.css"),
+ "The style sheet 'style.css' must be available to scripts");
+
+ style_test.href = "resources/neutral.css?pipe=trickle(d1)";
+
+ assert_true(document.styleSheets.length === 1 &&
+ document.styleSheets[0].href.includes("style.css"),
+ "The style sheet 'style.css' must remain accessible to " +
+ "scripts until its replacement has finished loading");
+ }, "Check that a style sheet loaded by a <link> is available until its successor is loaded");
+</script>
+</head>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-with-base-ref.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-with-base-ref.html
new file mode 100644
index 0000000000..83f0d06772
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-with-base-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Stylesheet Without Base Tag</title>
+ <style>
+ body { background-color: green; }
+ </style>
+</head>
+<body>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-with-base.html b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-with-base.html
new file mode 100644
index 0000000000..a9f2a8bce0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet-with-base.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Stylesheet With Base Tag</title>
+ <link rel="match" href="stylesheet-with-base-ref.html">
+ <base href="resources/">
+ <link rel="stylesheet" href="stylesheet.css">
+</head>
+<body>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet.css b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet.css
new file mode 100644
index 0000000000..e8f24f94a3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet.css
@@ -0,0 +1,3 @@
+body {
+ background-color: red;
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet.py b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet.py
new file mode 100644
index 0000000000..1a4dec2724
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-link-element/stylesheet.py
@@ -0,0 +1,9 @@
+def main(request, response):
+ try:
+ count = int(request.server.stash.take(request.GET[b"id"]))
+ except:
+ count = 0
+ if b"count" in request.GET:
+ return str(count)
+ request.server.stash.put(request.GET[b"id"], str(count + 1))
+ return u'body { color: red }'
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-attribute-changes.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-attribute-changes.html
new file mode 100644
index 0000000000..6f877ee416
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-attribute-changes.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<title>Meta color-scheme - attribute changes</title>
+<meta id="meta" name="color-scheme" content="dark">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/compute-root-color-scheme.js"></script>
+<!--
+ NOTE: This test assumes that the browser's default color-scheme is "light",
+ see https://github.com/web-platform-tests/wpt/pull/31268 for reasoning
+-->
+<script>
+ assert_root_color_scheme("dark", "Meta color-scheme initially 'dark'.");
+
+ meta.removeAttribute("name");
+ assert_root_color_scheme("light", "Removed name attribute from meta color-scheme.");
+
+ meta.setAttribute("name", "color-scheme");
+ assert_root_color_scheme("dark", "Set meta name to color-scheme.");
+
+ meta.setAttribute("content", "");
+ assert_root_color_scheme("light", "Set content attribute of meta color-scheme to empty string.");
+
+ meta.setAttribute("content", ",,invalid");
+ assert_root_color_scheme("light", "Set content attribute of meta color-scheme to an invalid value.");
+
+ meta.setAttribute("content", "light");
+ assert_root_color_scheme("light", "Set content attribute of meta color-scheme to 'light'.");
+
+ meta.setAttribute("content", "dark");
+ assert_root_color_scheme("dark", "Set content attribute of meta color-scheme to 'dark'.");
+
+ meta.removeAttribute("content");
+ assert_root_color_scheme("light", "Removed the content attribute of meta color-scheme.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-empty-content-value.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-empty-content-value.html
new file mode 100644
index 0000000000..8a3cf18af8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-empty-content-value.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Meta color-scheme - empty content value</title>
+<meta name="color-scheme" content="">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/compute-root-color-scheme.js"></script>
+<!--
+ NOTE: This test assumes that the browser's default color-scheme is "light",
+ see https://github.com/web-platform-tests/wpt/pull/31268 for reasoning
+-->
+<script>
+ assert_root_color_scheme("light", "Meta color-scheme with empty content attribute has no effect.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-first-valid-applies.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-first-valid-applies.html
new file mode 100644
index 0000000000..095d0f360d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-first-valid-applies.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>Multiple color-scheme meta tags - first valid applies</title>
+<meta name="color-scheme">
+<meta name="color-scheme" content>
+<meta name="color-scheme" content="">
+<meta name="color-scheme" content="light,dark">
+<!-- This is first with a valid content value -->
+<meta name="color-scheme" content="dark">
+<meta name="color-scheme" content="light">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/compute-root-color-scheme.js"></script>
+<script>
+ assert_root_color_scheme("dark", "Tree order decides which meta color-scheme applies.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-insert.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-insert.html
new file mode 100644
index 0000000000..463c318105
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-insert.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<title>Insert color-scheme meta tags</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/compute-root-color-scheme.js"></script>
+<!--
+ NOTE: This test assumes that the browser's default color-scheme is "light",
+ see https://github.com/web-platform-tests/wpt/pull/31268 for reasoning
+-->
+<script>
+ function createMeta(content) {
+ const meta = document.createElement("meta");
+ meta.setAttribute("name", "color-scheme");
+ meta.setAttribute("content", content);
+ return meta;
+ }
+
+ assert_root_color_scheme("light", "Initial color-scheme");
+
+ document.head.appendChild(createMeta("dark"));
+ assert_root_color_scheme("dark", "Inserted meta color-scheme applies");
+
+ document.head.insertBefore(createMeta("light"), document.head.lastChild);
+ assert_root_color_scheme("light", "Inserted meta color-scheme before existing in head applies");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-no-content-value.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-no-content-value.html
new file mode 100644
index 0000000000..0d22e44b26
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-no-content-value.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Meta color-scheme - no content value</title>
+<meta name="color-scheme">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/compute-root-color-scheme.js"></script>
+<!--
+ NOTE: This test assumes that the browser's default color-scheme is "light",
+ see https://github.com/web-platform-tests/wpt/pull/31268 for reasoning
+-->
+<script>
+ assert_root_color_scheme("light", "Meta color-scheme without content attribute has no effect.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-normal-descendant-change.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-normal-descendant-change.html
new file mode 100644
index 0000000000..136f4c371b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-normal-descendant-change.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>Change color-scheme meta tag affecting normal descendant</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<meta id="meta" name="color-scheme" content="dark">
+<div style="color-scheme: dark; color: CanvasText" id="dark">
+ <div style="color-scheme: normal; color: CanvasText" id="normal"></div>
+</div>
+<script>
+ test(() => {
+ assert_equals(getComputedStyle(dark).color, getComputedStyle(normal).color);
+ }, "Normal initially dark");
+
+ meta.content = "light";
+
+ test(() => {
+ assert_not_equals(getComputedStyle(dark).color, getComputedStyle(normal).color);
+ }, "Normal should change to light from page color schemes");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-remove-head.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-remove-head.html
new file mode 100644
index 0000000000..587e2fa596
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-remove-head.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<title>Remove head with meta color-scheme</title>
+<meta name="color-scheme" content="dark">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/compute-root-color-scheme.js"></script>
+<!--
+ NOTE: This test assumes that the browser's default color-scheme is "light",
+ see https://github.com/web-platform-tests/wpt/pull/31268 for reasoning
+-->
+<body></body>
+<script>
+ assert_root_color_scheme("dark", "Meta color-scheme applies.");
+ document.head.remove();
+ assert_root_color_scheme("light", "Initial value after removing head including meta color-scheme.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-remove.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-remove.html
new file mode 100644
index 0000000000..a89a520791
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-remove.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>Remove color-scheme meta tag</title>
+<meta id="dark" name="color-scheme" content="dark">
+<meta id="light" name="color-scheme" content="light">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/compute-root-color-scheme.js"></script>
+<!--
+ NOTE: This test assumes that the browser's default color-scheme is "light",
+ see https://github.com/web-platform-tests/wpt/pull/31268 for reasoning
+-->
+<script>
+ assert_root_color_scheme("dark", "First meta applies.");
+ dark.remove();
+ assert_root_color_scheme("light", "Second meta applies after first one is removed.");
+ light.remove();
+ assert_root_color_scheme("light", "Initial color-scheme with both meta elements removed.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-single-value-in-body.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-single-value-in-body.html
new file mode 100644
index 0000000000..19f8d53994
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-single-value-in-body.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<title>Meta color-scheme in body should apply</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/compute-root-color-scheme.js"></script>
+<body>
+ <meta name="color-scheme" content="dark">
+</body>
+<script>
+ assert_root_color_scheme("dark", "Meta color-scheme in body should apply.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-single-value-in-head.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-single-value-in-head.html
new file mode 100644
index 0000000000..b9fd2c4384
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-single-value-in-head.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>Single meta color-scheme in head</title>
+<meta name="color-scheme" content="dark">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/compute-root-color-scheme.js"></script>
+<script>
+ assert_root_color_scheme("dark", "Meta color-scheme in head applies.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-single-value-in-shadow-tree.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-single-value-in-shadow-tree.html
new file mode 100644
index 0000000000..7ccafc8419
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/meta-color-scheme-single-value-in-shadow-tree.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>Meta color-scheme in shadow-tree should not apply</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/compute-root-color-scheme.js"></script>
+<!--
+ NOTE: This test assumes that the browser's default color-scheme is "light",
+ see https://github.com/web-platform-tests/wpt/pull/31268 for reasoning
+-->
+<script>
+ const host = document.createElement("div");
+ host.id = "host";
+ document.head.appendChild(host);
+ const root = host.attachShadow({mode:"open"});
+ const meta = document.createElement("meta");
+ meta.setAttribute("name", "color-scheme");
+ meta.setAttribute("content", "dark");
+ root.appendChild(meta);
+
+ assert_root_color_scheme("light", "Meta color-scheme in shadow tree does not apply.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/support/compute-root-color-scheme.js b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/support/compute-root-color-scheme.js
new file mode 100644
index 0000000000..74cbf895ce
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/color-scheme/support/compute-root-color-scheme.js
@@ -0,0 +1,28 @@
+'use strict';
+
+function assert_root_color_scheme(expected_used_scheme, description) {
+ function get_used_root_color_scheme() {
+ let light = get_system_color("only light", "CanvasText");
+ let dark = get_system_color("only dark", "CanvasText");
+ assert_not_equals(light, dark, "CanvasText system color should be different with light and dark color schemes");
+ let root = getComputedStyle(document.documentElement).color;
+ assert_in_array(root, [light, dark], "Root color scheme should be either light or dark, or the text needs to be extended for newer color-schemes");
+ return root == light ? "light" : "dark";
+ }
+
+ function get_system_color(scheme, color) {
+ let div = document.createElement("div");
+ div.style.color = color;
+ div.style.colorScheme = scheme;
+
+ document.documentElement.appendChild(div);
+ let computed = getComputedStyle(div).color;
+ div.remove();
+ return computed;
+ }
+
+ test(() => {
+ assert_equals(get_used_root_color_scheme(), expected_used_scheme);
+ assert_equals(getComputedStyle(document.documentElement).colorScheme, "normal", "Root element's color-scheme should be 'normal'");
+ }, description);
+}
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/http-equiv-and-name-1.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/http-equiv-and-name-1.html
new file mode 100644
index 0000000000..ac82e3396d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/http-equiv-and-name-1.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<title>Setting both http-equiv and name attributes on a meta element</title>
+<meta http-equiv=content-language name=color-scheme content=dark>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="color-scheme/support/compute-root-color-scheme.js"></script>
+<!--
+ NOTE: This test assumes that the browser's default color-scheme is "light",
+ see https://github.com/web-platform-tests/wpt/pull/31268 for reasoning
+-->
+<script>
+ // This creates a test()
+ assert_root_color_scheme("dark", "<meta> set the color-scheme to dark");
+
+ // We can't test content-language against :lang(), because CSS Selectors 4
+ // references BCP 47 syntax and RFC4647 "Matching of Language Tags", but
+ // "dark" is not a well-formed BCP 47 tag and therefore cannot be matched.
+ // Therefore, the test that content-language gets set is split off to a
+ // separate testcase using a well-formed lang tag as the content.
+ // test(() => {
+ // assert_equals(document.querySelector(":root:lang(dark)"), document.documentElement);
+ // }, "<meta> set the content-language to dark");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/http-equiv-and-name-2.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/http-equiv-and-name-2.html
new file mode 100644
index 0000000000..b73013a341
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/http-equiv-and-name-2.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>Setting both http-equiv and name attributes on a meta element</title>
+<meta http-equiv=content-language name=color-scheme content=de-DE>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ // We don't attempt to test the color-scheme here because "de-DE" is not a valid
+ // value for it.
+
+ test(() => {
+ assert_equals(document.querySelector(":root:lang(de-DE)"), document.documentElement);
+ }, "<meta> set the content-language to de-DE");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-1.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-1.html
new file mode 100644
index 0000000000..196f6d0409
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-1.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Meta refresh is blocked by the allow-scripts sandbox flag at its creation time, not when refresh comes due</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-refresh">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ single_test: true });
+
+const sourceIFrame = document.createElement("iframe");
+sourceIFrame.setAttribute("sandbox", "allow-same-origin");
+
+const destIFrame = document.createElement("iframe");
+
+let sourceLoadCount = 0;
+let destLoadCount = 0;
+
+sourceIFrame.onload = () => {
+ ++sourceLoadCount;
+
+ if (sourceLoadCount === 2) {
+ assert_unreached("The iframe from which the meta came from must not refresh");
+ }
+
+ maybeStartTest();
+};
+
+destIFrame.onload = () => {
+ ++destLoadCount;
+
+ if (destLoadCount === 2) {
+ // destIFrame doesn't have the sandboxed automatic features browsing context
+ // flag sets, thus navigated.
+ assert_equals(destIFrame.contentDocument.body.textContent.trim(), "foo");
+ done();
+ }
+
+ maybeStartTest();
+};
+
+function maybeStartTest() {
+ if (sourceLoadCount === 1 && destLoadCount === 1) {
+ const meta = sourceIFrame.contentDocument.querySelector("meta");
+ destIFrame.contentDocument.body.appendChild(meta);
+ }
+}
+
+sourceIFrame.src = "support/refresh.sub.html?input=" + encodeURIComponent("1; url=foo");
+destIFrame.src = "support/ufoo";
+
+document.body.appendChild(sourceIFrame);
+document.body.appendChild(destIFrame);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-2.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-2.html
new file mode 100644
index 0000000000..cc7eb5e5e0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-2.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Meta refresh of the original iframe is not blocked if moved into a sandboxed iframe</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-refresh">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ single_test: true });
+
+const sourceIFrame = document.createElement("iframe");
+
+const destIFrame = document.createElement("iframe");
+destIFrame.setAttribute("sandbox", "allow-same-origin");
+
+let sourceLoadCount = 0;
+let destLoadCount = 0;
+
+sourceIFrame.onload = () => {
+ ++sourceLoadCount;
+
+ if (sourceLoadCount === 2) {
+ assert_equals(sourceIFrame.contentDocument.body.textContent.trim(), "foo");
+ done();
+ }
+
+ maybeStartTest();
+};
+
+destIFrame.onload = () => {
+ ++destLoadCount;
+
+ if (destLoadCount === 2) {
+ assert_unreached("The iframe into which the meta was moved must not refresh");
+ }
+
+ maybeStartTest();
+};
+
+function maybeStartTest() {
+ if (sourceLoadCount === 1 && destLoadCount === 1) {
+ const meta = sourceIFrame.contentDocument.querySelector("meta");
+ destIFrame.contentDocument.body.appendChild(meta);
+ }
+}
+
+sourceIFrame.src = "support/refresh.sub.html?input=" + encodeURIComponent("1; url=foo");
+destIFrame.src = "support/ufoo";
+
+document.body.appendChild(sourceIFrame);
+document.body.appendChild(destIFrame);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/dynamic-append.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/dynamic-append.html
new file mode 100644
index 0000000000..4d2fa78940
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/dynamic-append.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Meta refresh applies even when dynamically appended</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#pragma-directives">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ single_test: true });
+
+const iframe = document.createElement("iframe");
+let loadCount = 0;
+
+iframe.onload = () => {
+ ++loadCount;
+ const iDocument = iframe.contentDocument;
+
+ if (loadCount === 1) {
+ iDocument.body.innerHTML = `<meta http-equiv="refresh" content="1; url=foo">`;
+ } else if (loadCount === 2) {
+ assert_equals(iDocument.body.textContent.trim(), "foo");
+ done();
+ }
+};
+
+iframe.src = "support/ufoo";
+document.body.appendChild(iframe);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/not-in-shadow-tree.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/not-in-shadow-tree.html
new file mode 100644
index 0000000000..2a9f301fff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/not-in-shadow-tree.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Meta refresh only applies while in the document tree, not in a shadow tree</title>
+<meta name="timeout" content="long" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#pragma-directives">
+
+<div id="log"></div>
+<script>
+"use strict";
+setup({ single_test: true });
+
+const iframe = document.createElement("iframe");
+iframe.src = "support/ufoo";
+
+let loadCount = 0;
+
+iframe.onload = () => {
+ ++loadCount;
+ const iDocument = iframe.contentDocument;
+
+ if (loadCount === 1) {
+ const div = iDocument.createElement("div");
+ assert_true('attachShadow' in div, 'attachShadow support');
+ const shadowRoot = div.attachShadow({ mode: "open" });
+ shadowRoot.innerHTML = `<meta http-equiv="refresh" content="1; url=foo">`;
+ iDocument.body.appendChild(div);
+
+ // Want to make sure no refreshes happen
+ step_timeout(done, 3000);
+ } else {
+ assert_unreached("Got more than 1 load event");
+ }
+};
+
+document.body.appendChild(iframe);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/parsing.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/parsing.html
new file mode 100644
index 0000000000..73ac4bcc00
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/parsing.html
@@ -0,0 +1,147 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="variant" content="?1-10">
+<meta name="variant" content="?11-20">
+<meta name="variant" content="?21-30">
+<meta name="variant" content="?31-40">
+<meta name="variant" content="?41-50">
+<meta name="variant" content="?51-60">
+<meta name="variant" content="?61-70">
+<meta name="variant" content="?71-80">
+<meta name="variant" content="?81-90">
+<meta name="variant" content="?91-100">
+<meta name="variant" content="?101-110">
+<meta name="variant" content="?111-120">
+<meta name="variant" content="?121-130">
+<meta name="variant" content="?131-last">
+<title>Parsing of meta refresh</title>
+<meta name="timeout" content="long">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/common/subset-tests.js></script>
+<style>
+iframe { display:none }
+</style>
+<body>
+<script>
+
+// failure to parse is []
+// success to parse is [time, url] where url is unresolved
+
+var tests_arr = [
+ {input: '', expected: []},
+ {input: '1', expected: [1, '__filename__']},
+ {input: '1 ', expected: [1, '__filename__']},
+ {input: '1\t', expected: [1, '__filename__']},
+ {input: '1\r', expected: [1, '__filename__']},
+ {input: '1\n', expected: [1, '__filename__']},
+ {input: '1\f', expected: [1, '__filename__']},
+ {input: '1;', expected: [1, '__filename__']},
+ {input: '1,', expected: [1, '__filename__']},
+ {input: '1; url=foo', expected: [1, 'foo']},
+ {input: '1, url=foo', expected: [1, 'foo']},
+ {input: '1 url=foo', expected: [1, 'foo']},
+ {input: '1;\turl=foo', expected: [1, 'foo']},
+ {input: '1,\turl=foo', expected: [1, 'foo']},
+ {input: '1\turl=foo', expected: [1, 'foo']},
+ {input: '1;\rurl=foo', expected: [1, 'foo']},
+ {input: '1,\rurl=foo', expected: [1, 'foo']},
+ {input: '1\rurl=foo', expected: [1, 'foo']},
+ {input: '1;\nurl=foo', expected: [1, 'foo']},
+ {input: '1,\nurl=foo', expected: [1, 'foo']},
+ {input: '1\nurl=foo', expected: [1, 'foo']},
+ {input: '1;\furl=foo', expected: [1, 'foo']},
+ {input: '1,\furl=foo', expected: [1, 'foo']},
+ {input: '1\furl=foo', expected: [1, 'foo']},
+ {input: '1url=foo', expected: []},
+ {input: '1x;url=foo', expected: []},
+ {input: '1 x;url=foo', expected: [1, 'x;url=foo']},
+ {input: '1;;url=foo', expected: [1, ';url=foo']},
+ {input: ' 1 ; url = foo', expected: [1, 'foo']},
+ {input: ' 1 , url = foo', expected: [1, 'foo']},
+ {input: ' 1 ; foo', expected: [1, 'foo']},
+ {input: ' 1 , foo', expected: [1, 'foo']},
+ {input: ' 1 url = foo', expected: [1, 'foo']},
+ {input: '1; url=foo ', expected: [1, 'foo']},
+ {input: '1; url=f\to\no', expected: [1, 'foo']},
+ {input: '1; url="foo"bar', expected: [1, 'foo']},
+ {input: '1; url=\'foo\'bar', expected: [1, 'foo']},
+ {input: '1; url="foo\'bar', expected: [1, 'foo\'bar']},
+ {input: '1; url foo', expected: [1, 'url foo']},
+ {input: '1; urlfoo', expected: [1, 'urlfoo']},
+ {input: '1; urfoo', expected: [1, 'urfoo']},
+ {input: '1; ufoo', expected: [1, 'ufoo']},
+ {input: '1; "foo"bar', expected: [1, 'foo']},
+ {input: '; foo', expected: []},
+ {input: ';foo', expected: []},
+ {input: ', foo', expected: []},
+ {input: ',foo', expected: []},
+ {input: 'foo', expected: []},
+ {input: '+1; url=foo', expected: []},
+ {input: '-1; url=foo', expected: []},
+ {input: '+0; url=foo', expected: []},
+ {input: '-0; url=foo', expected: []},
+ {input: '0; url=foo', expected: [0, 'foo']},
+ {input: '+1; foo', expected: []},
+ {input: '-1; foo', expected: []},
+ {input: '+0; foo', expected: []},
+ {input: '-0; foo', expected: []},
+ {input: '0; foo', expected: [0, 'foo']},
+ {input: '+1', expected: []},
+ {input: '-1', expected: []},
+ {input: '+0', expected: []},
+ {input: '-0', expected: []},
+ {input: '0', expected: [0, '__filename__']},
+ {input: '1.9; url=foo', expected: [1, 'foo']},
+ {input: '1.9..5.; url=foo', expected: [1, 'foo']},
+ {input: '.9; url=foo', expected: [0, 'foo']},
+ {input: '0.9; url=foo', expected: [0, 'foo']},
+ {input: '0...9; url=foo', expected: [0, 'foo']},
+ {input: '0...; url=foo', expected: [0, 'foo']},
+ {input: '1e0; url=foo', expected: []},
+ {input: '1e1; url=foo', expected: []},
+ {input: '10e-1; url=foo', expected: []},
+ {input: '-0.1; url=foo', expected: []},
+];
+
+tests_arr.forEach(function(test_obj) {
+ ["<meta>", "Refresh header"].forEach(type => {
+ if(type === "Refresh header" && test_obj.input.match("[\n\r\f]")) { // See https://github.com/web-platform-tests/wpt/issues/8372 for why \f as well
+ return;
+ }
+ const filename = type === "<meta>" ? "refresh.sub.html" : "refresh.py";
+ subsetTest(async_test, function(t) {
+ var iframe = document.createElement('iframe');
+ t.add_cleanup(function() {
+ document.body.removeChild(iframe);
+ });
+ iframe.src = "support/" + filename + "?input=" + encodeURIComponent(test_obj.input);
+ document.body.appendChild(iframe);
+ var loadCount = 0;
+ iframe.onload = t.step_func(function() {
+ loadCount++;
+ var got = iframe.contentDocument.body.textContent.trim();
+ if (test_obj.expected.length === 0) {
+ assert_equals(got, filename);
+ if (loadCount === 1) {
+ t.step_timeout(function() {
+ t.done();
+ }, 3000); // want to make sure it doesn't redirect when it shouldn't
+ } else {
+ assert_unreached('Got > 1 load events');
+ }
+ } else {
+ if (loadCount === 2) {
+ if(test_obj.expected[1] === "__filename__") {
+ assert_equals(got, filename);
+ } else {
+ assert_equals(got, test_obj.expected[1]);
+ }
+ t.done();
+ }
+ }
+ });
+ }, type + ": " + format_value(test_obj.input));
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/remove-from-document.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/remove-from-document.html
new file mode 100644
index 0000000000..1e608a3456
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/remove-from-document.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>A meta must refresh the original document even if it was removed.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-refresh">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ single_test: true });
+
+const sourceIFrame = document.createElement("iframe");
+let sourceLoadCount = 0;
+
+sourceIFrame.onload = () => {
+ ++sourceLoadCount;
+
+ if (sourceLoadCount === 2) {
+ assert_equals(sourceIFrame.contentDocument.body.textContent.trim(), "foo");
+ done();
+ }
+
+ maybeStartTest();
+};
+
+function maybeStartTest() {
+ if (sourceLoadCount === 1) {
+ sourceIFrame.contentDocument.querySelector("meta").remove();
+ }
+}
+
+sourceIFrame.src = "support/refresh.sub.html?input=" + encodeURIComponent("1; url=foo");
+
+document.body.appendChild(sourceIFrame);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/;url=foo b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/;url=foo
new file mode 100644
index 0000000000..622ff110d3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/;url=foo
@@ -0,0 +1 @@
+;url=foo
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/__dir__.headers b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/__dir__.headers
new file mode 100644
index 0000000000..156209f9c8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/__dir__.headers
@@ -0,0 +1 @@
+Content-Type: text/html
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/foo b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/foo
new file mode 100644
index 0000000000..257cc5642c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/foo
@@ -0,0 +1 @@
+foo
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/foo'bar b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/foo'bar
new file mode 100644
index 0000000000..80e7410879
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/foo'bar
@@ -0,0 +1 @@
+foo'bar
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/refresh.py b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/refresh.py
new file mode 100644
index 0000000000..797c7b9412
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/refresh.py
@@ -0,0 +1,4 @@
+def main(request, response):
+ response.headers.set(b"Content-Type", b"text/html")
+ response.headers.set(b"Refresh", request.GET.first(b"input"))
+ response.content = u"<!doctype html>refresh.py\n"
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/refresh.sub.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/refresh.sub.html
new file mode 100644
index 0000000000..bc97f29c62
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/refresh.sub.html
@@ -0,0 +1 @@
+<!doctype html><meta http-equiv=refresh content="{{GET[input]}}">refresh.sub.html
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/ufoo b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/ufoo
new file mode 100644
index 0000000000..8fff3cf4fb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/ufoo
@@ -0,0 +1 @@
+ufoo
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/urfoo b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/urfoo
new file mode 100644
index 0000000000..7d7373f4b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/urfoo
@@ -0,0 +1 @@
+urfoo
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/url foo b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/url foo
new file mode 100644
index 0000000000..a1e6a92290
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/url foo
@@ -0,0 +1 @@
+url foo
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/urlfoo b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/urlfoo
new file mode 100644
index 0000000000..3e67b2f7ca
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/urlfoo
@@ -0,0 +1 @@
+urlfoo
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/x;url=foo b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/x;url=foo
new file mode 100644
index 0000000000..f10371aa7b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/support/x;url=foo
@@ -0,0 +1 @@
+x;url=foo
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-lower.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-lower.html
new file mode 100644
index 0000000000..026e61c2ca
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-lower.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta http-equiv="content-security-policy" content="script-src 'self'">
+<script>inline = true;</script>
+<script src="http-equiv-enumerated-ascii-case-insensitive-message.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-message.js b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-message.js
new file mode 100644
index 0000000000..1dc218a0a5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-message.js
@@ -0,0 +1 @@
+parent.postMessage(null, "*");
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-mixed.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-mixed.html
new file mode 100644
index 0000000000..b4c547d342
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-mixed.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta http-equiv="CoNtEnT-sEcUrItY-pOlIcY" content="script-src 'self'">
+<script>inline = true;</script>
+<script src="http-equiv-enumerated-ascii-case-insensitive-message.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-other.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-other.html
new file mode 100644
index 0000000000..5c89a5e8bc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive-other.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta http-equiv="content-ſecurity-policy" content="script-src 'self'">
+<script>inline = true;</script>
+<script src="http-equiv-enumerated-ascii-case-insensitive-message.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive.html
new file mode 100644
index 0000000000..6d19be4149
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/pragma-directives/http-equiv-enumerated-ascii-case-insensitive.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/#attr-meta-http-equiv">
+<link rel="help" href="https://html.spec.whatwg.org/#enumerated-attribute">
+<meta name="assert" content="meta@http-equiv values are ASCII case-insensitive">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function() {
+ let loaded = 0;
+
+ // 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(event => {
+ if (++loaded == 3) {
+ const iframe = document.querySelectorAll("iframe");
+
+ assert_equals(iframe[0].contentWindow.inline,
+ undefined, "lowercase valid");
+ assert_equals(iframe[1].contentWindow.inline,
+ undefined, "mixed case valid");
+ assert_equals(iframe[2].contentWindow.inline,
+ true, "non-ASCII invalid");
+
+ this.done();
+ }
+ }));
+}, "keyword content-security-policy");
+</script>
+<iframe src="http-equiv-enumerated-ascii-case-insensitive-lower.html"></iframe>
+<iframe src="http-equiv-enumerated-ascii-case-insensitive-mixed.html"></iframe>
+<iframe src="http-equiv-enumerated-ascii-case-insensitive-other.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/the-lang-attribute-012.html b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/the-lang-attribute-012.html
new file mode 100644
index 0000000000..af872d6e3a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-meta-element/the-lang-attribute-012.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html >
+<head>
+<meta charset="utf-8"/>
+ <meta http-equiv="Content-Language" content="ko,zh,ja" >
+<title>Multiple languages in Content-Language meta element</title>
+<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
+<link rel='help' href='https://html.spec.whatwg.org/multipage/#pragma-directives'>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<meta name='flags' content='dom'>
+<style type='text/css'>
+ #colonlangcontroltest { color: red; font-weight: bold; width: 400px; }
+ #colonlangcontroltest:lang(xx) { display:none; }
+.test div { width: 50px; }
+
+#box:lang(ko) { width: 100px; }
+#box:lang(zh) { width: 100px; }
+#box:lang(ja) { width: 100px; }
+
+ /* styling for debugging related notes */
+ .notes span:lang(ko) { background-color: #0000FF; color: white; padding: 0 5px; }
+ .notes span:lang(zh) { background-color: #0000FF; color: white; padding: 0 5px; }
+ .notes span:lang(ja) { background-color: #0000FF; color: white; padding: 0 5px; }
+
+</style>
+</head>
+<body>
+
+
+
+<div class="test"><div id="box">&#xA0;</div></div>
+<p lang='xx' id='colonlangcontroltest'>This test failed because it relies on :lang for results, but :lang is not supported by this browser.</p>
+
+
+<!--Notes:
+
+This test uses :lang to detect whether the language has been set. If :lang is not supported, a message will appear and the test will fail.
+
+-->
+<script>
+test(function() {
+assert_equals(document.getElementById('colonlangcontroltest').offsetWidth, 0)
+assert_equals(document.getElementById('box').offsetWidth, 50);
+}, "The UA will not recognize a language declaration in the Content-Language meta element when more than one language is declared.");
+</script>
+
+<div id='log'></div>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/historical.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/historical.html
new file mode 100644
index 0000000000..d475f5b3c8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/historical.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Historical style element features should not be supported</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+function t(property) {
+ test(function() {
+ assert_false(property in document.createElement('style'));
+ }, 'style.' + property + ' should not be supported');
+}
+// added in https://github.com/whatwg/html/commit/29cf39d2163cfc85b67409f4e10390619ffb2b40
+// removed in https://github.com/whatwg/html/commit/c2a3b2a2e3db49c14b486a5e99acf7d10cfe8443
+t('scoped');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/html_style_in_comment-ref.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/html_style_in_comment-ref.html
new file mode 100644
index 0000000000..999383c769
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/html_style_in_comment-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>[style] Reference file</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<style>
+ h4 {
+ color: green;
+ }
+</style>
+<body>
+ <p>
+ This page tests that Style written inside HTML comment is not applied
+ </p>
+ This test passes if the text below is <b>Green. NOT Red.</b>
+ <h4>
+ This is some text.
+ </h4>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/html_style_in_comment.xhtml b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/html_style_in_comment.xhtml
new file mode 100644
index 0000000000..839548f01c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/html_style_in_comment.xhtml
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<link rel="match" href="html_style_in_comment-ref.html"/>
+<style type="text/css">
+h4 {color: green}
+<!--
+h4 {color: red}
+-->
+</style>
+</head>
+<body>
+<p> This page tests that Style written inside HTML comment is not applied</p>
+This test passes if the text below is <b>Green. NOT Red.</b>
+<h4>
+This is some text.
+</h4>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/mutations.window.js b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/mutations.window.js
new file mode 100644
index 0000000000..1c93b40394
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/mutations.window.js
@@ -0,0 +1,48 @@
+test(t => {
+ const style = document.body.appendChild(document.createElement("style"));
+ const sheet = style.sheet;
+ t.add_cleanup(() => style.remove());
+ assert_not_equals(sheet, null);
+ style.appendChild(new Comment());
+ assert_not_equals(sheet, style.sheet);
+}, "Mutating the style element: inserting a Comment node");
+
+test(t => {
+ const style = document.body.appendChild(document.createElement("style"));
+ t.add_cleanup(() => style.remove());
+ const comment = style.appendChild(new Comment());
+ const sheet = style.sheet;
+ comment.appendData("x");
+ assert_not_equals(sheet, style.sheet);
+}, "Mutating the style element: mutating a Comment node");
+
+test(t => {
+ const style = document.body.appendChild(document.createElement("style"));
+ t.add_cleanup(() => style.remove());
+ const text1 = style.appendChild(new Text("1"));
+ const text2 = style.appendChild(new Text("2"));
+ assert_equals(style.textContent, "12");
+ assert_equals(style.childNodes.length, 2);
+ const sheet = style.sheet;
+ style.normalize();
+ assert_equals(style.childNodes.length, 1);
+ assert_not_equals(sheet, style.sheet);
+}, "Mutating the style element: using normalize()");
+
+test(t => {
+ const style = document.body.appendChild(document.createElement("style"));
+ t.add_cleanup(() => style.remove());
+ const comment = style.appendChild(new Comment());
+ const sheet = style.sheet;
+ comment.remove();
+ assert_not_equals(sheet, style.sheet);
+}, "Mutating the style element: removing a Comment node");
+
+test(t => {
+ const style = document.body.appendChild(document.createElement("style"));
+ const sheet = style.sheet;
+ t.add_cleanup(() => style.remove());
+ assert_not_equals(sheet, null);
+ style.appendChild(new DocumentFragment());
+ assert_equals(sheet, style.sheet);
+}, "Mutating the style element: inserting an empty DocumentFragment node");
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style-error-01.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style-error-01.html
new file mode 100644
index 0000000000..0bdef0e175
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style-error-01.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>style: error events</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-style-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="test">
+<script>
+//var t404 = async_test("Should get an error event for a 404 error.")
+//t404.step(function() {
+// var elt = document.createElement("style");
+// elt.onerror = t404.step_func(function() {
+// assert_true(true, "Got error event for 404 error.")
+// t404.done()
+// })
+// elt.appendChild(
+// document.createTextNode('@import 404 error;'));
+// document.getElementsByTagName("head")[0].appendChild(elt);
+//})
+var tText = async_test("Should get an error event for a text/plain response.")
+tText.step(function() {
+ var elt = document.createElement("style");
+ elt.onerror = tText.step_func(function() {
+ assert_true(true, "Got error event for 404 error.")
+ tText.done()
+ })
+ elt.appendChild(
+ document.createTextNode('@import "support/css-red.txt";'));
+ document.getElementsByTagName("head")[0].appendChild(elt);
+})
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style-load-after-mutate.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style-load-after-mutate.html
new file mode 100644
index 0000000000..901c5c1ac2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style-load-after-mutate.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>The 'load' event on the style element should still fire after mutation</title>
+<link rel="help" href="https://crbug.com/1323319">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(async () => {
+ const style = document.createElement('style');
+ document.head.appendChild(style);
+ style.appendChild(document.createTextNode('@import url(/support/css-red.txt);'));
+ style.appendChild(document.createTextNode('body {color: green; }'));
+
+ // The 'load' event should fire.
+ await new Promise(resolve => style.onload = resolve);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_disabled.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_disabled.html
new file mode 100644
index 0000000000..1a88bf1305
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_disabled.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>HTML Test: The style should not be applied if it is disabled</title>
+ <link rel="author" title="Intel" href="http://www.intel.com/">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-style-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style>
+ #test {
+ width: 100px;
+ }
+ </style>
+ <style id="style">
+ #test {
+ width: 50px;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="log"></div>
+ <div id="test"></div>
+ <script>
+ test(function() {
+ var testElement = document.getElementById("test");
+ var style = document.getElementById("style");
+ var width1, width2;
+
+ width1 = window.getComputedStyle(testElement)["width"];
+ assert_equals(width1, "50px", "The style should be applied.");
+
+ style.disabled = true;
+ width2 = window.getComputedStyle(testElement)["width"];
+ assert_equals(width2, "100px", "The style should not be applied.");
+ }, "The style is not applied when it is disabled");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_events.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_events.html
new file mode 100644
index 0000000000..5e07e50882
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_events.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>HTML Test: The style events</title>
+ <link rel="author" title="Intel" href="http://www.intel.com/">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-style-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ var tLoad = async_test("If the style is loaded successfully, the 'load' event must be fired");
+ var tError = async_test("If the style is loaded unsuccessfully, the 'error' event must be fired");
+
+ function onstyleload(e) {
+ tLoad.done();
+ }
+
+ function onstyleerror(e) {
+ tError.done();
+ }
+ </script>
+ <style onload="onstyleload()">
+ #test {
+ height: 100px;
+ width: 100px;
+ }
+ </style>
+ <style onerror="onstyleerror()">
+ @import url(nonexistent.css);
+ </style>
+ </head>
+ <body>
+ <div id="log"></div>
+ <div id="test"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_load_async.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_load_async.html
new file mode 100644
index 0000000000..ef8ac89c46
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_load_async.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+<meta charset="utf-8">
+<title>Style load event should be async</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ var t = async_test("style load should be async");
+ var sync = true;
+ function check() {
+ assert_false(sync);
+ t.done();
+ }
+</script>
+<style onload="t.step(check)">
+</style>
+<script>
+ sync = false
+</script>
+
+<body>
+ <div id="log"></div>
+ <div id="test"></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_load_event.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_load_event.html
new file mode 100644
index 0000000000..d852661791
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_load_event.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>HTML Test: The style load event should fire when textContent is changed</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#update-a-style-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+ var loadCount = 0;
+ function load() { loadCount++; }
+</script>
+
+<style id=target onload="load()">
+ .box { color:red; }
+</style>
+<div class='box'>Box</div>
+
+<script>
+window.onload = () => {
+ const target = document.getElementById('target');
+ promise_test(async t => {
+ assert_equals(loadCount,1,"Style element should have loaded once by now");
+ target.textContent = `.box { color: green; }`;
+ await new Promise(resolve => target.addEventListener('load', resolve));
+ assert_equals(loadCount,2,"Style element should fire the load event when textContent changes");
+ },"style load event should fire when textContent changed");
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_media.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_media.html
new file mode 100644
index 0000000000..04bcbc53ef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_media.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>HTML Test: The style information must be applied to the environment specified by the media attribute</title>
+ <link rel="author" title="Intel" href="http://www.intel.com/">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-style-media">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-style-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style>
+ #test {
+ width: 100px;
+ }
+ </style>
+ <style id="style">
+ #test {
+ width: 50px;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="log"></div>
+ <div id="test"></div>
+ <script>
+ test(function() {
+ var testElement = document.getElementById("test");
+ var style = document.getElementById("style");
+ var width1, width2;
+
+ width1 = window.getComputedStyle(testElement)["width"];
+ assert_equals(width1, "50px", "The style should be applied.");
+
+ style.media = "print";
+ width2 = window.getComputedStyle(testElement)["width"];
+ assert_equals(width2, "100px", "The style should not be applied.");
+ }, "The style information must be applied to the environment specified by the media attribute");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_media_change.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_media_change.html
new file mode 100644
index 0000000000..8b7e8440d5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_media_change.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Dynamically changing HTMLStyleElement.media should change the rendering accordingly</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-style-element">
+ <style>
+ span {
+ color: red;
+ }
+ </style>
+ <style id="text-style" media="none">
+ span {
+ color: green;
+ }
+ </style>
+ <style id="body-style" media="aural">
+ body {
+ color: green;
+ }
+ </style>
+ </head>
+ <body>
+ <span>text</span>
+ <script>
+ test(function() {
+ var element = document.querySelector("span");
+ assert_equals(getComputedStyle(element).color, "rgb(255, 0, 0)");
+ document.getElementById("text-style").media = 'all';
+ assert_equals(getComputedStyle(element).color, "rgb(0, 128, 0)");
+ }, "change media value dynamically");
+
+ test(function() {
+ var style = document.getElementById("body-style");
+ assert_not_equals(getComputedStyle(document.querySelector("body")).color, "rgb(0, 128, 0)");
+ style.removeAttribute("media");
+ assert_equals(getComputedStyle(document.querySelector("body")).color, "rgb(0, 128, 0)");
+ }, "removing media attribute");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_non_matching_media.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_non_matching_media.html
new file mode 100644
index 0000000000..74d3554b13
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_non_matching_media.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>HTML Test: Non-matching media type should have stylesheet</title>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-style-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style media="unknown">
+ body { color: green }
+ </style>
+ </head>
+ <body>
+ <script>
+ test(function() {
+ assert_equals(document.styleSheets.length, 1);
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_type_change.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_type_change.html
new file mode 100644
index 0000000000..a19b3c86d7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_type_change.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Dynamically changing HTMLStyleElement.type should change the rendering accordingly</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-style-element">
+ <style type="no/mime">
+ body { color: green }
+ </style>
+ </head>
+ <body>
+ Text content.
+ <script>
+ var style = document.querySelector("style");
+ test(function() {
+ assert_equals(document.styleSheets.length, 0);
+ }, "Check initial styleSheets length type");
+
+ test(function() {
+ assert_not_equals(getComputedStyle(document.querySelector("body")).color, "rgb(0, 128, 0)");
+ assert_equals(document.styleSheets.length, 0);
+ style.type = "text/css";
+ assert_equals(getComputedStyle(document.querySelector("body")).color, "rgb(0, 128, 0)");
+ assert_equals(document.styleSheets.length, 1);
+ }, "Change type from invalid type to valid type");
+
+ test(function() {
+ assert_equals(getComputedStyle(document.querySelector("body")).color, "rgb(0, 128, 0)");
+ assert_equals(document.styleSheets.length, 1);
+ style.type = "no/mime";
+ assert_not_equals(getComputedStyle(document.querySelector("body")).color, "rgb(0, 128, 0)");
+ assert_equals(document.styleSheets.length, 0);
+ }, "Change type from valid type to invalid type");
+
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_type_html.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_type_html.html
new file mode 100644
index 0000000000..cc48868bd7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_type_html.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>&lt;style> type="" edge cases</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#update-a-style-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+#test1 { color: rgb(0, 128, 0); }
+</style>
+
+<style type="">
+#test2 { color: rgb(0, 128, 0); }
+</style>
+
+<style type="TEXT/CsS">
+#test3 { color: rgb(0, 128, 0); }
+</style>
+
+<style type=" text/css ">
+#test4 { color: rgb(0, 128, 0); }
+</style>
+
+<style type="text/css; charset=utf-8">
+#test5 { color: rgb(0, 128, 0); }
+</style>
+
+<body>
+
+<div id="test1"></div>
+<div id="test2"></div>
+<div id="test3"></div>
+<div id="test4"></div>
+<div id="test5"></div>
+
+<script>
+"use strict";
+
+test(() => {
+ assertApplied("test1");
+}, "With no type attribute, the style should apply");
+
+test(() => {
+ assertApplied("test2");
+}, "With an empty type attribute, the style should apply");
+
+test(() => {
+ assertApplied("test3");
+}, "With a mixed-case type attribute, the style should apply");
+
+test(() => {
+ assertNotApplied("test4");
+}, "With a whitespace-surrounded type attribute, the style should not apply");
+
+test(() => {
+ assertNotApplied("test5");
+}, "With a charset parameter in the type attribute, the style should not apply");
+
+function getColor(id) {
+ return window.getComputedStyle(document.getElementById(id)).color;
+}
+
+function assertApplied(id) {
+ assert_equals(getColor(id), "rgb(0, 128, 0)");
+}
+
+function assertNotApplied(id) {
+ assert_not_equals(getColor(id), "rgb(0, 128, 0)");
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_type_svg.svg b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_type_svg.svg
new file mode 100644
index 0000000000..6b0d1e874e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/style_type_svg.svg
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml"
+ width="800px" height="8000px">
+ <title>&lt;style&gt; type="" edge cases</title>
+ <metadata>
+ <h:link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#update-a-style-block"/>
+ </metadata>
+ <h:script src="/resources/testharness.js"/>
+ <h:script src="/resources/testharnessreport.js"/>
+
+ <style>
+ #test1 { color: rgb(0, 128, 0); }
+ </style>
+
+ <style type="">
+ #test2 { color: rgb(0, 128, 0); }
+ </style>
+
+ <style type="TEXT/CsS">
+ #test3 { color: rgb(0, 128, 0); }
+ </style>
+
+ <style type=" text/css ">
+ #test4 { color: rgb(0, 128, 0); }
+ </style>
+
+ <style type="text/css; charset=utf-8">
+ #test5 { color: rgb(0, 128, 0); }
+ </style>
+
+ <h:body>
+ <h:div id="test1"/>
+ <h:div id="test2"/>
+ <h:div id="test3"/>
+ <h:div id="test4"/>
+ <h:div id="test5"/>
+
+ <h:script><![CDATA[
+ "use strict";
+
+ test(() => {
+ assertApplied("test1");
+ }, "With no type attribute, the style should apply");
+
+ test(() => {
+ assertApplied("test2");
+ }, "With an empty type attribute, the style should apply");
+
+ test(() => {
+ assertApplied("test3");
+ }, "With a mixed-case type attribute, the style should apply");
+
+ test(() => {
+ assertNotApplied("test4");
+ }, "With a whitespace-surrounded type attribute, the style should not apply");
+
+ test(() => {
+ assertNotApplied("test5");
+ }, "With a charset parameter in the type attribute, the style should not apply");
+
+ function getColor(id) {
+ return window.getComputedStyle(document.getElementById(id)).color;
+ }
+
+ function assertApplied(id) {
+ assert_equals(getColor(id), "rgb(0, 128, 0)");
+ }
+
+ function assertNotApplied(id) {
+ assert_not_equals(getColor(id), "rgb(0, 128, 0)");
+ }
+ ]]></h:script>
+ </h:body>
+</svg>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/support/css-red.txt b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/support/css-red.txt
new file mode 100644
index 0000000000..9ef04cbd12
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/support/css-red.txt
@@ -0,0 +1 @@
+html { color: red; }
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/update-style-block-ascii-case-insensitive-ref.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/update-style-block-ascii-case-insensitive-ref.html
new file mode 100644
index 0000000000..5ac2432547
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/update-style-block-ascii-case-insensitive-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+p:after { font-weight: bold; }
+p:after { content: "PASS"; color: green; }
+</style>
+<p>text/css treated as CSS?
+<p>TeXt/CsS treated as CSS?
+<p>text/cſs ignored?
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/update-style-block-ascii-case-insensitive.html b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/update-style-block-ascii-case-insensitive.html
new file mode 100644
index 0000000000..3c5cd152d6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-style-element/update-style-block-ascii-case-insensitive.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/#update-a-style-block">
+<link rel="match" href="update-style-block-ascii-case-insensitive-ref.html">
+<meta name="assert" content="style@type values are ASCII case-insensitive">
+<style>
+p:after { font-weight: bold; }
+p:after { content: "FAIL"; color: red; }
+#c:after { content: "PASS"; color: green; }
+</style>
+<style type="text/css">#a:after { content: "PASS"; color: green; }</style>
+<style type="TeXt/CsS">#b:after { content: "PASS"; color: green; }</style>
+<style type="text/cſs">#c:after { content: "FAIL"; color: red; }</style>
+<p id="a">text/css treated as CSS?
+<p id="b">TeXt/CsS treated as CSS?
+<p id="c">text/cſs ignored?
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-01.html b/testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-01.html
new file mode 100644
index 0000000000..7f25400ea8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-01.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>title.text with comment and element children.</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-title-text">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+try {
+ var title = document.getElementsByTagName("title")[0];
+ while (title.childNodes.length)
+ title.removeChild(title.childNodes[0]);
+ title.appendChild(document.createComment("COMMENT"));
+ title.appendChild(document.createTextNode("TEXT"));
+ title.appendChild(document.createElement("a"))
+ .appendChild(document.createTextNode("ELEMENT"))
+} catch (e) {
+}
+</script>
+<script>
+test(function() {
+ assert_equals(title.text, "TEXT");
+ assert_equals(title.textContent, "TEXTELEMENT");
+})
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-02.xhtml b/testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-02.xhtml
new file mode 100644
index 0000000000..068b105046
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-02.xhtml
@@ -0,0 +1,30 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>title.text with comment and element children.</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"/>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-title-text"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+try {
+ var title = document.getElementsByTagName("title")[0];
+ while (title.childNodes.length)
+ title.removeChild(title.childNodes[0]);
+ title.appendChild(document.createComment("COMMENT"));
+ title.appendChild(document.createTextNode("TEXT"));
+ title.appendChild(document.createElement("a"))
+ .appendChild(document.createTextNode("ELEMENT"))
+} catch (e) {
+}
+</script>
+<script>
+test(function() {
+ assert_equals(title.text, "TEXT");
+ assert_equals(title.textContent, "TEXTELEMENT");
+})
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-03.html b/testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-03.html
new file mode 100644
index 0000000000..1c119a825c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-03.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title> title.text and space normalization </title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-title-text">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_equals(document.getElementsByTagName("title")[0].text,
+ " title.text and space normalization ");
+ assert_equals(document.getElementsByTagName("title")[0].textContent,
+ " title.text and space normalization ");
+ assert_equals(document.getElementsByTagName("title")[0].firstChild.nodeValue,
+ " title.text and space normalization ");
+}, "title.text and space normalization (markup)");
+[
+ "one space", "two spaces",
+ "one\ttab", "two\t\ttabs",
+ "one\nnewline", "two\n\nnewlines",
+ "one\fform feed", "two\f\fform feeds",
+ "one\rcarriage return", "two\r\rcarriage returns"
+].forEach(function(str) {
+ test(function() {
+ document.title = str;
+ var title = document.getElementsByTagName("title")[0];
+ assert_equals(title.text, str);
+ assert_equals(title.textContent, str);
+ assert_equals(title.firstChild.nodeValue, str);
+ }, "title.text and space normalization: " + format_value(str))
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-04.xhtml b/testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-04.xhtml
new file mode 100644
index 0000000000..de382ab4d5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/document-metadata/the-title-element/title.text-04.xhtml
@@ -0,0 +1,37 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title> title.text and space normalization </title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"/>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-title-text"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_equals(document.getElementsByTagName("title")[0].text,
+ " title.text and space normalization ");
+ assert_equals(document.getElementsByTagName("title")[0].textContent,
+ " title.text and space normalization ");
+ assert_equals(document.getElementsByTagName("title")[0].firstChild.nodeValue,
+ " title.text and space normalization ");
+}, "title.text and space normalization (markup)");
+[
+ "one space", "two spaces",
+ "one\ttab", "two\t\ttabs",
+ "one\nnewline", "two\n\nnewlines",
+ "one\fform feed", "two\f\fform feeds",
+ "one\rcarriage return", "two\r\rcarriage returns"
+].forEach(function(str) {
+ test(function() {
+ document.title = str;
+ var title = document.getElementsByTagName("title")[0];
+ assert_equals(title.text, str);
+ assert_equals(title.textContent, str);
+ assert_equals(title.firstChild.nodeValue, str);
+ }, "title.text and space normalization: " + format_value(str))
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/edits/the-del-element/del_effect.html b/testing/web-platform/tests/html/semantics/edits/the-del-element/del_effect.html
new file mode 100644
index 0000000000..14297e5293
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/edits/the-del-element/del_effect.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset=UTF-8>
+<title>HTML Test: Text in the del element should be 'line-through'</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-del-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<p><del>crossed-off text</del></p>
+<div id="log"></div>
+
+<script>
+ test(function() {
+ var element = document.getElementsByTagName('del')[0],
+ textDecoration = getComputedStyle(element).textDecorationLine ||
+ getComputedStyle(element).textDecoration;
+ assert_equals(textDecoration, 'line-through');
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/edits/the-ins-element/ins_effect.html b/testing/web-platform/tests/html/semantics/edits/the-ins-element/ins_effect.html
new file mode 100644
index 0000000000..6e1b344596
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/edits/the-ins-element/ins_effect.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset=UTF-8>
+<title>HTML Test: Text in the ins element should be 'underline'</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ins-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<p><ins>underlined text</ins></p>
+<div id="log"></div>
+
+<script>
+ test(function() {
+ var element = document.getElementsByTagName('ins')[0],
+ textDecoration = getComputedStyle(element).textDecorationLine ||
+ getComputedStyle(element).textDecoration;
+ assert_equals(textDecoration, 'underline');
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/META.yml b/testing/web-platform/tests/html/semantics/embedded-content/META.yml
new file mode 100644
index 0000000000..c1dd8dddf9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/META.yml
@@ -0,0 +1,2 @@
+suggested_reviewers:
+ - foolip
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-html.html b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-html.html
new file mode 100644
index 0000000000..0808538337
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-html.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<link rel="help" href="http://crbug.com/1325192">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/test-only-api.js"></script>
+
+<script type="module">
+import * as common from "./resources/common.js";
+common.runBfcacheTestForEmbeds({"type": "text/html", "src": "/resources/blank.html"});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-img.html b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-img.html
new file mode 100644
index 0000000000..7e9d713c0a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-img.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<link rel="help" href="http://crbug.com/1325192">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/test-only-api.js"></script>
+
+<script type="module">
+import * as common from "./resources/common.js";
+common.runBfcacheTestForEmbeds({'type': 'image/png', 'src': '/images/blue.png'});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-js.html b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-js.html
new file mode 100644
index 0000000000..c3b027563d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-js.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<link rel="help" href="http://crbug.com/1325192">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/test-only-api.js"></script>
+
+<script type="module">
+import * as common from "./resources/common.js";
+common.runBfcacheTestForEmbeds(
+ {'type': 'application/javascript', 'src': '/resources/test-only-api.js'});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-mp4.html b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-mp4.html
new file mode 100644
index 0000000000..fde560e5be
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-mp4.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<link rel="help" href="http://crbug.com/1325192">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/test-only-api.js"></script>
+
+<script type="module">
+import * as common from "./resources/common.js";
+common.runBfcacheTestForEmbeds({'src': '/media/white.mp4'});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-not-found.html b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-not-found.html
new file mode 100644
index 0000000000..0b56b5eadc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-not-found.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<link rel="help" href="http://crbug.com/1325192">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/test-only-api.js"></script>
+
+<script type="module">
+import * as common from "./resources/common.js";
+common.runBfcacheTestForEmbeds({'type': 'image/png', 'src': '/404.png'});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-type-only.html b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-type-only.html
new file mode 100644
index 0000000000..90c9d3311c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/embedded-type-only.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<link rel="help" href="http://crbug.com/1325192">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/test-only-api.js"></script>
+
+<script type="module">
+import * as common from "./resources/common.js";
+common.runBfcacheTestForEmbeds({'type': 'text/html'});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/bfcache/resources/common.js b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/resources/common.js
new file mode 100644
index 0000000000..5bb9642a83
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/bfcache/resources/common.js
@@ -0,0 +1,46 @@
+'use strict';
+
+async function loadBfCacheTestHelperResources() {
+ await loadScript('/common/utils.js');
+ await loadScript('/common/dispatcher/dispatcher.js');
+ await loadScript(
+ '/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js');
+}
+await loadBfCacheTestHelperResources();
+
+// Runs BFCache tests for embed elements, specifically <embed> and <object>.
+// 1. Attaches the target element to first page.
+// 2. Navigates away, then back via bfcache if this case is supported by the
+// browser.
+// @param {Object} testCase - The target element's attributes to test with.
+export function runBfcacheTestForEmbeds(testCase) {
+ assert_implements(runBfcacheTest, '`runBfcacheTest()` is unavailable.');
+ assert_implements(originSameOrigin, '`originSameOrigin` is unavailable.');
+
+ const tags = [
+ {'name': 'embed', 'srcAttr': 'src'},
+ {'name': 'object', 'srcAttr': 'data'},
+ ];
+ for (const tag of tags) {
+ runBfcacheTest(
+ {
+ targetOrigin: originSameOrigin,
+ shouldBeCached: true,
+ funcBeforeNavigation: (tag, attrs) => {
+ let e = document.createElement(tag.name);
+ // Only sets defined attributes to match the intended test behavior
+ // like embedded-type-only.html test.
+ if ('type' in attrs) {
+ e.type = attrs.type;
+ }
+ if ('src' in attrs) {
+ e[tag.srcAttr] = attrs.src;
+ }
+ document.body.append(e);
+ },
+ argsBeforeNavigation: [tag, testCase]
+ },
+ `Page with <${tag.name} ` +
+ `type=${testCase.type} ${tag.srcAttr}=${testCase.src}>`);
+ }
+}
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference-test-data.html b/testing/web-platform/tests/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference-test-data.html
new file mode 100644
index 0000000000..1fb71431f7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference-test-data.html
@@ -0,0 +1,254 @@
+<!DOCTYPE {{GET[doctype]}}>
+<!-- This file should be polyglot -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf-8"/>
+ <title>Test data for hash name reference</title>
+ <style>
+ body { margin: 0 }
+ img, object { height: 1px; display:block }
+ </style>
+ </head>
+ <body>
+
+<div data-expect="no match">
+ <img src="/images/threecolors.png" usemap="no-hash-name"/>
+ <object data="/images/threecolors.png" usemap="no-hash-name"></object>
+ <map name="no-hash-name">
+ <area shape="rect" coords="0,0,99,50" href="#area-no-hash-name"/>
+ </map>
+</div>
+
+<div data-expect="no match">
+ <img src="/images/threecolors.png" usemap="no-hash-id"/>
+ <object data="/images/threecolors.png" usemap="no-hash-id"></object>
+ <map id="no-hash-id">
+ <area shape="rect" coords="0,0,99,50" href="#area-no-hash-id"/>
+ </map>
+</div>
+
+<div data-expect="area-hash-name">
+ <img src="/images/threecolors.png" usemap="#hash-name"/>
+ <object data="/images/threecolors.png" usemap="#hash-name"></object>
+ <map name="hash-name">
+ <area shape="rect" coords="0,0,99,50" href="#area-hash-name"/>
+ </map>
+</div>
+
+<div data-expect="area-hash-id">
+ <img src="/images/threecolors.png" usemap="#hash-id"/>
+ <object data="/images/threecolors.png" usemap="#hash-id"></object>
+ <map id="hash-id">
+ <area shape="rect" coords="0,0,99,50" href="#area-hash-id"/>
+ </map>
+</div>
+
+<div data-expect="area-non-map-with-this-name">
+ <img src="/images/threecolors.png" usemap="#non-map-with-this-name" name="non-map-with-this-name"/>
+ <object data="/images/threecolors.png" usemap="#non-map-with-this-name"></object>
+ <map name="non-map-with-this-name">
+ <area shape="rect" coords="0,0,99,50" href="#area-non-map-with-this-name"/>
+ </map>
+</div>
+
+<div data-expect="area-non-map-with-this-id">
+ <img src="/images/threecolors.png" usemap="#non-map-with-this-id" id="non-map-with-this-id"/>
+ <object data="/images/threecolors.png" usemap="#non-map-with-this-id"></object>
+ <map id="non-map-with-this-id">
+ <area shape="rect" coords="0,0,99,50" href="#area-non-map-with-this-id"/>
+ </map>
+</div>
+
+<div data-expect="area-two-maps-with-this-name-1">
+ <img src="/images/threecolors.png" usemap="#two-maps-with-this-name"/>
+ <object data="/images/threecolors.png" usemap="#two-maps-with-this-name"></object>
+ <map name="two-maps-with-this-name">
+ <area shape="rect" coords="0,0,99,50" href="#area-two-maps-with-this-name-1"/>
+ </map>
+ <map name="two-maps-with-this-name">
+ <area shape="rect" coords="0,0,99,50" href="#area-two-maps-with-this-name-2"/>
+ </map>
+</div>
+
+<div data-expect="area-two-maps-with-this-id-1">
+ <img src="/images/threecolors.png" usemap="#two-maps-with-this-id"/>
+ <object data="/images/threecolors.png" usemap="#two-maps-with-this-id"></object>
+ <map id="two-maps-with-this-id">
+ <area shape="rect" coords="0,0,99,50" href="#area-two-maps-with-this-id-1"/>
+ </map>
+ <map id="two-maps-with-this-id">
+ <area shape="rect" coords="0,0,99,50" href="#area-two-maps-with-this-id-2"/>
+ </map>
+</div>
+
+<div data-expect="area-two-maps-with-this-name-or-id-1">
+ <img src="/images/threecolors.png" usemap="#two-maps-with-this-name-or-id"/>
+ <object data="/images/threecolors.png" usemap="#two-maps-with-this-name-or-id"></object>
+ <map name="two-maps-with-this-name-or-id">
+ <area shape="rect" coords="0,0,99,50" href="#area-two-maps-with-this-name-or-id-1"/>
+ </map>
+ <map id="two-maps-with-this-name-or-id">
+ <area shape="rect" coords="0,0,99,50" href="#area-two-maps-with-this-name-or-id-2"/>
+ </map>
+</div>
+
+<div data-expect="area-two-maps-with-this-id-or-name-1">
+ <img src="/images/threecolors.png" usemap="#two-maps-with-this-id-or-name"/>
+ <object data="/images/threecolors.png" usemap="#two-maps-with-this-id-or-name"></object>
+ <map id="two-maps-with-this-id-or-name">
+ <area shape="rect" coords="0,0,99,50" href="#area-two-maps-with-this-id-or-name-1"/>
+ </map>
+ <map name="two-maps-with-this-id-or-name">
+ <area shape="rect" coords="0,0,99,50" href="#area-two-maps-with-this-id-or-name-2"/>
+ </map>
+</div>
+
+<div data-expect="no match">
+ <img src="/images/threecolors.png" usemap="hash-last#"/>
+ <object data="/images/threecolors.png" usemap="hash-last#"></object>
+ <map name="hash-last" id="hash-last">
+ <area shape="rect" coords="0,0,99,50" href="#area-hash-last-no-hash-in-map-name-and-id"/>
+ </map>
+ <map name="hash-last#" id="hash-last#">
+ <area shape="rect" coords="0,0,99,50" href="#area-hash-last-with-hash-in-map-name-and-id"/>
+ </map>
+</div>
+
+<div data-expect="no match">
+ <img src="/images/threecolors.png" usemap=""/>
+ <object data="/images/threecolors.png" usemap=""></object>
+ <map name="" id="">
+ <area shape="rect" coords="0,0,99,50" href="#area-empty-usemap-empty-map-name-and-id"/>
+ </map>
+</div>
+
+<div data-expect="no match">
+ <img src="/images/threecolors.png" usemap="#"/>
+ <object data="/images/threecolors.png" usemap="#"></object>
+ <map name="" id="">
+ <area shape="rect" coords="0,0,99,50" href="#area-hash-usemap-empty-name-and-id"/>
+ </map>
+</div>
+
+<div data-expect="area-hash-space-usemap-space-map-name">
+ <img src="/images/threecolors.png" usemap="# "/>
+ <object data="/images/threecolors.png" usemap="# "></object>
+ <map name=" ">
+ <area shape="rect" coords="0,0,99,50" href="#area-hash-space-usemap-space-map-name"/>
+ </map>
+</div>
+
+<div data-expect="area-hash-LF-usemap-LF-map-id">
+ <img src="/images/threecolors.png" usemap="#&#x0A;"/>
+ <object data="/images/threecolors.png" usemap="#&#x0A;"></object>
+ <map id="&#x0A;">
+ <area shape="rect" coords="0,0,99,50" href="#area-hash-LF-usemap-LF-map-id"/>
+ </map>
+</div>
+
+<div data-expect="no match">
+ <img src="/images/threecolors.png" usemap="#percent-escape-name-%41"/>
+ <object data="/images/threecolors.png" usemap="#percent-escape-name-%41"></object>
+ <map name="percent-escape-name-A">
+ <area shape="rect" coords="0,0,99,50" href="#area-percent-escape-name-A"/>
+ </map>
+</div>
+
+<div data-expect="no match">
+ <img src="/images/threecolors.png" usemap="#percent-escape-id-%41"/>
+ <object data="/images/threecolors.png" usemap="#percent-escape-id-%41"></object>
+ <map id="percent-escape-id-A">
+ <area shape="rect" coords="0,0,99,50" href="#area-percent-escape-id-A"/>
+ </map>
+</div>
+
+<div data-expect="area-percent-escape-name-B">
+ <img src="/images/threecolors.png" usemap="#percent-escape-name-%42"/>
+ <object data="/images/threecolors.png" usemap="#percent-escape-name-%42"></object>
+ <map name="percent-escape-name-%42">
+ <area shape="rect" coords="0,0,99,50" href="#area-percent-escape-name-B"/>
+ </map>
+</div>
+
+<div data-expect="area-percent-escape-id-B">
+ <img src="/images/threecolors.png" usemap="#percent-escape-id-%42"/>
+ <object data="/images/threecolors.png" usemap="#percent-escape-id-%42"></object>
+ <map id="percent-escape-id-%42">
+ <area shape="rect" coords="0,0,99,50" href="#area-percent-escape-id-B"/>
+ </map>
+</div>
+
+<div data-expect="area-hash-space-name">
+ <img src="/images/threecolors.png" usemap="# hash-space-name"/>
+ <object data="/images/threecolors.png" usemap="# hash-space-name"></object>
+ <map name=" hash-space-name">
+ <area shape="rect" coords="0,0,99,50" href="#area-hash-space-name"/>
+ </map>
+</div>
+
+<div data-expect="area-hash-space-id">
+ <img src="/images/threecolors.png" usemap="# hash-space-id"/>
+ <object data="/images/threecolors.png" usemap="# hash-space-id"></object>
+ <map id=" hash-space-id">
+ <area shape="rect" coords="0,0,99,50" href="#area-hash-space-id"/>
+ </map>
+</div>
+
+<div data-expect="area-space-before-hash-name">
+ <img src="/images/threecolors.png" usemap=" #space-before-hash-name"/>
+ <object data="/images/threecolors.png" usemap=" #space-before-hash-name"></object>
+ <map name="space-before-hash-name">
+ <area shape="rect" coords="0,0,99,50" href="#area-space-before-hash-name"/>
+ </map>
+</div>
+
+<div data-expect="area-space-before-hash-id">
+ <img src="/images/threecolors.png" usemap=" #space-before-hash-id"/>
+ <object data="/images/threecolors.png" usemap=" #space-before-hash-id"></object>
+ <map id="space-before-hash-id">
+ <area shape="rect" coords="0,0,99,50" href="#area-space-before-hash-id"/>
+ </map>
+</div>
+
+<div data-expect="area-garbage-before-hash-name">
+ <img src="/images/threecolors.png" usemap="http://example.org/#garbage-before-hash-name"/>
+ <object data="/images/threecolors.png" usemap="http://example.org/#garbage-before-hash-name"></object>
+ <map name="garbage-before-hash-name">
+ <area shape="rect" coords="0,0,99,50" href="#area-garbage-before-hash-name"/>
+ </map>
+</div>
+
+<div data-expect="area-garbage-before-hash-id">
+ <img src="/images/threecolors.png" usemap="http://example.org/#garbage-before-hash-id"/>
+ <object data="/images/threecolors.png" usemap="http://example.org/#garbage-before-hash-id"></object>
+ <map id="garbage-before-hash-id">
+ <area shape="rect" coords="0,0,99,50" href="#area-garbage-before-hash-id"/>
+ </map>
+</div>
+
+<div data-expect="no match">
+ <img src="/images/threecolors.png" usemap="#no-such-map"/>
+ <object data="/images/threecolors.png" usemap="#no-such-map"></object>
+ <map>
+ <area shape="rect" coords="0,0,99,50" href="#area-no-such-map"/>
+ </map>
+</div>
+
+<div data-expect="no match">
+ <img src="/images/threecolors.png" usemap="#different-CASE-name"/>
+ <object data="/images/threecolors.png" usemap="#different-CASE-name"></object>
+ <map name="different-case-name">
+ <area shape="rect" coords="0,0,99,50" href="#area-different-case-name"/>
+ </map>
+</div>
+
+<div data-expect="no match">
+ <img src="/images/threecolors.png" usemap="#different-CASE-id"/>
+ <object data="/images/threecolors.png" usemap="#different-CASE-id"></object>
+ <map id="different-case-id">
+ <area shape="rect" coords="0,0,99,50" href="#area-different-case-id"/>
+ </map>
+</div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference.html b/testing/web-platform/tests/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference.html
new file mode 100644
index 0000000000..b00f8fe2ae
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>parsing a hash-name reference for img and object</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ body { margin-top: 0 }
+ iframe { height: 600px; width:50px; border-top: none }
+</style>
+
+<div id="log"></div>
+
+<iframe data-name="HTML (standards)" src="hash-name-reference-test-data.html?pipe=sub&amp;doctype=html"></iframe>
+<iframe data-name="HTML (quirks)" src="hash-name-reference-test-data.html?pipe=sub&amp;doctype=quirks"></iframe>
+<iframe data-name="XHTML" src="hash-name-reference-test-data.html?pipe=sub|header(Content-Type, application/xhtml%2Bxml)&amp;doctype=html"></iframe>
+
+<script>
+setup({explicit_done: true});
+
+onload = function() {
+ var iframes = document.querySelectorAll('iframe');
+ iframes.forEach(function(iframe) {
+ var iframeName = iframe.getAttribute('data-name');
+ var doc = iframe.contentDocument;
+ var divs = doc.querySelectorAll('div[data-expect]');
+ var div, img, object;
+ for (var i = 0; i < divs.length; ++i) {
+ div = divs[i];
+ img = div.querySelector('img');
+ object = div.querySelector('object');
+ [img, object].forEach(function(elm) {
+ test(function(t) {
+ var expected = div.getAttribute('data-expect');
+ var expected_elm = (expected === 'no match' || elm === object) ? elm : div.querySelector('area[href="#' + expected + '"]');
+ var got_elm = doc.elementFromPoint(elm.offsetLeft, elm.offsetTop);
+ assert_not_equals(expected_elm, null, 'sanity check (data-expect value wrong?)');
+ assert_not_equals(got_elm, null, 'sanity check (too many tests to fit in viewport?)');
+ assert_equals(got_elm, expected_elm);
+ }, iframeName + ' ' + elm.tagName + ' usemap=' + format_value(elm.useMap));
+ });
+ }
+ });
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_controls_present-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_controls_present-manual.html
new file mode 100644
index 0000000000..38faa4d00a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_controls_present-manual.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Audio Test: audio_controls_present.html</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-controls" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check if the controls attribute is present in the audio element that expecting the user agent exposes a controller user interface" />
+ </head>
+ <body>
+ <p>Test passes if a controller user interface appears below and the text 'The user agent doesn't support media element.' does not appear anywhere on this page</p>
+ <audio id="m" controls>The user agent doesn't support media element.</audio>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_loop_base.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_loop_base.html
new file mode 100644
index 0000000000..418e1b19c3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_loop_base.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Audio Test: audio_loop_base</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-loop" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check if audio.loop is set to true that expecting the seeking event is fired more than once" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+ <audio id="m" controls>The user agent doesn't support media element.</audio>
+ <script type="text/javascript">
+ var media = document.getElementById("m");
+ var name = document.getElementsByName("assert")[0].content;
+ var t = async_test(name);
+
+ var looped = false;
+
+ function startTest() {
+ if (looped) {
+ t.step(function() {
+ assert_true(true, "looped");
+ });
+ t.done();
+ media.pause();
+ }
+
+ looped = true;
+ }
+
+ media.addEventListener("error", t.unreached_func());
+ media.addEventListener("seeking", startTest, false);
+ media.loop = true;
+ media.src = getAudioURI("/media/sound_0") + "?" + new Date() + Math.random();
+ media.play();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_loop_seek_to_eos.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_loop_seek_to_eos.html
new file mode 100644
index 0000000000..01a2d4bea9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_loop_seek_to_eos.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Seeking to the end of looping audio</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<audio id="a" controls loop></audio>
+<script type="text/javascript">
+
+promise_test(async t => {
+ const a = document.getElementById("a");
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+ await a.play();
+
+ // Seek to the end of audio (EOS). However, as audio is looping, it should
+ // keep playing after seeking.
+ a.currentTime = a.duration;
+ await new Promise(r => a.onseeked = r);
+ await new Promise(r => a.ontimeupdate = r);
+ assert_false(a.paused);
+} , "seeking to the end of looping audio");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_muted_overriding_volume-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_muted_overriding_volume-manual.html
new file mode 100644
index 0000000000..cc1892ce89
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_muted_overriding_volume-manual.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Audio Test: audio_muted_overriding_volume</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-muted" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check if the muted attribute is present in the audio element with volume is set to loudest that expecting the user hears no sound" />
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p>Test passes if the audio is playing without sound output and the text 'The user agent doesn't support media element.' does not appear anywhere on this page</p>
+ <audio id="m" controls muted>The user agent doesn't support media element.</audio>
+ <script type="text/javascript">
+ var media = document.getElementById("m");
+ media.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+ media.volume = 1.0;
+ media.play();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_muted_present-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_muted_present-manual.html
new file mode 100644
index 0000000000..16d6f07eed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_muted_present-manual.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Audio Test: audio_muted_present</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-muted" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check if the muted attribute is present in the audio element that expecting the user hears no sound" />
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p>Test passes if the audio is playing without sound output and the text 'The user agent doesn't support media element.' does not appear anywhere on this page</p>
+ <audio id="m" controls muted>The user agent doesn't support media element.</audio>
+ <script type="text/javascript">
+ var media = document.getElementById("m");
+ media.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+ media.play();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_volume_check.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_volume_check.html
new file mode 100644
index 0000000000..b467c702a7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_volume_check.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Audio Test: audio_volume_check</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-volume" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check that audio.volume returns the value of the muted content attribute" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+ <audio id="m">The user agent doesn't support media element.</audio>
+ <script type="text/javascript">
+ var media = document.getElementById("m");
+ var VOLUME = {
+ 'SILENT' : 0.0,
+ 'NORMAL' : 0.5,
+ 'LOUDEST' : 1.0,
+ 'LOWER' : -1.1,
+ 'UPPER' : 1.1,
+ };
+
+ test(function() {
+ assert_false(media.volume < VOLUME.SILENT || media.volume > VOLUME.LOUDEST, "media.volume outside the range 0.0 to 1.0 inclusive");
+ }, "Check if the intial value of the audio.volume is in the range 0.0 to 1.0 inclusive");
+
+ function volume_setting(vol, name)
+ {
+ if (vol < VOLUME.SILENT || vol > VOLUME.LOUDEST) {
+ try {
+ media.volume = vol;
+ test(function() {
+ assert_true(false, "media.volume setting exception");
+ }, name);
+ } catch(e) {
+ test(function() {
+ // 1 should be e.IndexSizeError or e.INDEX_SIZE_ERR in previous spec
+ assert_equals(e.code, 1, "media.volume setting exception");
+ }, name);
+ }
+ } else {
+ media.volume = vol;
+ test(function() {
+ assert_equals(media.volume, vol, "media.volume new value");
+ }, name);
+ }
+ }
+
+ volume_setting(VOLUME.NORMAL, "Check if audio.volume is able to set to new value in the range 0.0 to 1.0");
+ volume_setting(VOLUME.SILENT, "Check if media.volume is able to set to new value 0.0 as silent");
+ volume_setting(VOLUME.LOUDEST, "Check if media.volume is able to set to new value 1.0 as loudest");
+ volume_setting(VOLUME.LOWER, "Check if media.volume is set to new value less than 0.0 that expecting an IndexSizeError exception is to be thrown");
+ volume_setting(VOLUME.UPPER, "Check if audio.volume is set to new value greater than 1.0 that expecting an IndexSizeError exception is to be thrown");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_volume_loudest-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_volume_loudest-manual.html
new file mode 100644
index 0000000000..a623e8f5c2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_volume_loudest-manual.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Audio Test: audio_volume_loudest</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-volume" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check if the volume attribute is set to 1.0 as loudest in the audio element that expecting the user hears sound loudly" />
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p>Test passes if the audio is playing with sound heard and the text 'The user agent doesn't support media element.' does not appear anywhere on this page</p>
+ <audio id="m" controls>The user agent doesn't support media element.</audio>
+ <script type="text/javascript">
+ var media = document.getElementById("m");
+ media.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+ media.volume = 1.0;
+ media.play();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_volume_silent-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_volume_silent-manual.html
new file mode 100644
index 0000000000..257bd46289
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/audio_volume_silent-manual.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Audio Test: audio_volume_silent</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-volume" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check if the volume attribute is set to 0.0 as silent in the audio element that expecting the user hears no sound" />
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p>Test passes if the audio is playing without sound heard and the text 'The user agent doesn't support media element.' does not appear anywhere on this page</p>
+ <audio id="m" controls volume=0.0>The user agent doesn't support media element.</audio>
+ <script type="text/javascript">
+ var media = document.getElementById("m");
+ media.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+ media.volume = 0.0;
+ media.play();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html
new file mode 100644
index 0000000000..6f11f8995b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<body>
+ <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=/common/media.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script src=/feature-policy/resources/autoplay.js></script>
+ <script>
+ 'use strict';
+ const relative_path = '/feature-policy/resources/feature-policy-autoplay.html';
+ const base_src = '/feature-policy/resources/redirect-on-load.html#';
+ const same_origin_src = base_src + relative_path;
+ const cross_origin_src = base_src + 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ relative_path;
+ const header = 'Feature-Policy allow="autoplay"';
+
+ async_test(t => {
+ simulateGesture(t, () => {
+ test_feature_availability(
+ 'autoplay', t, same_origin_src,
+ expect_feature_available_default, 'autoplay');
+ });
+ }, header + ' allows same-origin navigation in an iframe.');
+
+ async_test(t => {
+ simulateGesture(t, () => {
+ test_feature_availability(
+ 'autoplay', t, cross_origin_src,
+ expect_feature_unavailable_default, 'autoplay');
+ });
+ }, header + ' disallows cross-origin navigation in an iframe.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy-attribute.https.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy-attribute.https.sub.html
new file mode 100644
index 0000000000..59b33d7c4d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy-attribute.https.sub.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<body>
+ <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=/common/media.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script src=/feature-policy/resources/autoplay.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/feature-policy/resources/feature-policy-autoplay.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const feature_name = 'Feature policy "autoplay"';
+ const header = 'allow="autoplay" attribute';
+
+ async_test(t => {
+ simulateGesture(t, () => {
+ test_feature_availability(
+ 'autoplay', t, same_origin_src,
+ expect_feature_available_default, 'autoplay');
+ });
+ }, feature_name + ' can be enabled in same-origin iframe using ' + header);
+
+ async_test(t => {
+ simulateGesture(t, () => {
+ test_feature_availability(
+ 'autoplay', t, cross_origin_src,
+ expect_feature_available_default, 'autoplay');
+ });
+ }, feature_name + ' can be enabled in cross-origin iframe using ' + header);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy.https.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy.https.sub.html
new file mode 100644
index 0000000000..63479c0cb6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy.https.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<body>
+ <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=/common/media.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script src=/feature-policy/resources/autoplay.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/feature-policy/resources/feature-policy-autoplay.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const header = 'Feature-Policy header: autoplay *';
+
+ async_test(t => {
+ simulateGesture(t, () => {
+ isAutoplayAllowed().then(t.step_func_done((result) => {
+ assert_true(result);
+ }));
+ });
+ }, header + ' allows the top-level document.');
+
+ async_test(t => {
+ simulateGesture(t, () => {
+ test_feature_availability('autoplay', t, same_origin_src,
+ expect_feature_available_default);
+ });
+ }, header + ' allows same-origin iframes.');
+
+ async_test(t => {
+ simulateGesture(t, () => {
+ test_feature_availability('autoplay', t, cross_origin_src,
+ expect_feature_available_default);
+ });
+ }, header + ' allows cross-origin iframes.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy.https.sub.html.headers b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000000..08461fadc2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: autoplay *
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-default-feature-policy.https.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-default-feature-policy.https.sub.html
new file mode 100644
index 0000000000..763073e437
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-default-feature-policy.https.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<body>
+ <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=/common/media.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script src=/feature-policy/resources/autoplay.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/feature-policy/resources/feature-policy-autoplay.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const header = 'Default "autoplay" feature policy ["self"]';
+
+ async_test(t => {
+ simulateGesture(t, () => {
+ isAutoplayAllowed().then(t.step_func_done((result) => {
+ assert_true(result);
+ }));
+ });
+ }, header + ' allows the top-level document.');
+
+ async_test(t => {
+ simulateGesture(t, () => {
+ test_feature_availability('autoplay', t, same_origin_src,
+ expect_feature_available_default);
+ });
+ }, header + ' allows same-origin iframes.');
+
+ async_test(t => {
+ simulateGesture(t, () => {
+ test_feature_availability('autoplay', t, cross_origin_src,
+ expect_feature_unavailable_default,);
+ });
+ }, header + ' disallows cross-origin iframes.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-disabled-by-feature-policy.https.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-disabled-by-feature-policy.https.sub.html
new file mode 100644
index 0000000000..3dd3afbf77
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-disabled-by-feature-policy.https.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<body>
+ <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=/common/media.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script src=/feature-policy/resources/autoplay.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/feature-policy/resources/feature-policy-autoplay.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const header = 'Feature-Policy header: autoplay "none"';
+
+ async_test(t => {
+ simulateGesture(t, () => {
+ isAutoplayAllowed().then(t.step_func_done((result) => {
+ assert_true(result);
+ }));
+ });
+ }, header + ' has no effect on the top level document.');
+
+ async_test(t => {
+ simulateGesture(t, () => {
+ test_feature_availability('autoplay', t, same_origin_src,
+ expect_feature_unavailable_default);
+ });
+ }, header + ' disallows same-origin iframes.');
+
+ async_test(t => {
+ simulateGesture(t, () => {
+ test_feature_availability('autoplay', t, cross_origin_src,
+ expect_feature_unavailable_default,);
+ });
+ }, header + ' disallows cross-origin iframes.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-disabled-by-feature-policy.https.sub.html.headers b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-disabled-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000000..69ce436270
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-disabled-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: autoplay 'none'
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-supported-by-feature-policy.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-supported-by-feature-policy.html
new file mode 100644
index 0000000000..af4de6bf89
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-supported-by-feature-policy.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Test that autoplay is advertised in the feature list</title>
+<link rel="help" href="https://w3c.github.io/webappsec-feature-policy/#dom-featurepolicy-features">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/infrastructure.html#policy-controlled-features">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+ assert_in_array('autoplay', document.featurePolicy.features());
+}, 'document.featurePolicy.features should advertise autoplay.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-with-broken-track.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-with-broken-track.html
new file mode 100644
index 0000000000..f687edf198
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/autoplay-with-broken-track.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/media.html#text-track-model">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id="log"></div>
+<script>
+// Media elements have a "list of pending text tracks" which should be populated
+// with text tracks with readyState "loading". When the text track src is
+// invalid or points to a non-existent resource, it shouldn't be possible to
+// block the media element's readyState indefinitely.
+function t(trackSrc) {
+ const track = document.createElement('track');
+ track.src = trackSrc;
+ track.default = true;
+ async_test(t => {
+ const video = document.createElement('video');
+ video.autoplay = true;
+ video.controls = true; // for visual inspection, not part of test
+ video.src = getVideoURI('/media/movie_5');
+ video.appendChild(track);
+ document.body.appendChild(video);
+ // The playing event isn't used because it's fired in Safari even when the
+ // playback doesn't actually start.
+ video.ontimeupdate = t.step_func(() => {
+ if (video.currentTime > 0)
+ t.done();
+ });
+ }, `<video autoplay> with ${track.outerHTML} child`);
+}
+t("invalid://url");
+t("404");
+t("");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/controlsList.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/controlsList.tentative.html
new file mode 100644
index 0000000000..11144839ee
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/controlsList.tentative.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Test controlsList attribute</title>
+<link rel="help" href="https://github.com/whatwg/html/pull/6715">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const allowedValues = [
+ "nodownload",
+ "nofullscreen",
+ "noplaybackrate",
+ "noremoteplayback",
+];
+
+function testControlsList(tagName) {
+ const element = document.createElement(tagName);
+
+ // Test that supports() is returning true for allowed values.
+ for (const value of allowedValues) {
+ assert_true(
+ element.controlsList.supports(value),
+ `tag = ${element.tagName}, value = ${value} must be supported`
+ );
+ }
+}
+
+["audio", "video"].forEach((tagName) => {
+ test(
+ () => testControlsList(tagName),
+ `Test controlsList allowed values for <${tagName}>`
+ );
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/error-codes/error.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/error-codes/error.html
new file mode 100644
index 0000000000..42d86e49b0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/error-codes/error.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<title>error</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id="log"></div>
+<script>
+function error_test(tagName, src) {
+ test(function() {
+ assert_equals(document.createElement(tagName).error, null);
+ }, tagName + '.error initial value');
+
+ async_test(function(t) {
+ var e = document.createElement(tagName);
+ e.src = src;
+ e.onerror = t.unreached_func();
+ e.onloadeddata = t.step_func(function() {
+ assert_equals(e.error, null);
+ t.done();
+ });
+ }, tagName + '.error after successful load');
+
+ // TODO: MEDIA_ERR_ABORTED, MEDIA_ERR_NETWORK, MEDIA_ERR_DECODE
+
+ async_test(function(t) {
+ var e = document.createElement(tagName);
+ e.src = '';
+ e.onerror = t.step_func(function() {
+ assert_true(e.error instanceof MediaError);
+ assert_equals(e.error.code, 4);
+ assert_equals(e.error.code, e.error.MEDIA_ERR_SRC_NOT_SUPPORTED);
+ assert_equals(typeof e.error.message, 'string', 'error.message type');
+ t.done();
+ });
+ }, tagName + '.error after setting src to the empty string');
+}
+
+error_test('audio', getAudioURI('/media/sound_5'));
+error_test('video', getVideoURI('/media/movie_5'));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplay.html
new file mode 100644
index 0000000000..e5c632bc17
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplay.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - canplay</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on autoplay audio should trigger canplay event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("canplay", t.step_func(function() {
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - canplay");
+
+test(function() {
+ var t = async_test("setting src attribute on autoplay video should trigger canplay event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("canplay", t.step_func(function() {
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - canplay");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplay_noautoplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplay_noautoplay.html
new file mode 100644
index 0000000000..b43f8d052a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplay_noautoplay.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - canplay</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" controls>
+ </audio>
+ <video id="v" controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function () {
+ var t = async_test("setting src attribute on non-autoplay audio should trigger canplay event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("canplay", t.step_func_done(), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - canplay");
+
+test(function () {
+ var t = async_test("setting src attribute on non-autoplay video should trigger canplay event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("canplay", t.step_func_done(), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - canplay");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplaythrough.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplaythrough.html
new file mode 100644
index 0000000000..b0895a97c2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplaythrough.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - canplaythrough</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on autoplay audio should trigger canplaythrough event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("canplaythrough", t.step_func(function() {
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - canplaythrough");
+
+test(function() {
+ var t = async_test("setting src attribute on autoplay video should trigger canplaythrough event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("canplaythrough", t.step_func(function() {
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - canplaythrough");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplaythrough_noautoplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplaythrough_noautoplay.html
new file mode 100644
index 0000000000..195b464f01
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_canplaythrough_noautoplay.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - canplaythrough</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" controls>
+ </audio>
+ <video id="v" controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on non-autoplay audio should trigger canplaythrough event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("canplaythrough", t.step_func_done(), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - canplaythrough");
+
+test(function() {
+ var t = async_test("setting src attribute on non-autoplay video should trigger canplaythrough event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("canplaythrough", t.step_func_done(), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - canplaythrough");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadeddata.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadeddata.html
new file mode 100644
index 0000000000..f502c595e6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadeddata.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - loadeddata</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on autoplay audio should trigger loadeddata event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("loadeddata", t.step_func(function() {
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - loadeddata");
+
+test(function() {
+ var t = async_test("setting src attribute on autoplay video should trigger loadeddata event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("loadeddata", t.step_func(function() {
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - loadeddata");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadeddata_noautoplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadeddata_noautoplay.html
new file mode 100644
index 0000000000..08b2f2f86e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadeddata_noautoplay.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - loadeddata</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" controls>
+ </audio>
+ <video id="v" controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on non-autoplay audio should trigger loadeddata event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("loadeddata", t.step_func_done(), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - loadeddata");
+
+test(function() {
+ var t = async_test("setting src attribute on non-autoplay video should trigger loadeddata event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("loadeddata", t.step_func_done(), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - loadeddata");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadedmetadata.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadedmetadata.html
new file mode 100644
index 0000000000..5a0731e811
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadedmetadata.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - loadedmetadata</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on autoplay audio should trigger loadedmetadata event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("loadedmetadata", t.step_func(function() {
+ t.done();
+ a.pause();
+ }));
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - loadedmetadata");
+
+test(function() {
+ var t = async_test("setting src attribute on autoplay video should trigger loadedmetadata event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("loadedmetadata", t.step_func(function() {
+ t.done();
+ v.pause();
+ }));
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - loadedmetadata");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadedmetadata_noautoplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadedmetadata_noautoplay.html
new file mode 100644
index 0000000000..b460317f8c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadedmetadata_noautoplay.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - loadedmetadata</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" controls>
+ </audio>
+ <video id="v" controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on non-autoplay audio should trigger loadedmetadata event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("loadedmetadata", t.step_func_done(), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - loadedmetadata");
+
+test(function() {
+ var t = async_test("setting src attribute on non-autoplay video should trigger loadedmetadata event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("loadedmetadata", t.step_func_done(), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events, loadedmetadata");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadstart.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadstart.html
new file mode 100644
index 0000000000..192821a961
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadstart.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - loadstart</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on autoplay audio should trigger loadstart event");
+ var a = document.getElementById("a");
+ a.addEventListener("loadstart", function() {
+ t.done();
+ a.pause();
+ }, false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - loadstart");
+
+test(function() {
+ var t = async_test("setting src attribute on autoplay video should trigger loadstart event");
+ var v = document.getElementById("v");
+ v.addEventListener("loadstart", function() {
+ t.done();
+ v.pause();
+ }, false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - loadstart");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadstart_noautoplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadstart_noautoplay.html
new file mode 100644
index 0000000000..10af32afcb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_loadstart_noautoplay.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - loadstart</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" controls>
+ </audio>
+ <video id="v" controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on non-autoplay audio should trigger loadstart event");
+ var a = document.getElementById("a");
+ a.addEventListener("loadstart", function() {
+ t.done();
+ }, false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - loadstart");
+
+test(function() {
+ var t = async_test("setting src attribute on non-autoplay video should trigger loadstart event");
+ var v = document.getElementById("v");
+ v.addEventListener("loadstart", function() {
+ t.done();
+ }, false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - loadstart");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_canplay_canplaythrough.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_canplay_canplaythrough.html
new file mode 100644
index 0000000000..e1bae90ed0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_canplay_canplaythrough.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - canplay, then canplaythrough</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on autoplay audio should trigger canplay then canplaythrough event");
+ var a = document.getElementById("a");
+ var found_canplay = false;
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("canplay", t.step_func(function() {
+ found_canplay = true;
+ }));
+ a.addEventListener("canplaythrough", t.step_func(function() {
+ assert_true(found_canplay);
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - canplay, then canplaythrough");
+
+test(function() {
+ var t = async_test("setting src attribute on autoplay video should trigger canplay then canplaythrough event");
+ var v = document.getElementById("v");
+ var found_canplay = false;
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("canplay", t.step_func(function() {
+ found_canplay = true;
+ }));
+ v.addEventListener("canplaythrough", t.step_func(function() {
+ assert_true(found_canplay);
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - canplay, then canplaythrough");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_canplay_playing.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_canplay_playing.html
new file mode 100644
index 0000000000..3571e5151c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_canplay_playing.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - canplay, then playing</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on autoplay audio should trigger canplay then playing event");
+ var a = document.getElementById("a");
+ var found_canplay = false;
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("canplay", t.step_func(function() {
+ found_canplay = true;
+ }));
+ a.addEventListener("playing", t.step_func(function() {
+ assert_true(found_canplay);
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - canplay, then playing");
+
+test(function() {
+ var t = async_test("setting src attribute on autoplay video should trigger canplay then playing event");
+ var v = document.getElementById("v");
+ var found_canplay = false;
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("canplay", t.step_func(function() {
+ found_canplay = true;
+ }));
+ v.addEventListener("playing", t.step_func(function() {
+ assert_true(found_canplay);
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - canplay, then playing");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_loadedmetadata_loadeddata.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_loadedmetadata_loadeddata.html
new file mode 100644
index 0000000000..71aeca50c1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_loadedmetadata_loadeddata.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - loadedmetadata, then loadeddata</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on autoplay audio should trigger loadedmetadata then loadeddata event");
+ var a = document.getElementById("a");
+ var found_loadedmetadata = false;
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("loadedmetadata", t.step_func(function() {
+ found_loadedmetadata = true;
+ }));
+ a.addEventListener("loadeddata", t.step_func(function() {
+ assert_true(found_loadedmetadata);
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - loadedmetadata, then loadeddata");
+
+test(function() {
+ var t = async_test("setting src attribute on autoplay video should trigger loadedmetadata then loadeddata event");
+ var v = document.getElementById("v");
+ var found_loadedmetadata = false;
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("loadedmetadata", t.step_func(function() {
+ found_loadedmetadata = true;
+ }));
+ v.addEventListener("loadeddata", t.step_func(function() {
+ assert_true(found_loadedmetadata);
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - loadedmetadata, then loadeddata");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_loadstart_progress.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_loadstart_progress.html
new file mode 100644
index 0000000000..c6e1dbe07a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_order_loadstart_progress.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - loadstart, then progress</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on autoplay audio should trigger loadstart then progress event");
+ var a = document.getElementById("a");
+ var found_loadstart = false;
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("loadstart", t.step_func(function() {
+ found_loadstart = true;
+ }));
+ a.addEventListener("progress", t.step_func(function() {
+ assert_true(found_loadstart);
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - loadstart, then progress");
+
+test(function() {
+ var t = async_test("setting src attribute on autoplay video should trigger loadstart then progress event");
+ var v = document.getElementById("v");
+ var found_loadstart = false;
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("loadstart", t.step_func(function() {
+ found_loadstart = true;
+ }));
+ v.addEventListener("progress", t.step_func(function() {
+ assert_true(found_loadstart);
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - loadstart, then progress");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_pause.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_pause.html
new file mode 100644
index 0000000000..841e124d5b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_pause.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - pause</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("calling pause() on autoplay audio should trigger pause event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("pause", t.step_func_done(), false);
+ a.addEventListener("play", t.step_func(function() {
+ a.pause(); // pause right after play
+ }));
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - pause");
+
+test(function() {
+ var t = async_test("calling pause() on autoplay video should trigger pause event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("pause", t.step_func_done(), false);
+ v.addEventListener("play", t.step_func(function() {
+ v.pause(); // pause right after play
+ }));
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - pause");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_pause_noautoplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_pause_noautoplay.html
new file mode 100644
index 0000000000..c6d9d5920b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_pause_noautoplay.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - pause</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" controls>
+ </audio>
+ <video id="v" controls>
+ </video>
+ <div id="log"></div>
+ <script>
+promise_test(function(t) {
+ var async_t = async_test("calling play() then pause() on non-autoplay audio should trigger pause event");
+ var a = document.getElementById("a");
+ a.addEventListener("pause", function() {
+ async_t.done();
+ }, false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+ var play_promise = a.play();
+ a.pause();
+ return promise_rejects_dom(t, "AbortError", play_promise, "pause() should reject all pending play Promises");
+}, "audio events - pause");
+
+promise_test(function(t) {
+ var async_t = async_test("calling play() then pause() on non-autoplay video should trigger pause event");
+ var v = document.getElementById("v");
+ v.addEventListener("pause", function() {
+ async_t.done();
+ }, false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+ var play_promise = v.play()
+ v.pause();
+ return promise_rejects_dom(t, "AbortError", play_promise, "pause() should reject all pending play Promises");
+}, "video events - pause");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_play.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_play.html
new file mode 100644
index 0000000000..f96c35113b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_play.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - play</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on autoplay audio should trigger play event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("play", t.step_func(function() {
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - play");
+
+test(function() {
+ var t = async_test("setting src attribute on autoplay video should trigger play event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("play", t.step_func(function() {
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - play");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_play_noautoplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_play_noautoplay.html
new file mode 100644
index 0000000000..0dff37c800
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_play_noautoplay.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - play</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" controls>
+ </audio>
+ <video id="v" controls>
+ </video>
+ <div id="log"></div>
+ <script>
+promise_test(function(t) {
+ var async_t = async_test("calling play() on audio should trigger play event");
+ var a = document.getElementById("a");
+ a.addEventListener("play", async_t.step_func(function() {
+ a.pause();
+ async_t.done();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+ return promise_rejects_dom(t, "AbortError", a.play(), "pause() should reject all pending play Promises");
+}, "audio events - play");
+
+promise_test(function(t) {
+ var async_t = async_test("calling play() on video should trigger play event");
+ var v = document.getElementById("v");
+ v.addEventListener("play", async_t.step_func(function() {
+ v.pause();
+ async_t.done();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+ return promise_rejects_dom(t, "AbortError", v.play(), "pause() should reject all pending play Promises");
+}, "video events - play");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_playing.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_playing.html
new file mode 100644
index 0000000000..18204c457a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_playing.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - playing</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on autoplay audio should trigger playing event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("playing", t.step_func(function() {
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - playing");
+
+test(function() {
+ var t = async_test("setting src attribute on autoplay video should trigger playing event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("playing", t.step_func(function() {
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - playing");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_playing_noautoplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_playing_noautoplay.html
new file mode 100644
index 0000000000..e9714d7ec5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_playing_noautoplay.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - playing</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" controls>
+ </audio>
+ <video id="v" controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("calling play() on audio should trigger playing event");
+ var a = document.getElementById("a");
+ a.addEventListener("playing", function() {
+ t.done();
+ a.pause();
+ });
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+ a.play();
+}, "audio events - playing");
+
+test(function() {
+ var t = async_test("calling play() on video should trigger playing event");
+ var v = document.getElementById("v");
+ v.addEventListener("playing", function() {
+ t.done();
+ v.pause();
+ });
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+ v.play();
+}, "video events - playing");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_progress.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_progress.html
new file mode 100644
index 0000000000..ae4496c99f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_progress.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - progress</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on autoplay audio should trigger progress event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("progress", t.step_func(function() {
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - progress");
+
+test(function() {
+ var t = async_test("setting src attribute on autoplay video should trigger progress event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("progress", t.step_func(function() {
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - progress");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_progress_noautoplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_progress_noautoplay.html
new file mode 100644
index 0000000000..8b32448b9f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_progress_noautoplay.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - progress</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" controls>
+ </audio>
+ <video id="v" controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("setting src attribute on non-autoplay audio should trigger progress event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("progress", t.step_func_done(), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - progress");
+
+test(function() {
+ var t = async_test("setting src attribute on non-autoplay video should trigger progress event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("progress", t.step_func_done(), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - progress");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_timeupdate.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_timeupdate.html
new file mode 100644
index 0000000000..0909c864e3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_timeupdate.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - timeupdate</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+var ta = async_test("setting src attribute on a sufficiently long autoplay audio should trigger timeupdate event");
+var a = document.getElementById("a");
+a.addEventListener("timeupdate", function() {
+ ta.done();
+ a.pause();
+}, false);
+a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+
+var tv = async_test("setting src attribute on a sufficiently long autoplay video should trigger timeupdate event");
+var v = document.getElementById("v");
+v.addEventListener("timeupdate", function() {
+ tv.done();
+ v.pause();
+}, false);
+v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_timeupdate_noautoplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_timeupdate_noautoplay.html
new file mode 100644
index 0000000000..2738a3b4ac
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_timeupdate_noautoplay.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - timeupdate</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" controls>
+ </audio>
+ <video id="v" controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("calling play() on a sufficiently long audio should trigger timeupdate event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("timeupdate", t.step_func(function() {
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+ a.play();
+}, "audio events - timeupdate");
+
+test(function() {
+ var t = async_test("calling play() on a sufficiently long video should trigger timeupdate event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("timeupdate", t.step_func(function() {
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+ v.play();
+}, "video events - timeupdate");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_volumechange.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_volumechange.html
new file mode 100644
index 0000000000..3481947e87
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/event_volumechange.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<title>volumechange event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function volumechange_test(tagName) {
+ async_test(function(t) {
+ var e = document.createElement(tagName);
+ assert_equals(e.volume, 1);
+ e.volume = 0.5;
+ assert_equals(e.volume, 0.5);
+ e.onvolumechange = t.step_func(function() {
+ assert_equals(e.volume, 0.5);
+ e.volume = 1;
+ assert_equals(e.volume, 1);
+ e.onvolumechange = t.step_func(function() {
+ assert_equals(e.volume, 1);
+ t.done();
+ });
+ });
+ }, "setting " + tagName + ".volume fires volumechange");
+
+ async_test(function(t) {
+ var e = document.createElement(tagName);
+ assert_false(e.muted);
+ e.muted = true;
+ assert_true(e.muted);
+ e.onvolumechange = t.step_func(function() {
+ assert_true(e.muted);
+ e.muted = false;
+ assert_false(e.muted);
+ e.onvolumechange = t.step_func(function() {
+ assert_false(e.muted);
+ t.done();
+ });
+ });
+ }, "setting " + tagName + ".muted fires volumechange");
+
+ async_test(function(t) {
+ var e = document.createElement(tagName);
+ e.volume = e.volume;
+ e.muted = e.muted;
+ e.onvolumechange = t.step_func(function() {
+ assert_unreached();
+ });
+ var e2 = document.createElement(tagName);
+ e2.muted = !e2.muted;
+ e2.onvolumechange = t.step_func(function() {
+ t.done();
+ });
+ }, "setting " + tagName + ".volume/muted to the same value does not fire volumechange");
+
+ async_test(function(t) {
+ var e = document.createElement(tagName);
+ e.muted = !e.muted;
+ e.volume = 1 - e.volume;
+ e.muted = !e.muted;
+ e.volume = 1 - e.volume;
+ var volumechange_count = 0;
+ e.onvolumechange = t.step_func(function() {
+ volumechange_count++;
+ if (volumechange_count == 4) {
+ t.done();
+ }
+ });
+ }, "setting " + tagName + ".volume/muted repeatedly fires volumechange repeatedly");
+}
+
+volumechange_test("audio");
+volumechange_test("video");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/historical.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/historical.html
new file mode 100644
index 0000000000..d7395632eb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/historical.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<title>Historical media element features should not be supported</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+function t(property, tagName) {
+ var tagNames = tagName ? [tagName] : ['audio', 'video'];
+ tagNames.forEach(function(tagName) {
+ test(function() {
+ assert_false(property in document.createElement(tagName));
+ }, tagName + '.' + property + ' should not be supported');
+ });
+}
+
+t('bufferingRate'); // added in r678, removed in r2872.
+t('start'); // added in r692, removed in r2401.
+t('end'); // added in r692, removed in r2401.
+t('loopStart'); // added in r692, removed in r2401.
+t('loopEnd'); // added in r692, removed in r2401.
+t('loopCount'); // added in r692, replaced with playCount in r1105.
+t('currentLoop'); // added in r692, removed in r2401.
+t('addCuePoint'); // added in r721, replaced with addCueRange in r1106.
+t('removeCuePoint'); // added in r721, replaced with removeCueRanges in r1106.
+t('playCount'); // added in r1105, removed in r2401.
+t('addCueRange'); // added in r1106, removed in r5070.
+t('removeCueRanges'); // added in r1106, removed in r5070.
+t('pixelratio', 'source'); // added in r1629, removed in r2493.
+t('bufferedBytes'); // added in r1630, removed in r2405.
+t('totalBytes'); // added in r1630, removed in r2405.
+t('bufferingThrottled'); // added in r1632, removed in r2872.
+t('autobuffer'); // added in r2855, replaced with preload in r4811.
+t('startTime'); // added in r3035, replaced with initialTime in r5310.
+t('startOffsetTime'); // added in r5310, replaced with startDate in r7045.
+t('initialTime'); // added in r5310, removed in r7046.
+t('audio', 'video'); // added in r5636, replaced with muted in r5991.
+t('startDate'); // added in r7045, replaced with getStartDate() in r8113.
+t('mozSrcObject'); // never in the spec
+t('mozPreservesPitch'); // prefixed version should be removed.
+t('webkitPreservesPitch'); // prefixed version should be removed.
+
+// TextTrackCue constructor: added in r5723, removed in r7742.
+test(function() {
+ assert_throws_js(TypeError, function() {
+ new TextTrackCue(0, 0, '');
+ });
+}, 'TextTrackCue constructor should not be supported');
+
+// added in https://github.com/whatwg/html/commit/66c5b32240c202c74f475872e7ea2cd163777b4a
+// removed in https://github.com/whatwg/html/commit/634698e70ea4586d58c989fa7d2cbfcad20d33e6
+t('mediaGroup');
+t('controller');
+test(function() {
+ assert_false('MediaController' in window);
+}, 'MediaController constructor should not be supported');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/addTextTrack.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/addTextTrack.html
new file mode 100644
index 0000000000..0e1a48f78a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/addTextTrack.html
@@ -0,0 +1,116 @@
+<!doctype html>
+<title>HTMLMediaElement.addTextTrack</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+var video = document.createElement('video');
+test(function(){
+ assert_throws_js(TypeError, function(){
+ video.addTextTrack('foo');
+ });
+ assert_throws_js(TypeError, function(){
+ video.addTextTrack(undefined);
+ });
+ assert_throws_js(TypeError, function(){
+ video.addTextTrack(null);
+ });
+}, document.title + ' bogus first arg');
+
+test(function(){
+ assert_throws_js(TypeError, function(){
+ video.addTextTrack('SUBTITLES');
+ });
+}, document.title + ' uppercase first arg');
+
+test(function(){
+ var t = video.addTextTrack('subtitles');
+ assert_equals(t.kind, 'subtitles');
+ assert_equals(t.label, '');
+ assert_equals(t.language, '');
+ assert_equals(t.mode, 'hidden');
+ assert_true(t.cues instanceof TextTrackCueList);
+ assert_equals(t.cues.length, 0);
+}, document.title + ' subtitles first arg');
+
+test(function(){
+ var t = video.addTextTrack('captions');
+ assert_equals(t.kind, 'captions');
+ assert_equals(t.label, '');
+ assert_equals(t.language, '');
+ assert_equals(t.mode, 'hidden');
+ assert_true(t.cues instanceof TextTrackCueList);
+ assert_equals(t.cues.length, 0);
+}, document.title + ' captions first arg');
+
+test(function(){
+ var t = video.addTextTrack('descriptions');
+ assert_equals(t.kind, 'descriptions');
+ assert_equals(t.label, '');
+ assert_equals(t.language, '');
+ assert_equals(t.mode, 'hidden');
+ assert_true(t.cues instanceof TextTrackCueList);
+ assert_equals(t.cues.length, 0);
+}, document.title + ' descriptions first arg');
+
+test(function(){
+ var t = video.addTextTrack('chapters');
+ assert_equals(t.kind, 'chapters');
+ assert_equals(t.label, '');
+ assert_equals(t.language, '');
+ assert_equals(t.mode, 'hidden');
+ assert_true(t.cues instanceof TextTrackCueList);
+ assert_equals(t.cues.length, 0);
+}, document.title + ' chapters first arg');
+
+test(function(){
+ var t = video.addTextTrack('metadata');
+ assert_equals(t.kind, 'metadata');
+ assert_equals(t.label, '');
+ assert_equals(t.language, '');
+ assert_equals(t.mode, 'hidden');
+ assert_true(t.cues instanceof TextTrackCueList);
+ assert_equals(t.cues.length, 0);
+}, document.title + ' metadata first arg');
+
+test(function(){
+ var t = video.addTextTrack('subtitles', undefined, undefined);
+ assert_equals(t.kind, 'subtitles');
+ assert_equals(t.label, '');
+ assert_equals(t.language, '');
+ assert_equals(t.mode, 'hidden');
+ assert_true(t.cues instanceof TextTrackCueList);
+ assert_equals(t.cues.length, 0);
+}, document.title + ' undefined second and third arg');
+
+test(function(){
+ var t = video.addTextTrack('subtitles', null, null);
+ assert_equals(t.kind, 'subtitles');
+ assert_equals(t.label, 'null');
+ assert_equals(t.language, 'null');
+ assert_equals(t.mode, 'hidden');
+ assert_true(t.cues instanceof TextTrackCueList);
+ assert_equals(t.cues.length, 0);
+}, document.title + ' null second and third arg');
+
+test(function(){
+ var t = video.addTextTrack('subtitles', 'foo', 'bar');
+ assert_equals(t.kind, 'subtitles');
+ assert_equals(t.label, 'foo');
+ assert_equals(t.language, 'bar');
+ assert_equals(t.mode, 'hidden');
+ assert_true(t.cues instanceof TextTrackCueList);
+ assert_equals(t.cues.length, 0);
+}, document.title + ' foo and bar second and third arg');
+
+test(function(){
+ var t = video.addTextTrack('subtitles', 'foo');
+ assert_equals(t.kind, 'subtitles');
+ assert_equals(t.label, 'foo');
+ assert_equals(t.language, '');
+ assert_equals(t.mode, 'hidden');
+ assert_true(t.cues instanceof TextTrackCueList);
+ assert_equals(t.cues.length, 0);
+}, document.title + ' foo second arg, third arg omitted');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/crossOrigin.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/crossOrigin.html
new file mode 100644
index 0000000000..e29f2b0fbc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/crossOrigin.html
@@ -0,0 +1,60 @@
+<!doctype html>
+<title>HTMLMediaElement.crossOrigin</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var video = document.createElement('video');
+ assert_true('crossOrigin' in video);
+});
+test(function(){
+ var video = document.createElement('video');
+ assert_equals(video.crossOrigin, null);
+}, document.title+', content attribute missing');
+test(function(){
+ var video = document.createElement('video');
+ video.setAttribute('crossorigin', 'foo');
+ assert_equals(video.crossOrigin, 'anonymous');
+}, document.title+', content attribute invalid value');
+test(function(){
+ var video = document.createElement('video');
+ video.setAttribute('crossorigin', '');
+ assert_equals(video.crossOrigin, 'anonymous');
+}, document.title+', content attribute empty string');
+test(function(){
+ var video = document.createElement('video');
+ video.setAttribute('crossorigin', 'ANONYMOUS');
+ assert_equals(video.crossOrigin, 'anonymous');
+}, document.title+', content attribute uppercase ANONYMOUS');
+test(function(){
+ var video = document.createElement('video');
+ video.setAttribute('crossorigin', 'use-credentials');
+ assert_equals(video.crossOrigin, 'use-credentials');
+}, document.title+', content attribute use-credentials');
+test(function(){
+ var video = document.createElement('video');
+ video.crossOrigin = '';
+ assert_equals(video.getAttribute('crossorigin'), '');
+}, document.title+', setting to empty string');
+test(function(){
+ var video = document.createElement('video');
+ video.crossOrigin = null;
+ assert_false(video.hasAttribute('crossorigin'));
+}, document.title+', setting to null');
+test(function(){
+ var video = document.createElement('video');
+ video.crossOrigin = 'foo';
+ assert_equals(video.getAttribute('crossorigin'), 'foo');
+}, document.title+', setting to invalid value');
+test(function(){
+ var video = document.createElement('video');
+ video.crossOrigin = 'ANONYMOUS';
+ assert_equals(video.getAttribute('crossorigin'), 'ANONYMOUS');
+}, document.title+', setting to uppercase ANONYMOUS');
+test(function(){
+ var video = document.createElement('video');
+ video.crossOrigin = 'use-credentials';
+ assert_equals(video.getAttribute('crossorigin'), 'use-credentials');
+}, document.title+', setting to use-credentials');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/textTracks.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/textTracks.html
new file mode 100644
index 0000000000..0f183b7e15
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/textTracks.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>HTMLMediaElement.textTracks</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+var video = document.createElement('video');
+test(function(){
+ assert_equals(video.textTracks, video.textTracks);
+ assert_equals(video.textTracks.length, 0);
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/default.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/default.html
new file mode 100644
index 0000000000..05fd0f7f7d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/default.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<title>HTMLTrackElement.default</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var track = document.createElement('track');
+ assert_equals(track['default'], false);
+ assert_equals(track.getAttribute('default'), null);
+}, document.title + ' missing value');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('default', '');
+ assert_equals(track['default'], true);
+ assert_equals(track.getAttribute('default'), '');
+}, document.title + ' empty string content attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track['default'] = '';
+ assert_equals(track['default'], false);
+ assert_equals(track.getAttribute('default'), null);
+}, document.title + ' empty string IDL attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('default', 'foo');
+ assert_equals(track['default'], true);
+ assert_equals(track.getAttribute('default'), 'foo');
+}, document.title + ' foo in content attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track['default'] = 'foo';
+ assert_equals(track['default'], true);
+ assert_equals(track.getAttribute('default'), '');
+}, document.title + ' foo in IDL attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track['default'] = true;
+ assert_equals(track['default'], true);
+ assert_equals(track.getAttribute('default'), '');
+}, document.title + ' true in IDL attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('default', '');
+ track['default'] = false;
+ assert_equals(track['default'], false);
+ assert_equals(track.getAttribute('default'), null);
+}, document.title + ' false in IDL attribute');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/kind.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/kind.html
new file mode 100644
index 0000000000..78c3bff51a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/kind.html
@@ -0,0 +1,146 @@
+<!doctype html>
+<title>HTMLTrackElement.kind</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var track = document.createElement('track');
+ assert_equals(track.kind, 'subtitles');
+ assert_equals(track.getAttribute('kind'), null);
+}, document.title + ' missing value');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('kind', 'invalid');
+ assert_equals(track.kind, 'metadata');
+ assert_equals(track.getAttribute('kind'), 'invalid');
+}, document.title + ' invalid value in content attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('kind', 'CAPTIONS');
+ assert_equals(track.kind, 'captions');
+ assert_equals(track.getAttribute('kind'), 'CAPTIONS');
+}, document.title + ' content attribute uppercase');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('kind', 'CAPT\u0130ONS');
+ assert_equals(track.kind, 'metadata');
+ assert_equals(track.getAttribute('kind'), 'CAPT\u0130ONS');
+}, document.title + ' content attribute with uppercase turkish I (with dot)');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('kind', 'capt\u0131ons');
+ assert_equals(track.kind, 'metadata');
+ assert_equals(track.getAttribute('kind'), 'capt\u0131ons');
+}, document.title + ' content attribute with lowercase turkish i (dotless)');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('kind', 'subtitles');
+ assert_equals(track.kind, 'subtitles');
+ assert_equals(track.getAttribute('kind'), 'subtitles');
+}, document.title + ' content attribute "subtitles"');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('kind', 'captions');
+ assert_equals(track.kind, 'captions');
+ assert_equals(track.getAttribute('kind'), 'captions');
+}, document.title + ' content attribute "captions"');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('kind', 'descriptions');
+ assert_equals(track.kind, 'descriptions');
+ assert_equals(track.getAttribute('kind'), 'descriptions');
+}, document.title + ' content attribute "descriptions"');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('kind', 'chapters');
+ assert_equals(track.kind, 'chapters');
+ assert_equals(track.getAttribute('kind'), 'chapters');
+}, document.title + ' content attribute "chapters"');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('kind', 'metadata');
+ assert_equals(track.kind, 'metadata');
+ assert_equals(track.getAttribute('kind'), 'metadata');
+}, document.title + ' content attribute "metadata"');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('kind', 'captions\u0000');
+ assert_equals(track.kind, 'metadata');
+ assert_equals(track.getAttribute('kind'), 'captions\u0000');
+}, document.title + ' content attribute "captions\\u0000"');
+
+test(function(){
+ var track = document.createElement('track');
+ track.kind = 'subtitles';
+ assert_equals(track.getAttribute('kind'), 'subtitles');
+ assert_equals(track.kind, 'subtitles');
+}, document.title + ' setting IDL attribute to "subtitles"');
+
+test(function(){
+ var track = document.createElement('track');
+ track.kind = 'captions';
+ assert_equals(track.getAttribute('kind'), 'captions');
+ assert_equals(track.kind, 'captions');
+}, document.title + ' setting IDL attribute to "captions"');
+
+test(function(){
+ var track = document.createElement('track');
+ track.kind = 'descriptions';
+ assert_equals(track.getAttribute('kind'), 'descriptions');
+ assert_equals(track.kind, 'descriptions');
+}, document.title + ' setting IDL attribute to "descriptions"');
+
+test(function(){
+ var track = document.createElement('track');
+ track.kind = 'chapters';
+ assert_equals(track.getAttribute('kind'), 'chapters');
+ assert_equals(track.kind, 'chapters');
+}, document.title + ' setting IDL attribute to "chapters"');
+
+test(function(){
+ var track = document.createElement('track');
+ track.kind = 'metadata';
+ assert_equals(track.getAttribute('kind'), 'metadata');
+ assert_equals(track.kind, 'metadata');
+}, document.title + ' setting IDL attribute to "metadata"');
+
+test(function(){
+ var track = document.createElement('track');
+ track.kind = 'CAPTIONS';
+ assert_equals(track.getAttribute('kind'), 'CAPTIONS');
+ assert_equals(track.kind, 'captions');
+}, document.title + ' setting IDL attribute to "CAPTIONS"');
+
+test(function(){
+ var track = document.createElement('track');
+ track.kind = 'CAPT\u0130ONS';
+ assert_equals(track.getAttribute('kind'), 'CAPT\u0130ONS');
+ assert_equals(track.kind, 'metadata');
+}, document.title + ' setting IDL attribute with uppercase turkish I (with dot)');
+
+test(function(){
+ var track = document.createElement('track');
+ track.kind = 'capt\u0131ons';
+ assert_equals(track.getAttribute('kind'), 'capt\u0131ons');
+ assert_equals(track.kind, 'metadata');
+}, document.title + ' setting IDL attribute with lowercase turkish I (dotless)');
+
+test(function(){
+ var track = document.createElement('track');
+ track.kind = 'captions\u0000';
+ assert_equals(track.getAttribute('kind'), 'captions\u0000');
+ assert_equals(track.kind, 'metadata');
+}, document.title + ' setting IDL attribute with \\u0000');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/label.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/label.html
new file mode 100644
index 0000000000..b2360315cf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/label.html
@@ -0,0 +1,83 @@
+<!doctype html>
+<title>HTMLTrackElement.label</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var track = document.createElement('track');
+ assert_equals(track.label, '');
+ assert_equals(track.getAttribute('label'), null);
+}, document.title + ' missing value');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('label', '');
+ assert_equals(track.label, '');
+ assert_equals(track.getAttribute('label'), '');
+}, document.title + ' empty string content attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.label = '';
+ assert_equals(track.label, '');
+ assert_equals(track.getAttribute('label'), '');
+}, document.title + ' empty string IDL attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('label', 'foo');
+ assert_equals(track.label, 'foo');
+ assert_equals(track.getAttribute('label'), 'foo');
+}, document.title + ' lowercase content attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('label', 'FOO');
+ assert_equals(track.label, 'FOO');
+ assert_equals(track.getAttribute('label'), 'FOO');
+}, document.title + ' uppercase content attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('label', '\u0000');
+ assert_equals(track.label, '\u0000');
+ assert_equals(track.getAttribute('label'), '\u0000');
+}, document.title + '\\u0000 in content attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.label = 'foo';
+ assert_equals(track.label, 'foo');
+ assert_equals(track.getAttribute('label'), 'foo');
+}, document.title + ' lowercase IDL attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.label = 'FOO';
+ assert_equals(track.label, 'FOO');
+ assert_equals(track.getAttribute('label'), 'FOO');
+}, document.title + ' uppercase IDL attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('label', ' foo \n');
+ assert_equals(track.label, ' foo \n');
+ assert_equals(track.getAttribute('label'), ' foo \n');
+}, document.title + ' whitespace in content attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.label = ' foo \n';
+ assert_equals(track.label, ' foo \n');
+ assert_equals(track.getAttribute('label'), ' foo \n');
+}, document.title + ' whitespace in IDL attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.label = '\u0000';
+ assert_equals(track.label, '\u0000');
+ assert_equals(track.getAttribute('label'), '\u0000');
+}, document.title + ' \\u0000 in IDL attribute');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/readyState.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/readyState.html
new file mode 100644
index 0000000000..cde21e694e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/readyState.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>HTMLTrackElement.readyState</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var track = document.createElement('track');
+ assert_equals(track.readyState, 0);
+}, document.title + ' default value');
+
+test(function(){
+ assert_equals(HTMLTrackElement.NONE, 0);
+ assert_equals(HTMLTrackElement.LOADING, 1);
+ assert_equals(HTMLTrackElement.LOADED, 2);
+ assert_equals(HTMLTrackElement.ERROR, 3);
+}, document.title + ' values');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/src.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/src.html
new file mode 100644
index 0000000000..4089913cbd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/src.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<title>HTMLTrackElement.src</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var track = document.createElement('track');
+ assert_equals(track.src, '');
+ assert_equals(track.getAttribute('src'), null);
+}, document.title + ' missing value');
+
+function resolve(url) {
+ var link = document.createElement('a');
+ link.setAttribute('href', url);
+ return link.href;
+}
+
+var tests = [
+ {input:'', expectedIDL:resolve(''), desc:'empty string'},
+ {input:'http://foo bar', expectedIDL:'http://foo bar', desc:'unresolvable value'},
+ {input:'test', expectedIDL:resolve('test'), desc:'resolvable value'},
+ // Leading and trailing C0 controls and space is stripped per url spec.
+ {input:'\u0000', expectedIDL:resolve(''), desc:'\\u0000'},
+ {input:'foo\u0000bar', expectedIDL:resolve('foo%00bar'), desc:'foo\\u0000bar'},
+];
+
+tests.forEach(function(t) {
+ test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('src', t.input);
+ assert_equals(track.src, t.expectedIDL);
+ assert_equals(track.getAttribute('src'), t.input);
+ }, [document.title, t.desc, 'in content attribute'].join(' '));
+
+ test(function(){
+ var track = document.createElement('track');
+ track.src = t.input;
+ assert_equals(track.src, t.expectedIDL);
+ assert_equals(track.getAttribute('src'), t.input);
+ }, [document.title, 'assigning', t.desc, 'to IDL attribute'].join(' '));
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/srclang.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/srclang.html
new file mode 100644
index 0000000000..b5071e0c36
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/srclang.html
@@ -0,0 +1,82 @@
+<!doctype html>
+<title>HTMLTrackElement.srclang</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var track = document.createElement('track');
+ assert_equals(track.srclang, '');
+ assert_equals(track.getAttribute('srclang'), null);
+}, document.title + ' missing value');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('srclang', '');
+ assert_equals(track.srclang, '');
+ assert_equals(track.getAttribute('srclang'), '');
+}, document.title + ' empty string content attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.srclang = '';
+ assert_equals(track.srclang, '');
+ assert_equals(track.getAttribute('srclang'), '');
+}, document.title + ' empty string IDL attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('srclang', 'foo');
+ assert_equals(track.srclang, 'foo');
+ assert_equals(track.getAttribute('srclang'), 'foo');
+}, document.title + ' lowercase content attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('srclang', 'FOO');
+ assert_equals(track.srclang, 'FOO');
+ assert_equals(track.getAttribute('srclang'), 'FOO');
+}, document.title + ' uppercase content attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('srclang', '\u0000');
+ assert_equals(track.srclang, '\u0000');
+ assert_equals(track.getAttribute('srclang'), '\u0000');
+}, document.title + ' \\u0000 content attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.srclang = 'foo';
+ assert_equals(track.srclang, 'foo');
+ assert_equals(track.getAttribute('srclang'), 'foo');
+}, document.title + ' lowercase IDL attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.srclang = 'FOO';
+ assert_equals(track.srclang, 'FOO');
+ assert_equals(track.getAttribute('srclang'), 'FOO');
+}, document.title + ' uppercase IDL attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('srclang', ' foo \n');
+ assert_equals(track.srclang, ' foo \n');
+ assert_equals(track.getAttribute('srclang'), ' foo \n');
+}, document.title + ' whitespace in content attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.srclang = ' foo \n';
+ assert_equals(track.srclang, ' foo \n');
+ assert_equals(track.getAttribute('srclang'), ' foo \n');
+}, document.title + ' whitespace in IDL attribute');
+
+test(function(){
+ var track = document.createElement('track');
+ track.srclang = '\u0000';
+ assert_equals(track.srclang, '\u0000');
+ assert_equals(track.getAttribute('srclang'), '\u0000');
+}, document.title + ' \\u0000 in IDL attribute');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/track.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/track.html
new file mode 100644
index 0000000000..1de0a88046
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/track.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<title>HTMLTrackElement.track</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var track = document.createElement('track');
+ assert_equals(track.track, track.track, 'same object should be returned');
+ assert_true(track.track instanceof TextTrack, 'returned object should be a TextTrack');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/activeCues.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/activeCues.html
new file mode 100644
index 0000000000..7a57826f30
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/activeCues.html
@@ -0,0 +1,101 @@
+<!doctype html>
+<title>TextTrack.activeCues</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/common/media.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ window.t1 = video.addTextTrack('subtitles');
+ window.track = document.createElement('track');
+ track['default'] = true;
+ video.appendChild(track);
+ window.t2 = track.track;
+ t2.mode = 'showing';
+ window.t1_cues = t1.activeCues;
+ window.t2_cues = t2.activeCues;
+ document.body.appendChild(video);
+ if (!t1)
+ throw new Error('t1 was undefined')
+});
+function smoke_test() {
+ assert_true('HTMLTrackElement' in window, 'track not supported');
+}
+
+test(function(){
+ smoke_test();
+ assert_equals(t1.activeCues, t1_cues, 't1.activeCues should return same object');
+ assert_equals(t2.activeCues, t2_cues, 't2.activeCues should return same object');
+ assert_not_equals(t1.activeCues, t2.activeCues, 't1.activeCues and t2.activeCues should be different objects');
+ assert_not_equals(t1.activeCues, null, 't1.activeCues should not be null');
+ assert_not_equals(t2.activeCues, null, 't2.activeCues should not be null');
+ assert_equals(t1.activeCues.length, 0, 't1.activeCues should have length 0');
+ assert_equals(t2.activeCues.length, 0, 't2.activeCues should have length 0');
+}, document.title+', empty list');
+test(function(){
+ smoke_test();
+ var c = new VTTCue(0, 1, "text");
+ t1.addCue(c);
+ assert_equals(t1.activeCues, t1_cues, "t1.activeCues should return same object");
+ assert_equals(t1.activeCues.length, 0, "t1.activeCues.length");
+ var c2 = new VTTCue(1, 2, "text2");
+ t1.addCue(c2);
+ assert_equals(t1.activeCues, t1_cues, "t1.activeCues should return the same object after adding a second cue");
+ assert_equals(t1.activeCues.length, 0, "t1.activeCues.length after adding a second cue");
+}, document.title+', after addCue()');
+test(function(){
+ smoke_test();
+ t1.mode = 'showing';
+ assert_equals(t1.activeCues, t1_cues, "t1.activeCues should return the same object after setting mode to showing");
+ t1.mode = 'hidden';
+ assert_equals(t1.activeCues, t1_cues, "t1.activeCues should return the same object after setting mode to hidden");
+ t1.mode = 'disabled';
+ assert_equals(t1.activeCues, null, "t1.activeCues should be null when mode is disabled");
+ assert_equals(t1_cues.length, 0, "t1_cues should still be intact after setting mode to disabled");
+}, document.title+', different modes');
+
+// ok now let's load in a video
+var test1 = async_test(document.title+', video loading');
+var test2 = async_test(document.title+', video playing');
+var test3 = async_test(document.title+', adding cue during playback');
+test1.step(smoke_test);
+test2.step(smoke_test);
+test3.step(smoke_test);
+test1.step(function(){
+ t1.mode = 'showing';
+ video.onloadeddata = test1.step_func(function(e) {
+ video.onplaying = test2.step_func(function(e) {
+ try {
+ assert_equals(t1.activeCues, t1_cues, "t1.activeCues should return the same object after playing a video");
+ assert_equals(t1.activeCues.length, 1, "t1.activeCues.length after the video has started playing");
+ } catch(ex) {
+ test2.step(function() { throw ex; });
+ test3.step(function() { assert_unreached(); });
+ return;
+ }
+ test3.step(function(){
+ var c3 = new VTTCue(0, 2, "text3");
+ t1.addCue(c3);
+ assert_equals(t1.activeCues.length, 2, "t1.activeCues.length should be changed immediately");
+ test3.done();
+ });
+ test2.done();
+ });
+ try {
+ assert_equals(t1.activeCues, t1_cues, "t1.activeCues should return the same object after loading a video");
+ assert_equals(t2.activeCues, t2_cues, "t2.activeCues should return the same object after loading a video");
+ assert_equals(t1.activeCues.length, 0, "t1.activeCues.length before the video has started playing");
+ assert_equals(t2.activeCues.length, 0, "t1.activeCues.length before the video has started playing");
+ } catch(ex) {
+ test1.step(function() { throw ex; });
+ test2.step(function() { assert_unreached(); });
+ test3.step(function() { assert_unreached(); });
+ return;
+ }
+ video.play();
+ test1.done();
+ });
+ video.src = getVideoURI("/media/movie_5");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/addCue.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/addCue.html
new file mode 100644
index 0000000000..622ec4abfd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/addCue.html
@@ -0,0 +1,68 @@
+<!doctype html>
+<title>TextTrack.addCue()</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ document.body.appendChild(video);
+});
+test(function() {
+ var t1 = video.addTextTrack('subtitles');
+ var t2 = video.addTextTrack('subtitles');
+ var c1 = new VTTCue(0, 1, 'text1');
+ t1.addCue(c1);
+ t2.addCue(c1);
+ assert_equals(c1.track, t2);
+}, document.title+', adding a cue to two different tracks');
+test(function() {
+ var t1 = video.addTextTrack('subtitles');
+ var c1 = new VTTCue(0, 1, 'text1');
+ t1.addCue(c1);
+ assert_equals(c1.track, t1);
+ t1.addCue(c1);
+ assert_equals(c1.track, t1);
+}, document.title+', adding a cue to a track twice');
+test(function() {
+ var t1 = video.addTextTrack('subtitles');
+ var t2 = video.addTextTrack('subtitles');
+ var c1 = new VTTCue(0, 1, 'text1');
+ t1.addCue(c1);
+ assert_equals(c1.track, t1);
+ t1.removeCue(c1);
+ assert_equals(c1.track, null);
+ t2.addCue(c1);
+ assert_equals(c1.track, t2);
+}, document.title+', adding a removed cue to a different track');
+test(function() {
+ var t1 = video.addTextTrack('subtitles');
+ var c1 = new VTTCue(0, 1, 'text1');
+ t1.addCue(c1);
+ assert_equals(t1.cues.length, 1, 't1.cues.length after first addition');
+ t1.removeCue(c1);
+ assert_equals(t1.cues.length, 0, 't1.cues.length after removal');
+ t1.addCue(c1);
+ assert_equals(t1.cues.length, 1, 't1.cues.length after second addition');
+}, document.title+', adding an associated but removed cue to the same track');
+
+var t = async_test(document.title+', adding a cue associated with a track element to other track');
+t.step(function(){
+ var t1 = video.addTextTrack('subtitles');
+ var track = document.createElement('track');
+ track.onload = t.step_func(function(){
+ var cue = track.track.cues[0];
+ track.track.removeCue(cue);
+ t1.addCue(cue);
+ assert_equals(cue.track, t1);
+ t.done();
+ });
+ track.onerror = t.step_func(function() {
+ assert_unreached('got error event');
+ });
+ track.src= 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00:00.000 --> 00:00:01.000\ntest\n');
+ track.kind = 'subtitles';
+ track.track.mode = 'hidden';
+ video.appendChild(track);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/constants.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/constants.html
new file mode 100644
index 0000000000..3c8046cdc4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/constants.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<title>TextTrack constants</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ window.t1 = video.addTextTrack('subtitles');
+});
+test(function(){
+ assert_equals(t1.DISABLED, undefined, "t1.DISABLED");
+ assert_equals(t1.HIDDEN, undefined, "t1.HIDDEN");
+ assert_equals(t1.SHOWING, undefined, "t1.SHOWING");
+ assert_equals(TextTrack.prototype.DISABLED, undefined, "TextTrack.prototype.DISABLED");
+ assert_equals(TextTrack.prototype.HIDDEN, undefined, "TextTrack.prototype.HIDDEN");
+ assert_equals(TextTrack.prototype.SHOWING, undefined, "TextTrack.prototype.SHOWING");
+ assert_equals(TextTrack.DISABLED, undefined, "TextTrack.DISABLED");
+ assert_equals(TextTrack.HIDDEN, undefined, "TextTrack.HIDDEN");
+ assert_equals(TextTrack.SHOWING, undefined, "TextTrack.SHOWING");
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/cues.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/cues.html
new file mode 100644
index 0000000000..4b7808c963
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/cues.html
@@ -0,0 +1,100 @@
+<!doctype html>
+<title>TextTrack.cues</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var video = document.createElement('video');
+ var t1 = video.addTextTrack('subtitles');
+ assert_equals(t1.cues, t1.cues, 't1.cues should return same object');
+ assert_not_equals(t1.cues, null, 't1.cues should not be null');
+ assert_true(t1.cues instanceof TextTrackCueList, 't1.cues instanceof TextTrackCueList');
+ assert_equals(t1.cues.length, 0, 't1.cues.length');
+}, document.title+', empty list');
+
+function addCue(texttrack, start, end, text, id) {
+ var c = new VTTCue(start, end, text);
+ c.id = id;
+ texttrack.addCue(c);
+ return c;
+}
+
+test(function(){
+ var video = document.createElement('video');
+ var t1 = video.addTextTrack('subtitles');
+ var t1_cues = t1.cues;
+ var c = addCue(t1, 0, 1, 'text', 'id');
+ assert_equals(t1.cues, t1_cues, "t1.cues should return same object");
+ assert_equals(t1.cues.length, 1, "t1.cues.length");
+ var c2 = addCue(t1, 1, 2, 'text2', 'id2');
+ assert_equals(t1.cues, t1_cues, "t1.cues should return the same object after adding a second cue");
+ assert_equals(t1.cues.length, 2, "t1.cues.length after adding a second cue");
+ assert_equals(t1.cues[0].id, "id");
+ assert_equals(t1.cues[1].id, "id2");
+}, document.title+', after addCue()');
+
+test(function(){
+ var video = document.createElement('video');
+ var t1 = video.addTextTrack('subtitles');
+ var t1_cues = t1.cues;
+ var c = addCue(t1, 0, 1, 'text', 'id');
+ var c2 = addCue(t1, 1, 2, 'text2', 'id2');
+ t1.mode = 'showing';
+ assert_equals(t1.cues, t1_cues, "t1.cues should return the same object after setting mode to 'showing'");
+ t1.mode = 'hidden';
+ assert_equals(t1.cues, t1_cues, "t1.cues should return the same object after setting mode to 'hidden'");
+ t1.mode = 'disabled';
+ assert_equals(t1.cues, null, "t1.cues should be null when mode is 'disabled'");
+ assert_equals(t1_cues.length, 2, "t1_cues should still be intact after setting mode to 'disabled'");
+ assert_equals(t1_cues[0].id, "id", "t1_cues first cue should still be intact after setting mode to 'disabled'");
+ assert_equals(t1_cues[1].id, "id2", "t1_cues second cue should still be intact after setting mode to 'disabled'");
+ t1.mode = 'hidden';
+ assert_equals(t1.cues, t1_cues, "t1.cues should return the same object after setting mode to 'disabled' and then 'hidden'");
+ t1.mode = 'disabled';
+ assert_equals(t1.cues, null, "t1.cues should be null when mode is set to 'disabled' again");
+ assert_equals(t1_cues.length, 2, "t1_cues should still be intact after setting mode to 'disabled' again");
+ assert_equals(t1_cues[0].id, "id", "t1_cues first cue should still be intact after setting mode to 'disabled' again");
+ assert_equals(t1_cues[1].id, "id2", "t1_cues second cue should still be intact after setting mode to 'disabled' again");
+ t1.mode = 'showing';
+ assert_equals(t1.cues, t1_cues, "t1.cues should return the same object after setting mode to 'disabled' and then 'showing'");
+}, document.title+', different modes');
+
+test(function(){
+ var video = document.createElement('video');
+ var t1 = video.addTextTrack('subtitles');
+ var t1_cues = t1.cues;
+ var c = addCue(t1, 0, 1, 'text', 'id');
+ var c2 = addCue(t1, 1, 2, 'text2', 'id2');
+ t1.mode = 'showing';
+ t1.cues[1].startTime = 0; // this should change the text track cue order
+ assert_equals(t1.cues[0].id, 'id2');
+ assert_equals(t1.cues[1].id, 'id');
+ t1.cues[0].startTime = 0.5; // this should change it back
+ assert_equals(t1.cues[0].id, 'id');
+ assert_equals(t1.cues[1].id, 'id2');
+}, document.title+', changing order');
+
+async_test(function(){
+ var video = document.createElement('video');
+ var t1 = video.addTextTrack('subtitles');
+ var t1_cues = t1.cues;
+ t1.mode = 'hidden';
+ var track = document.createElement('track');
+ track['default'] = true;
+ video.appendChild(track); // queues a task to "honor user preferences...", media element event task source
+ var t2 = track.track;
+ assert_equals(t2.cues, null, 't2.cues should be null');
+ // We need to wait until the "honor user preferences..." steps have run so we invoke play()
+ // which queues an event with the same task source.
+ video.onplay = this.step_func(function(){
+ assert_equals(t2.cues, t2.cues, 't2.cues should return same object');
+ assert_not_equals(t1.cues, t2.cues, 't1.cues and t2.cues should be different objects');
+ assert_not_equals(t2.cues, null, 't2.cues should not be null');
+ assert_true(t2.cues instanceof TextTrackCueList, 't2.cues instanceof TextTrackCueList');
+ assert_equals(t2.cues.length, 0, 't2.cues should have length 0');
+ this.done();
+ });
+ video.play(); // queues a task to fire 'play', media element event task source
+}, document.title+', default attribute');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/kind.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/kind.html
new file mode 100644
index 0000000000..d5dbc8342c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/kind.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<title>TextTrack.kind</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var video = document.createElement('video');
+ var t1 = video.addTextTrack('subtitles');
+ var t2 = video.addTextTrack('captions');
+ var t3 = video.addTextTrack('descriptions');
+ var t4 = video.addTextTrack('chapters');
+ var t5 = video.addTextTrack('metadata');
+ assert_equals(t1.kind, 'subtitles');
+ assert_equals(t2.kind, 'captions');
+ assert_equals(t3.kind, 'descriptions');
+ assert_equals(t4.kind, 'chapters');
+ assert_equals(t5.kind, 'metadata');
+}, document.title+', addTextTrack');
+test(function(){
+ var track = document.createElement('track');
+ track.setAttribute('kind', 'CAPTIONS');
+ var t = track.track;
+ assert_equals(t.kind, 'captions');
+}, document.title+', track element');
+test(function(){
+ var track = document.createElement('track');
+ track.kind = 'captions\u0000';
+ assert_equals(track.track.kind, 'metadata');
+}, document.title+', \\u0000');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/label.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/label.html
new file mode 100644
index 0000000000..c60e85c21a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/label.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<title>TextTrack.label</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ window.t1 = video.addTextTrack('subtitles', 'foo');
+ window.track = document.createElement('track');
+ track.setAttribute('label', 'bar');
+ video.appendChild(track);
+ window.t2 = track.track;
+});
+test(function(){
+ assert_equals(t1.label, 'foo');
+ assert_equals(t2.label, 'bar');
+ track.label = 'baz';
+ assert_equals(t2.label, 'baz');
+ track.removeAttribute('label');
+ assert_equals(t2.label, '');
+});
+test(function(){
+ track.label = '\u0000a';
+ assert_equals(t2.label, '\u0000a');
+ track.setAttribute('label', '\u0000b', 'IDL attribute');
+ assert_equals(t2.label, '\u0000b', 'content attribute');
+}, document.title+', \\u0000');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/language.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/language.html
new file mode 100644
index 0000000000..eda3653de0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/language.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<title>TextTrack.language</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ window.t1 = video.addTextTrack('subtitles', 'foo', 'foo');
+ window.track = document.createElement('track');
+ track.setAttribute('srclang', 'bar');
+ video.appendChild(track);
+ window.t2 = track.track;
+});
+test(function(){
+ assert_equals(t1.language, 'foo');
+ assert_equals(t2.language, 'bar');
+ track.srclang = 'baz';
+ assert_equals(t2.language, 'baz');
+ track.removeAttribute('srclang');
+ assert_equals(t2.language, '');
+});
+test(function(){
+ track.srclang = '\u0000a';
+ assert_equals(t2.language, '\u0000a', 'IDL attribute');
+ track.setAttribute('srclang', '\u0000b');
+ assert_equals(t2.language, '\u0000b', 'content attribute');
+}, document.title+', \\u0000');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/mode.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/mode.html
new file mode 100644
index 0000000000..9f94156706
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/mode.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<title>TextTrack.mode</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var track = document.createElement('track');
+ assert_equals(track.track.mode, 'disabled', 'initial');
+ track.track.mode = 1;
+ assert_equals(track.track.mode, 'disabled', '1');
+ track.track.mode = '';
+ assert_equals(track.track.mode, 'disabled', '""');
+ track.track.mode = null;
+ assert_equals(track.track.mode, 'disabled', 'null');
+ track.track.mode = undefined;
+ assert_equals(track.track.mode, 'disabled', 'undefined');
+ track.track.mode = 'showing';
+ assert_equals(track.track.mode, 'showing', 'showing (correct value)');
+ track.track.mode = 'DISABLED';
+ assert_equals(track.track.mode, 'showing', '"DISABLED"');
+ track.track.mode = 'd\u0130sabled'; // dotted uppercase i
+ assert_equals(track.track.mode, 'showing', '"d\u0130sabled" (dotted uppercase i)');
+ track.track.mode = 'd\u0131sabled'; // dotless lowercase i
+ assert_equals(track.track.mode, 'showing', '"d\u0131sabled" (dotless lowercase i)');
+ track.track.mode = 'disabled ';
+ assert_equals(track.track.mode, 'showing', '"disabled "');
+ track.track.mode = ' disabled';
+ assert_equals(track.track.mode, 'showing', '" disabled"');
+ track.track.mode = {};
+ assert_equals(track.track.mode, 'showing', '{}');
+ track.track.mode = 'HIDDEN';
+ assert_equals(track.track.mode, 'showing', '"HIDDEN"');
+ track.track.mode = 'h\u0130dden'; // dotted uppercase i
+ assert_equals(track.track.mode, 'showing', '"h\u0130dden" (dotted uppercase i)');
+ track.track.mode = 'h\u0131dden'; // dotless lowercase i
+ assert_equals(track.track.mode, 'showing', '"h\u0131dden" (dotless lowercase i)');
+}, document.title+', wrong value');
+test(function() {
+ var track = document.createElement('track');
+ assert_equals(track.track.mode, 'disabled', 'initial');
+ track.track.mode = 'disabled'; // no-op
+ assert_equals(track.track.mode, 'disabled', 'disabled (1)');
+ track.track.mode = 'hidden';
+ assert_equals(track.track.mode, 'hidden', 'hidden (1)');
+ track.track.mode = 'hidden'; // no-op
+ assert_equals(track.track.mode, 'hidden', 'hidden (2)');
+ track.track.mode = 'showing';
+ assert_equals(track.track.mode, 'showing', 'showing (1)');
+ track.track.mode = 'showing'; // no-op
+ assert_equals(track.track.mode, 'showing', 'showing (2)');
+ track.track.mode = {toString:function() { return 'disabled'; }};
+ assert_equals(track.track.mode, 'disabled', '{toString:...}');
+}, document.title+', correct value');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/oncuechange.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/oncuechange.html
new file mode 100644
index 0000000000..16c76f9484
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/oncuechange.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>TextTrack.oncuechange</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ window.t1 = video.addTextTrack('subtitles');
+ window.ev = new Event('cuechange');
+ window.ran = false;
+ window.cb = function() { ran = true; };
+});
+test(function(){
+ assert_equals(t1.oncuechange, null);
+ t1.oncuechange = cb;
+ t1.dispatchEvent(ev);
+ assert_true(ran);
+ t1.oncuechange = null;
+ ran = false;
+ t1.dispatchEvent(ev);
+ assert_false(ran);
+});
+test(function(){
+ t1.addEventListener('cuechange', cb, false);
+ t1.dispatchEvent(ev);
+ assert_true(ran);
+ t1.removeEventListener('cuechange', cb, false);
+ ran = false;
+ t1.dispatchEvent(ev);
+ assert_false(ran);
+}, 'TextTrack.addEventListener/removeEventListener');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/removeCue.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/removeCue.html
new file mode 100644
index 0000000000..09043458cc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrack/removeCue.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<title>TextTrack.removeCue()</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ document.body.appendChild(video);
+});
+test(function() {
+ var t1 = video.addTextTrack('subtitles');
+ var t2 = video.addTextTrack('subtitles');
+ var c1 = new VTTCue(0, 1, 'text1');
+ assert_throws_dom("NOT_FOUND_ERR", function() {
+ t1.removeCue(c1);
+ }, 'standalone');
+ t1.addCue(c1);
+ assert_throws_dom("NOT_FOUND_ERR", function() {
+ t2.removeCue(c1);
+ }, 'listed in t1, remove from t2');
+ t1.removeCue(c1);
+ assert_throws_dom("NOT_FOUND_ERR", function() {
+ t1.removeCue(c1);
+ }, 'standalone, remove from t1');
+ assert_throws_dom("NOT_FOUND_ERR", function() {
+ t2.removeCue(c1);
+ }, 'standalone, remove from t2');
+}, document.title+', two elementless tracks');
+var t = async_test(document.title+', cue from track element');
+t.step(function(){
+ var t1 = video.addTextTrack('subtitles');
+ var track = document.createElement('track');
+ track.onload = t.step_func(function(){
+ var cue = track.track.cues[0];
+ assert_throws_dom('NOT_FOUND_ERR', function() { t1.removeCue(cue); }, 'listed in track.track, remove from t1');
+ track.track.removeCue(cue);
+ assert_throws_dom('NOT_FOUND_ERR', function() { track.track.removeCue(cue); }, 'standalone, remove from track.track');
+ assert_throws_dom('NOT_FOUND_ERR', function() { t1.removeCue(cue); }, 'standalone, remove from t1');
+ t.done();
+ });
+ track.onerror = t.step_func(function() {
+ assert_unreached('got error event');
+ });
+ track.src= 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00:00.000 --> 00:00:01.000\ntest\n');
+ track.kind = 'subtitles';
+ track.track.mode = 'hidden';
+ video.appendChild(track);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/constructor.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/constructor.html
new file mode 100644
index 0000000000..8ee9adb1c0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/constructor.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>TextTrackCue constructor</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ test(function()
+ {
+ assert_not_equals(TextTrackCue, VTTCue);
+ }, "TextTrackCue and VTTCue are separate interfaces");
+ test(function()
+ {
+ assert_throws_js(TypeError, function()
+ {
+ new TextTrackCue(0, 0, "");
+ });
+ }, "TextTrackCue constructor should not be supported");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/endTime.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/endTime.html
new file mode 100644
index 0000000000..18b14bdfa9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/endTime.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<title>TextTrackCue.endTime</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ window.t1 = video.addTextTrack('subtitles');
+ document.body.appendChild(video);
+});
+test(function(){
+ var c1 = new VTTCue(-2, -1, 'text1');
+ assert_equals(c1.endTime, -1);
+ c1.endTime = c1.endTime;
+ assert_equals(c1.endTime, -1);
+ assert_throws_js(TypeError, function(){ c1.endTime = NaN; });
+ c1.endTime = +Infinity;
+ assert_equals(c1.endTime, +Infinity);
+ assert_throws_js(TypeError, function(){ c1.endTime = -Infinity; });
+}, document.title+', script-created cue');
+
+var t_parsed = async_test(document.title+', parsed cue');
+t_parsed.step(function(){
+ var t = document.createElement('track');
+ t.onload = this.step_func(function(){
+ var c = t.track.cues;
+ assert_equals(c[0].endTime, 0.001);
+ assert_equals(c[1].endTime, 3600.001);
+ this.done();
+ });
+ t.onerror = this.step_func(function() {
+ assert_unreached('got error event');
+ });
+ t.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\ntest'+
+ '\n\nfoobar\n01:00:00.000 --> 01:00:00.001\ntest');
+ t.track.mode = 'showing';
+ video.appendChild(t);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/id.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/id.html
new file mode 100644
index 0000000000..a88f94766f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/id.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<title>TextTrackCue.id</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ window.t1 = video.addTextTrack('subtitles');
+ document.body.appendChild(video);
+});
+test(function(){
+ var c1 = new VTTCue(0, 1, 'text1');
+ c1.id = 'id1\r\n\u0000';
+ assert_equals(c1.id, 'id1\r\n\u0000');
+ c1.id = c1.id;
+ assert_equals(c1.id, 'id1\r\n\u0000');
+ c1.id = null;
+ assert_equals(c1.id, 'null');
+}, document.title+', script-created cue');
+
+var t_parsed = async_test(document.title+', parsed cue');
+t_parsed.step(function(){
+ var t = document.createElement('track');
+ t.onload = this.step_func(function(){
+ var c = t.track.cues;
+ assert_equals(c[0].id, '');
+ assert_equals(c[1].id, 'foobar');
+ this.done();
+ });
+ t.onerror = this.step_func(function() {
+ assert_unreached('got error event');
+ });
+ t.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\ntest'+
+ '\n\nfoobar\n00:00:00.000 --> 00:00:00.001\ntest');
+ t.track.mode = 'showing';
+ video.appendChild(t);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/onenter.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/onenter.html
new file mode 100644
index 0000000000..17deed0530
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/onenter.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<title>TextTrackCue.onenter</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.c1 = new VTTCue(0, 1, 'text1');
+ window.ev = new Event('enter');
+ window.ran = false;
+ window.cb = function() { ran = true; };
+});
+test(function(){
+ assert_equals(c1.onenter, null, 'initial value');
+ c1.onenter = undefined;
+ assert_equals(c1.onenter, null, 'assigning undefined');
+ c1.onenter = cb;
+ assert_equals(c1.onenter, cb, 'assigning onenter');
+ c1.dispatchEvent(ev);
+ assert_true(ran, 'dispatching event');
+ c1.onenter = null;
+ assert_equals(c1.onenter, null, 'assigning null');
+ ran = false;
+ c1.dispatchEvent(ev);
+ assert_false(ran, 'dispatching event after nulling onenter');
+});
+test(function(){
+ c1.addEventListener('enter', cb, false);
+ c1.dispatchEvent(ev);
+ assert_true(ran);
+ c1.removeEventListener('enter', cb, false);
+ ran = false;
+ c1.dispatchEvent(ev);
+ assert_false(ran);
+}, 'TextTrackCue.addEventListener/removeEventListener');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/onexit.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/onexit.html
new file mode 100644
index 0000000000..815377e4d1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/onexit.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<title>TextTrackCue.onexit</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.c1 = new VTTCue(0, 1, 'text1');
+ window.ev = new Event('exit');
+ window.ran = false;
+ window.cb = function() { ran = true; };
+});
+test(function(){
+ assert_equals(c1.onexit, null, 'initial value');
+ c1.onexit = undefined;
+ assert_equals(c1.onexit, null, 'assigning undefined');
+ c1.onexit = cb;
+ assert_equals(c1.onexit, cb, 'assigning onexit');
+ c1.dispatchEvent(ev);
+ assert_true(ran, 'dispatching event');
+ c1.onexit = null;
+ assert_equals(c1.onexit, null, 'assigning null');
+ ran = false;
+ c1.dispatchEvent(ev);
+ assert_false(ran, 'dispatching event after nulling onexit');
+});
+test(function(){
+ c1.addEventListener('exit', cb, false);
+ c1.dispatchEvent(ev);
+ assert_true(ran);
+ c1.removeEventListener('exit', cb, false);
+ ran = false;
+ c1.dispatchEvent(ev);
+ assert_false(ran);
+}, 'TextTrackCue.addEventListener/removeEventListener');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/pauseOnExit.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/pauseOnExit.html
new file mode 100644
index 0000000000..31ea4c63b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/pauseOnExit.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<title>TextTrackCue.pauseOnExit</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ window.t1 = video.addTextTrack('subtitles');
+ document.body.appendChild(video);
+});
+test(function(){
+ var c1 = new VTTCue(0, 1, 'text1');
+ assert_equals(c1.pauseOnExit, false);
+ c1.pauseOnExit = null;
+ assert_equals(c1.pauseOnExit, false);
+ c1.pauseOnExit = 'foo';
+ assert_equals(c1.pauseOnExit, true);
+}, document.title+', script-created cue');
+
+var t_parsed = async_test(document.title+', parsed cue');
+t_parsed.step(function(){
+ var t = document.createElement('track');
+ t.onload = this.step_func(function(){
+ var c1 = t.track.cues[0];
+ assert_equals(c1.pauseOnExit, false);
+ c1.pauseOnExit = null;
+ assert_equals(c1.pauseOnExit, false);
+ c1.pauseOnExit = 'foo';
+ assert_equals(c1.pauseOnExit, true);
+ this.done();
+ });
+ t.onerror = this.step_func(function() {
+ assert_unreached('got error event');
+ });
+ t.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\ntest');
+ t.track.mode = 'showing';
+ video.appendChild(t);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/startTime.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/startTime.html
new file mode 100644
index 0000000000..7fba1df415
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/startTime.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<title>TextTrackCue.startTime</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ window.t1 = video.addTextTrack('subtitles');
+ document.body.appendChild(video);
+});
+test(function(){
+ var c1 = new VTTCue(-1, 1, 'text1');
+ assert_equals(c1.startTime, -1);
+ c1.startTime = c1.startTime;
+ assert_equals(c1.startTime, -1);
+ assert_throws_js(TypeError, function(){ c1.startTime = NaN; });
+ assert_throws_js(TypeError, function(){ c1.startTime = +Infinity; });
+ assert_throws_js(TypeError, function(){ c1.startTime = -Infinity; });
+}, document.title+', script-created cue');
+
+var t_parsed = async_test(document.title+', parsed cue');
+t_parsed.step(function(){
+ var t = document.createElement('track');
+ t.onload = this.step_func(function(){
+ var c = t.track.cues;
+ assert_equals(c[0].startTime, 0);
+ assert_equals(c[1].startTime, 3600);
+ this.done();
+ });
+ t.onerror = this.step_func(function() {
+ assert_unreached('got error event');
+ });
+ t.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\ntest'+
+ '\n\nfoobar\n01:00:00.000 --> 01:00:00.001\ntest');
+ t.track.mode = 'showing';
+ video.appendChild(t);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/track.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/track.html
new file mode 100644
index 0000000000..219e3e703b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/track.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<title>TextTrackCue.track</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ window.t1 = video.addTextTrack('subtitles');
+ document.body.appendChild(video);
+});
+test(function(){
+ var c1 = new VTTCue(0, 1, 'text1');
+ assert_equals(c1.track, null);
+ t1.addCue(c1);
+ assert_equals(c1.track, t1);
+ t1.removeCue(c1);
+ assert_equals(c1.track, null);
+}, document.title+', script-created cue');
+
+var t_parsed = async_test(document.title+', parsed cue');
+t_parsed.step(function(){
+ var t = document.createElement('track');
+ t.onload = this.step_func(function(){
+ var c = t.track.cues[0];
+ assert_equals(c.track, t.track);
+ t.track.removeCue(c);
+ assert_equals(c.track, null);
+ this.done();
+ });
+ t.onerror = this.step_func(function() {
+ assert_unreached('got error event');
+ });
+ t.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\ntest');
+ t.track.mode = 'showing';
+ video.appendChild(t);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCueList/getCueById.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCueList/getCueById.html
new file mode 100644
index 0000000000..8184189b05
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCueList/getCueById.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<title>TextTrackCueList.getCueById</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var video = document.createElement('video');
+ var t = video.addTextTrack('subtitles');
+ document.body.appendChild(video);
+ var cues = t.cues;
+ var c = new VTTCue(0, 1, 'text1');
+ t.addCue(c);
+ assert_equals(cues.getCueById(""), null, '""');
+ assert_equals(cues.getCueById(null), null, 'null');
+ assert_equals(cues.getCueById(undefined), null, 'undefined');
+}, document.title+ ', no id');
+test(function(){
+ var video = document.createElement('video');
+ var t = video.addTextTrack('subtitles');
+ document.body.appendChild(video);
+ var cues = t.cues;
+ var c = new VTTCue(0, 1, 'text1');
+ c.id = 'foo';
+ t.addCue(c);
+ assert_equals(cues.getCueById(""), null, '""');
+ assert_equals(cues.getCueById("foo"), c, '"foo"');
+ assert_equals(cues.getCueById({toString:function(){return "foo"}}), c, 'object');
+}, document.title+ ', id foo');
+test(function(){
+ var video = document.createElement('video');
+ var t = video.addTextTrack('subtitles');
+ document.body.appendChild(video);
+ var cues = t.cues;
+ var c = new VTTCue(0, 1, 'text1');
+ c.id = '1';
+ t.addCue(c);
+ assert_equals(cues.getCueById(""), null, '""');
+ assert_equals(cues.getCueById("1"), c, '"1"');
+ assert_equals(cues.getCueById(1), c, '1');
+}, document.title+ ', no 1');
+test(function(){
+ var video = document.createElement('video');
+ var t = video.addTextTrack('subtitles');
+ document.body.appendChild(video);
+ var cues = t.cues;
+ var c = new VTTCue(0, 1, 'text1');
+ c.id = 'a\u0000b';
+ t.addCue(c);
+ assert_equals(cues.getCueById("a\u0000b"), c, '"a\\u0000b"');
+ assert_equals(cues.getCueById("a"), null, '"a"');
+}, document.title+ ', id a\\u0000b');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCueList/getter.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCueList/getter.html
new file mode 100644
index 0000000000..8056d24543
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCueList/getter.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<title>TextTrackCueList getter</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ window.t1 = video.addTextTrack('subtitles');
+ document.body.appendChild(video);
+});
+test(function(){
+ var cues = t1.cues;
+ assert_equals(cues[0], undefined, 'cues[0] before');
+ var c1 = new VTTCue(0, 1, 'text1');
+ t1.addCue(c1);
+ assert_equals(cues[0], c1, 'cues[0]');
+ assert_equals(cues[1], undefined, 'cues[1]');
+ assert_equals(cues[-1], undefined, 'cues[-1]');
+ t1.removeCue(c1);
+ assert_equals(cues[0], undefined, 'cues[0] after');
+});
+test(function(){
+ var cues = t1.cues;
+ assert_equals(cues[0], undefined);
+ cues[0] = 'foo';
+ assert_equals(cues[0], undefined);
+ var c1 = new VTTCue(0, 1, 'text1');
+ t1.addCue(c1);
+ assert_equals(cues[0], c1);
+ cues[0] = 'foo';
+ assert_equals(cues[0], c1);
+ t1.removeCue(c1);
+}, document.title+', no indexed set/create');
+test(function(){
+ 'use strict';
+ var cues = t1.cues;
+ assert_equals(cues[0], undefined);
+ assert_throws_js(TypeError, function() { cues[0] = 'foo'; });
+ assert_equals(cues[0], undefined);
+ var c1 = new VTTCue(0, 1, 'text1');
+ t1.addCue(c1);
+ assert_equals(cues[0], c1);
+ assert_throws_js(TypeError, function() { cues[0] = 'foo'; });
+ assert_equals(cues[0], c1);
+ t1.removeCue(c1);
+}, document.title+', no indexed set/create (strict)');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCueList/length.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCueList/length.html
new file mode 100644
index 0000000000..91e6e7ff99
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackCueList/length.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>TextTrackCueList.length</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ window.t1 = video.addTextTrack('subtitles');
+ document.body.appendChild(video);
+});
+test(function(){
+ var cues = t1.cues;
+ assert_equals(cues.length, 0);
+ var c1 = new VTTCue(0, 1, 'text1');
+ t1.addCue(c1);
+ assert_equals(cues.length, 1);
+ t1.removeCue(c1);
+ assert_equals(cues.length, 0);
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/getTrackById.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/getTrackById.html
new file mode 100644
index 0000000000..b701dd5e73
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/getTrackById.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>TextTrackList.getTrackById</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var video = document.createElement('video');
+ var track1 = video.addTextTrack('subtitles');
+ var track2 = video.addTextTrack('subtitles');
+ assert_equals(track1.id, '');
+ assert_equals(track2.id, '');
+ assert_equals(video.textTracks.getTrackById(''), track1);
+ assert_equals(video.textTracks.getTrackById('fake-id'), null);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/getter.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/getter.html
new file mode 100644
index 0000000000..9baa459419
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/getter.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<title>TextTrackList getter</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ video.addTextTrack('subtitles', 'b');
+ window.track = document.createElement('track');
+ track.label = 'a';
+ video.appendChild(track);
+ video.addTextTrack('subtitles', 'c');
+});
+test(function(){
+ assert_equals(video.textTracks[0].label, 'a');
+ assert_equals(video.textTracks[1].label, 'b');
+ assert_equals(video.textTracks[2].label, 'c');
+});
+test(function(){
+ var track_before = video.textTracks[0];
+ video.textTracks[0] = 'foo';
+ assert_equals(video.textTracks[0], track_before);
+}, document.title+', no indexed set/create');
+test(function(){
+ 'use strict';
+ var track_before = video.textTracks[0];
+ assert_throws_js(TypeError, function(){ video.textTracks[0] = 'foo'; });
+ assert_equals(video.textTracks[0], track_before);
+}, document.title+', no indexed set/create (strict)');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/length.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/length.html
new file mode 100644
index 0000000000..7a24130d10
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/length.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>TextTrackList.length</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.video = document.createElement('video');
+ video.addTextTrack('subtitles');
+ window.track = document.createElement('track');
+ video.appendChild(track);
+ video.addTextTrack('subtitles');
+});
+test(function(){
+ assert_equals(video.textTracks.length, 3);
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/onaddtrack.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/onaddtrack.html
new file mode 100644
index 0000000000..114ca89046
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/onaddtrack.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>TextTrackList.onaddtrack</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.tracks = document.createElement('video').textTracks;
+ window.ev = new Event('addtrack');
+ window.ran = false;
+ window.cb = function() { ran = true; };
+});
+test(function(){
+ assert_equals(tracks.onaddtrack, null);
+ tracks.onaddtrack = cb;
+ assert_equals(tracks.onaddtrack, cb);
+ tracks.dispatchEvent(ev);
+ assert_true(ran);
+ tracks.onaddtrack = null;
+ ran = false;
+ tracks.dispatchEvent(ev);
+ assert_false(ran);
+});
+test(function(){
+ tracks.addEventListener('addtrack', cb, false);
+ tracks.dispatchEvent(ev);
+ assert_true(ran);
+ tracks.removeEventListener('addtrack', cb, false);
+ ran = false;
+ tracks.dispatchEvent(ev);
+ assert_false(ran);
+}, 'TextTrackList.addEventListener/removeEventListener');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/onremovetrack.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/onremovetrack.html
new file mode 100644
index 0000000000..b8da16ce2e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TextTrackList/onremovetrack.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>TextTrackList.onremovetrack</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+setup(function(){
+ window.tracks = document.createElement('video').textTracks;
+ window.ev = new Event('removetrack');
+ window.ran = false;
+ window.cb = function() { ran = true; };
+});
+test(function(){
+ assert_equals(tracks.onremovetrack, null);
+ tracks.onremovetrack = cb;
+ assert_equals(tracks.onremovetrack, cb);
+ tracks.dispatchEvent(ev);
+ assert_true(ran);
+ tracks.onremovetrack = null;
+ ran = false;
+ tracks.dispatchEvent(ev);
+ assert_false(ran);
+});
+test(function(){
+ tracks.addEventListener('removetrack', cb, false);
+ tracks.dispatchEvent(ev);
+ assert_true(ran);
+ tracks.removeEventListener('removetrack', cb, false);
+ ran = false;
+ tracks.dispatchEvent(ev);
+ assert_false(ran);
+}, 'TextTrackList.addEventListener/removeEventListener');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TrackEvent/constructor.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TrackEvent/constructor.html
new file mode 100644
index 0000000000..cb5b89711f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TrackEvent/constructor.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>TrackEvent constructor</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ var ev = new TrackEvent('foo');
+ assert_true(ev instanceof TrackEvent, 'ev instanceof TrackEvent');
+ assert_true(ev instanceof Event, 'ev instanceof Event');
+ assert_equals(ev.track, null, 'ev.track');
+ ev.track = {};
+ assert_equals(ev.track, null, 'ev.track after assignment');
+}, document.title+', one arg');
+test(function(){
+ var video = document.createElement('video');
+ var testTrack = video.addTextTrack('subtitles', 'foo', 'foo');
+ var ev = new TrackEvent('foo', {track: testTrack});
+ assert_true(ev instanceof TrackEvent, 'ev instanceof TrackEvent');
+ assert_true(ev instanceof Event, 'ev instanceof Event');
+ assert_equals(ev.track, testTrack, 'ev.track');
+ ev.track = {};
+ assert_equals(ev.track, testTrack, 'ev.track after assignment');
+}, document.title+', two args');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TrackEvent/createEvent.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TrackEvent/createEvent.html
new file mode 100644
index 0000000000..1d7eb540c3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/interfaces/TrackEvent/createEvent.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>TrackEvent created with createEvent</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17268
+ assert_throws_dom('NOT_SUPPORTED_ERR', function() {
+ var ev = document.createEvent('TrackEvent');
+ });
+ var ev = new TrackEvent('foo');
+ assert_false('initTrackEvent' in ev, 'initTrackEvent');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/autoplay-overrides-preload.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/autoplay-overrides-preload.html
new file mode 100644
index 0000000000..332184d55c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/autoplay-overrides-preload.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<title>autoplay overrides preload</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id=log></div>
+<script>
+['none', 'metadata'].forEach(function(preload) {
+ ['first', 'last'].forEach(function(order) {
+ async_test(function(t) {
+ var a = document.createElement('audio');
+ a.src = getAudioURI('/media/sound_5');
+ if (order == 'first') {
+ a.autoplay = true;
+ a.preload = preload;
+ } else {
+ a.preload = preload;
+ a.autoplay = true;
+ }
+ a.addEventListener('error', t.unreached_func());
+ a.addEventListener('playing', t.step_func(function() {
+ assert_equals(a.readyState, a.HAVE_ENOUGH_DATA);
+ assert_false(a.paused);
+ t.done();
+ }));
+ }, 'autoplay (set ' + order + ') overrides preload "' + preload + '"');
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-events-networkState.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-events-networkState.html
new file mode 100644
index 0000000000..d163c0e5b9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-events-networkState.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<title>load() fires abort/emptied events when networkState is not NETWORK_EMPTY</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id=log></div>
+<script>
+// Load media resource
+// https://html.spec.whatwg.org/multipage/media.html#loading-the-media-resource
+function load_test(t, v) {
+ assert_not_equals(v.networkState, v.NETWORK_EMPTY);
+
+ var expected_events = [];
+ if (v.networkState == v.NETWORK_LOADING || v.networkState == v.NETWORK_IDLE) {
+ expected_events.push('abort');
+ }
+
+ if (v.networkState != v.NETWORK_EMPTY) {
+ expected_events.push('emptied');
+ }
+
+ if (v.currentTime != 0.0) {
+ expected_events.push('timeupdate');
+ }
+
+ var actual_events = [];
+ v.onabort = v.onemptied = v.ontimeupdate = t.step_func(function(e) {
+ actual_events.push(e.type);
+ });
+
+ v.onloadstart = t.step_func(function() {
+ assert_array_equals(actual_events, expected_events);
+ t.done();
+ });
+
+ v.load();
+
+ assert_array_equals(actual_events, [], 'events should be fired in queued tasks');
+}
+
+async_test(function(t) {
+ var v = document.createElement('video');
+ // suspend is fired optionally "if the user agent intends to not attempt to
+ // fetch the resource" or "once the entire media resource has been fetched"
+ v.preload = 'none';
+ v.src = getAudioURI('/media/sound_5');
+ v.onerror = t.unreached_func();
+ v.onsuspend = t.step_func(function() {
+ v.onsuspend = null;
+ assert_equals(v.networkState, v.NETWORK_IDLE);
+ load_test(t, v);
+ });
+}, 'NETWORK_IDLE');
+
+// Test if media element receives `emptied` before `timeupdate`
+async_test(function(t) {
+ var v = document.createElement('video');
+ v.src = getAudioURI('/media/sound_5');
+ v.onerror = t.unreached_func();
+ v.onloadeddata = t.step_func(function() {
+ v.onloadeddata = null;
+ assert_not_equals(v.networkState, v.NETWORK_EMPTY);
+ // Modify current time to ensure that loading would trigger `timeupdate` by
+ // resetting the current time.
+ v.currentTime = 1.0;
+ load_test(t, v);
+ });
+}, 'NETWORK_DISPATCH_EMPTIED_BEFORE_TIMEUPDATE');
+
+async_test(function(t) {
+ var v = document.createElement('video');
+ v.src = 'resources/delayed-broken-video.py';
+ v.onerror = t.unreached_func();
+ v.onloadstart = t.step_func(function() {
+ v.onloadstart = null;
+ assert_equals(v.networkState, v.NETWORK_LOADING);
+ load_test(t, v);
+ });
+}, 'NETWORK_LOADING');
+
+async_test(function(t) {
+ var v = document.createElement('video');
+ v.src = 'data:,';
+ v.onerror = t.step_func(function() {
+ v.onerror = null;
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE);
+ load_test(t, v);
+ });
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE);
+}, 'NETWORK_NO_SOURCE');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-removes-queued-error-event.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-removes-queued-error-event.html
new file mode 100644
index 0000000000..54d5c28dad
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-removes-queued-error-event.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<title>load() removes queued error event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+// The loadstart and error event firing tasks are queued in the synchronous
+// section of the resource selection algorithm, so no tasks can come between
+// them. Calling load() in the loadstart event handler removes the queued error
+// event task at very latest opportunity, failing any implementation that fires
+// the events in the same task.
+
+async_test(function(t) {
+ var v = document.createElement('video');
+ var events = [];
+ v.onloadstart = v.onerror = t.step_func(function(e) {
+ events.push(e.type);
+ if (events.length == 1) {
+ v.load();
+ } else if (events.length == 3) {
+ assert_array_equals(events, ['loadstart', 'loadstart', 'error']);
+ t.done();
+ }
+ });
+ v.src = '';
+}, 'video error event');
+
+async_test(function(t) {
+ var v = document.createElement('video');
+ var s = document.createElement('source');
+ var events = [];
+ v.onloadstart = s.onerror = t.step_func(function(e) {
+ events.push(e.type);
+ if (events.length == 1) {
+ v.load();
+ } else if (events.length == 3) {
+ assert_array_equals(events, ['loadstart', 'loadstart', 'error']);
+ t.done();
+ }
+ });
+ v.onerror = t.step_func(function() { assert_unreached(); });
+ v.appendChild(s);
+}, 'source error event');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-insert-before.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-insert-before.html
new file mode 100644
index 0000000000..39c9887505
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-insert-before.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<title>inserting another source before the candidate</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var v;
+function createSource(src) {
+ var source = document.createElement('source');
+ source.src = src;
+ return source;
+}
+var t = async_test(function(t) {
+ v = document.createElement('video');
+ v.addEventListener('loadstart', t.step_func(function() {
+ assert_equals(v.currentSrc.substr(v.currentSrc.lastIndexOf('#')), '#a');
+ t.done();
+ }), false);
+ v.appendChild(createSource('#a')); // invokes resource selection
+});
+</script>
+<!-- now resource selection algorithm will continue its sync section (the </script> tag below provides a stable state) -->
+<!-- #a is candidate -->
+<!-- pointer is between #a and the end of the list -->
+<script>
+t.step(function() {
+ v.insertBefore(createSource('#b'), v.firstChild); // pointer is unchanged, #a is still candidate
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-moved.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-moved.html
new file mode 100644
index 0000000000..f59452e0d8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-moved.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>moving the candidate source</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var s;
+var t = async_test(function(t) {
+ var v = document.createElement('video');
+ s = document.createElement('source');
+ s.src = 'resources/delayed-broken-video.py';
+ s.onerror = t.step_func(function() { t.done(); });
+ v.appendChild(s); // invokes resource selection
+ onload = t.step_func(function() { assert_unreached(); });
+});
+</script>
+<script>
+t.step(function() {
+ document.body.appendChild(s);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-remove-addEventListener.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-remove-addEventListener.html
new file mode 100644
index 0000000000..0c1e6f0ad8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-remove-addEventListener.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<title>removing the candidate source, addEventListener</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var v;
+function createSource(src) {
+ var source = document.createElement('source');
+ source.src = src;
+ return source;
+}
+var t = async_test(function(t) {
+ v = document.createElement('video');
+ v.appendChild(createSource('resources/delayed-broken-video.py')); // invokes resource selection
+ v.firstChild.addEventListener('error', t.step_func(function() { t.done(); }), false);
+});
+</script>
+<!-- now resource selection algorithm will continue its sync section (the </script> tag below provides a stable state) -->
+<!-- the <source> is candidate -->
+<!-- pointer is between the <source> and the end of the list -->
+<script>
+t.step(function() {
+ v.removeChild(v.firstChild); // tests that we fire 'error' on it despite being removed
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-remove-no-listener.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-remove-no-listener.html
new file mode 100644
index 0000000000..f384eb3121
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-remove-no-listener.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<title>removing the candidate source, no listener</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var v;
+function createSource(src) {
+ var source = document.createElement('source');
+ source.src = src;
+ return source;
+}
+var t = async_test(function(t) {
+ v = document.createElement('video');
+ v.appendChild(createSource('resources/delayed-broken-video.py')); // invokes resource selection
+});
+</script>
+<!-- now resource selection algorithm will continue its sync section (the </script> tag below provides a stable state) -->
+<!-- the <source> is candidate -->
+<!-- pointer is between the <source> and the end of the list -->
+<script>
+t.step(function() {
+ v.removeChild(v.firstChild); // just tests that we don't crash
+ onload = t.step_func(function() { t.done(); });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-remove-onerror.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-remove-onerror.html
new file mode 100644
index 0000000000..c295c85bfc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-remove-onerror.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<title>removing the candidate source, onerror</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var v;
+function createSource(src) {
+ var source = document.createElement('source');
+ source.src = src;
+ return source;
+}
+var t = async_test(function(t) {
+ v = document.createElement('video');
+ v.appendChild(createSource('resources/delayed-broken-video.py')); // invokes resource selection
+ v.firstChild.onerror = t.step_func(function() { t.done(); });
+});
+</script>
+<!-- now resource selection algorithm will continue its sync section (the </script> tag below provides a stable state) -->
+<!-- the <source> is candidate -->
+<!-- pointer is between the <source> and the end of the list -->
+<script>
+t.step(function() {
+ v.removeChild(v.firstChild); // tests that we fire 'error' on it despite being removed
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-currentSrc.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-currentSrc.html
new file mode 100644
index 0000000000..61902161ed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-currentSrc.html
@@ -0,0 +1,93 @@
+<!doctype html>
+<title>currentSrc should not be reset when changing source</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<audio src="/media/sine440.mp3"></audio>
+<script>
+let v;
+let t = async_test("Test currentSrc behaviour in various playback scenarios");
+v = document.querySelector('audio');
+function queueTaskAndStep(f) {
+ step_timeout(function() {
+ t.step(f);
+ }, 0);
+}
+
+function next() {
+ let testcase = tests.shift();
+ if (!testcase) {
+ t.done();
+ return;
+ }
+ step_timeout(testcase, 0);
+}
+
+let tests = [
+ function() {
+ v.src = "/media/sound_0.mp3";
+ queueTaskAndStep(function() {
+ assert_true(v.currentSrc.indexOf("sound_0.mp3") != -1, "currentSrc must be equal to the source after load if present");
+ next();
+ });
+ },
+ function() {
+ v.src = URL.createObjectURL(new MediaSource());
+ queueTaskAndStep(function() {
+ assert_not_equals(v.currentSrc, "", "currentSrc must not be equal to the empty string after load if playing a MediaSource from the src attribute");
+ next();
+ });
+ },
+ function() {
+ fetch('/media/sound_0.mp3')
+ .then(function(response) {
+ return response.arrayBuffer();
+ }).then((b) => {
+ v.src = URL.createObjectURL(new Blob([new Uint8Array(b)], ["audio/mpeg"]));
+ queueTaskAndStep(function() {
+ assert_not_equals(v.currentSrc, "", "currentSrc must be not equal to the empty string after load if playing a Blob from the src attribute");
+ next();
+ });
+ });
+ },
+ function() {
+ v.src = "/media/sound_0.mp3";
+ // Source should be ignored when there is an `src`
+ let sourceNode = document.createElement("source");
+ sourceNode.setAttribute("src", "/media/sine440.mp3");
+ sourceNode.setAttribute("type", "audio/mpeg");
+ v.appendChild(sourceNode);
+ queueTaskAndStep(function() {
+ assert_true(v.currentSrc.indexOf("sine440.mp3") == -1, "The src attribute takes precedence over any source child element when both are preset");
+ next();
+ })
+ },
+ function() {
+ // But taken into account when there is no `src` attribute;
+ v.src = "";
+ v.removeAttribute("src");
+ queueTaskAndStep(function() {
+ assert_true(v.currentSrc.indexOf("sine440.mp3") != -1, "The child source element is the current source when no src attribute is present");
+ next();
+ });
+ },
+ function() {
+ v.firstChild.remove();
+ v.src = "https://test:test/";
+ queueTaskAndStep(function() {
+ assert_true(v.currentSrc.indexOf("sine440.mp3") != -1, "Not reset when a new load errors");
+ next();
+ });
+ },
+ function() {
+ v.srcObject = new MediaStream();
+ queueTaskAndStep(function() {
+ assert_equals(v.currentSrc, "", "When playing a MediaStream, currentSrc should also be reset to an empty string");
+ next();
+ });
+ }
+];
+
+next();
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-audio-constructor-no-src.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-audio-constructor-no-src.html
new file mode 100644
index 0000000000..cb2a579597
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-audio-constructor-no-src.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>NOT invoking resource selection with new Audio() sans src</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+async_test(function(t) {
+ var a = new Audio();
+ assert_equals(a.networkState, a.NETWORK_EMPTY);
+ a.onloadstart = t.step_func(function() { assert_unreached(); });
+ window.onload = t.step_func(function() { t.done(); });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-audio-constructor.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-audio-constructor.html
new file mode 100644
index 0000000000..662129756f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-audio-constructor.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<title>invoking resource selection with new Audio(src)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+async_test(function(t) {
+ var a = new Audio('');
+ a.onloadstart = t.step_func(function() { t.done(); });
+ window.onload = t.step_func(function() { assert_unreached(); });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-in-sync-event.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-in-sync-event.html
new file mode 100644
index 0000000000..1635598efd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-in-sync-event.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<title>await a stable state and sync event handlers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<video></video>
+<script>
+var v;
+var t = async_test(function(t) {
+ v = document.querySelector('video');
+ var a = document.createElement('a');
+ a.onclick = t.step_func(function() {
+ v.setAttribute('src', '#'); // invokes media load which invokes resource selection
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState in onclick handler');
+ });
+ a.click(); // sync fires click, so sets src
+ // now we should still await a stable state because the script hasn't
+ // finished, the event handler has just returned
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState after click()');
+ v.removeAttribute('src');
+});
+t.step(function() {
+ // now the sync section of resource selection should have run and should
+ // have found no src="" or <source> thus networkState being set to NETWORK_EMPTY.
+ // if the sync section was run when onclick returned, then networkState
+ // would be either NETWORK_LOADING or NETWORK_NO_SOURCE.
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState after src removed');
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-fragment-into-document.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-fragment-into-document.html
new file mode 100644
index 0000000000..5d4c32f670
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-fragment-into-document.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>NOT invoking resource selection by inserting document fragment into a document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var v;
+var t = async_test(function(t) {
+ v = document.createElement('video');
+ var fragment = document.createDocumentFragment();
+ fragment.appendChild(v);
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState after fragment.appendChild(v)');
+ document.body.appendChild(fragment);
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState after document.body.appendChild(fragment)');
+});
+</script>
+<script>
+t.step(function() {
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState in separate script');
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-into-document.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-into-document.html
new file mode 100644
index 0000000000..2f9ec978a5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-into-document.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<title>NOT invoking resource selection by inserting into a document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function(t) {
+ var v = document.createElement('video');
+ document.body.appendChild(v);
+ assert_equals(v.networkState, v.NETWORK_EMPTY);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-into-iframe.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-into-iframe.html
new file mode 100644
index 0000000000..45d133d878
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-into-iframe.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<title>NOT invoking resource selection by inserting into other document with src set</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<iframe hidden></iframe>
+<script>
+async_test(function(t) {
+ var v = document.createElement('video');
+ v.src = 'data:,';
+ v.onerror = t.step_func(function() {
+ assert_equals(v.readyState, v.HAVE_NOTHING);
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE);
+ var iframe = document.querySelector('iframe');
+ iframe.contentDocument.body.appendChild(v);
+ v.onloadstart = t.step_func(function() { assert_unreached(); });
+ // wait for an event after the above
+ var v2 = document.createElement('video');
+ v2.src = 'data:,';
+ v2.onloadstart = t.step_func(function() { t.done(); });
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-parent-into-document.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-parent-into-document.html
new file mode 100644
index 0000000000..6da34344fd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-parent-into-document.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>NOT invoking resource selection by inserting parent into a document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+async_test(function(t) {
+ var v = document.createElement('video');
+ var div = document.createElement('div');
+ div.appendChild(v);
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState after div.appendChild(v)');
+ document.body.appendChild(div);
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState after document.body.appendChild(div)');
+ window.onload = t.step_func(function() {
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState in window.onload');
+ t.done();
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-in-div.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-in-div.html
new file mode 100644
index 0000000000..b79bea52f1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-in-div.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>NOT invoking resource selection by inserting &lt;source> in &lt;div> in &lt;video></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<video><div></div></video>
+<script>
+async_test(function(t) {
+ var v = document.querySelector('video');
+ v.onloadstart = t.step_func(function() { assert_unreached(); });
+ v.firstChild.appendChild(document.createElement('source'));
+ window.onload = t.step_func(function() { t.done(); });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-in-namespace.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-in-namespace.html
new file mode 100644
index 0000000000..b73f229ecc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-in-namespace.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>NOT invoking resource selection by inserting &lt;source> in the wrong namespace</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<video></video>
+<script>
+async_test(function(t) {
+ var v = document.querySelector('video');
+ v.onloadstart = t.step_func(function() { assert_unreached(); });
+ v.appendChild(document.createElementNS('bogus','source'));
+ window.onload = t.step_func(function() { t.done(); });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-networkState.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-networkState.html
new file mode 100644
index 0000000000..5ef6e4cb3b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-networkState.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<title>NOT invoking resource selection by inserting &lt;source> when networkState is not NETWORK_EMPTY</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var loadstartCount = 0;
+var s1ErrorCount = 0;
+var s2ErrorCount = 0;
+var v;
+var t = async_test(function(t) {
+ v = document.createElement('video');
+ v.onloadstart = function() { loadstartCount++; };
+ var s1 = document.createElement('source');
+ s1.src = 'resources/delayed-broken-video.py';
+ s1.onerror = function() { s1ErrorCount++; };
+ v.appendChild(s1); // invokes resource selection
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState in first script');
+});
+</script>
+<script>
+t.step(function() {
+ assert_equals(v.networkState, v.NETWORK_LOADING, 'networkState in second script');
+ assert_equals(s1ErrorCount, 0, 's1ErrorCount in second script');
+ var s2 = document.createElement('source');
+ s2.onerror = t.step_func(function() {
+ s2ErrorCount++;
+ assert_equals(s1ErrorCount, 1, 's1ErrorCount in s2.onerror');
+ });
+ v.appendChild(s2);
+ onload = t.step_func(function() {
+ assert_equals(s2ErrorCount, 1, 's2ErrorCount in window.onload');
+ assert_equals(loadstartCount, 1, 'loadstartCount in window.onload'); // reliable if https://www.w3.org/Bugs/Public/show_bug.cgi?id=24353 is fixed
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState in window.onload'); // See Waiting step
+ t.done();
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-not-in-document.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-not-in-document.html
new file mode 100644
index 0000000000..2007b2e8b3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-not-in-document.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>invoking resource selection by inserting &lt;source> in video not in a document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+async_test(function(t) {
+ var v = document.createElement('video');
+ v.onloadstart = t.step_func(function() { t.done(); });
+ v.appendChild(document.createElement('source'));
+ window.onload = t.step_func(function() { assert_unreached(); });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source.html
new file mode 100644
index 0000000000..969daad623
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>invoking resource selection by inserting &lt;source></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<video></video>
+<script>
+async_test(function(t) {
+ var v = document.querySelector('video');
+ v.onloadstart = t.step_func(function() { t.done(); });
+ v.appendChild(document.createElement('source'));
+ window.onload = t.step_func(function() { assert_unreached(); });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-load.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-load.html
new file mode 100644
index 0000000000..909c72cd15
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-load.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>invoking resource selection with load()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var v;
+var t = async_test(function(t) {
+ v = document.createElement('video');
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState after creating v');
+ v.load();
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState after v.load()');
+});
+</script>
+<script>
+t.step(function() {
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState in separate script');
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-pause-networkState.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-pause-networkState.html
new file mode 100644
index 0000000000..18561a2649
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-pause-networkState.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<title>NOT invoking resource selection with pause() when networkState is not NETWORK_EMPTY</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<video></video>
+<script>
+var v;
+var t = async_test(function(t) {
+ v = document.querySelector('video');
+ v.src = 'data:,';
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState after setting src');
+ var errorCount = 0;
+ v.onerror = t.step_func(function() {
+ errorCount++;
+ if (errorCount == 1) {
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState in onerror');
+ v.pause(); // should not invoke RSA. if it does, error will be fired again.
+ } else {
+ assert_unreached();
+ }
+ });
+ onload = t.step_func(function() {
+ assert_equals(errorCount, 1, 'errorCount');
+ t.done();
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-pause.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-pause.html
new file mode 100644
index 0000000000..4f1bca74dd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-pause.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>invoking resource selection with pause()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var v;
+var t = async_test(function(t) {
+ v = document.createElement('video');
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState after creating v');
+ v.pause();
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState after v.pause()');
+});
+</script>
+<script>
+t.step(function() {
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState in separate script');
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-play.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-play.html
new file mode 100644
index 0000000000..64a440080c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-play.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>invoking resource selection with play()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var v;
+var t = async_test(function(t) {
+ v = document.createElement('video');
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState after creating v');
+ v.play();
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState after v.play()');
+});
+</script>
+<script>
+t.step(function() {
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState in separate script');
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-from-document-networkState.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-from-document-networkState.html
new file mode 100644
index 0000000000..1eed276b20
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-from-document-networkState.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<title>NOT invoking resource selection with implicit pause() when networkState is not NETWORK_EMPTY</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<video></video>
+<script>
+var v;
+var t = async_test(function(t) {
+ v = document.querySelector('video');
+ v.src = 'data:,';
+ document.body.appendChild(v);
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState after setting src');
+ var errorCount = 0;
+ v.onerror = t.step_func(function() {
+ errorCount++;
+ if (errorCount == 1) {
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState in onerror');
+ document.body.removeChild(v); // invokes pause() which should not invoke RSA. if it does, error will be fired again.
+ } else {
+ assert_unreached();
+ }
+ });
+ onload = t.step_func(function() {
+ assert_equals(errorCount, 1, 'errorCount');
+ t.done();
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-from-document.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-from-document.html
new file mode 100644
index 0000000000..65d0f73114
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-from-document.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>NOT invoking resource selection by removing from document with NETWORK_EMPTY</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var v;
+test(function() {
+ v = document.createElement('video');
+ document.body.appendChild(v);
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState after appending v to document');
+ v.parentNode.removeChild(v); // search for "When a media element is removed from a Document,"
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState after removing v');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-src.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-src.html
new file mode 100644
index 0000000000..6302ffeacf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-src.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>NOT invoking media load or resource selection when removing the src attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var v;
+var t = async_test(function(t) {
+ v = document.createElement('video');
+ v.setAttribute('src', ''); // invokes media load
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState after setting src');
+ var s = document.createElement('source');
+ s.onerror = this.step_func(function() { assert_unreached(); });
+ v.appendChild(s); // src is present so nothing happens here
+ onload = this.step_func(function() { t.done(); });
+});
+</script>
+<script>
+t.step(function() {
+ v.removeAttribute('src'); // nothing should happen
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-in-namespace.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-in-namespace.html
new file mode 100644
index 0000000000..438db124d2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-in-namespace.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>NOT invoking load by setting src in the wrong namespace</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<video></video>
+<script>
+async_test(function(t) {
+ var v = document.querySelector('video');
+ v.onloadstart = t.step_func(function() { assert_unreached(); });
+ v.setAttributeNS('bogus','src', '');
+ window.onload = t.step_func(function() { t.done(); });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-networkState.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-networkState.html
new file mode 100644
index 0000000000..ed86dbe0c5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-networkState.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>invoking load by setting src when networkState is not NETWORK_EMPTY</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function(t) {
+ var v = document.createElement('video');
+ v.play().catch(() => {}); // invokes resource selection and sets .paused to false
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState');
+ assert_false(v.paused, 'paused');
+ v.setAttribute('src', ''); // invokes media load which sets .paused to true
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState after setting src');
+ assert_true(v.paused, 'paused after setting src');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-not-in-document.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-not-in-document.html
new file mode 100644
index 0000000000..f6c4f2406a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-not-in-document.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>invoking load by setting src on video not in a document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+async_test(function(t) {
+ var v = document.createElement('video');
+ v.onloadstart = t.step_func(function() { t.done(); });
+ v.setAttribute('src','');
+ window.onload = t.step_func(function() { assert_unreached(); });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src.html
new file mode 100644
index 0000000000..e04b1b0580
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>invoking load by setting src</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<video></video>
+<script>
+async_test(function(t) {
+ var v = document.querySelector('video');
+ v.onloadstart = t.step_func(function() { t.done(); });
+ v.setAttribute('src', '');
+ window.onload = t.step_func(function() { assert_unreached(); });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-control.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-control.html
new file mode 100644
index 0000000000..dad5e5fd00
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-control.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<title>pointer updates (control test)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var a = 0;
+var b = 0;
+var c = 0;
+</script>
+<video
+ ><source onerror=a++
+ ><source onerror=b++ src='resources/delayed-broken-video.py'
+ ><source onerror=c++
+ ></video
+>
+<script>
+async_test(function(t) {
+ window.onload = t.step_func(function() {
+ assert_equals(a, 1, 'error events on a');
+ assert_equals(b, 1, 'error events on b');
+ assert_equals(c, 1, 'error events on c');
+ t.done();
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-insert-br.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-insert-br.html
new file mode 100644
index 0000000000..3ee141e306
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-insert-br.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>pointer updates (adding br elements)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var a = 0;
+var b = 0;
+var c = 0;
+</script>
+<video
+ ><source onerror=a++
+ ><source onerror=b++ src='resources/delayed-broken-video.py'
+ ><source onerror=c++
+ ></video
+>
+<script>
+async_test(function(t) {
+ var video = document.querySelector('video');
+ // add br elements
+ var br = document.createElement('br');
+ video.insertBefore(br, video.querySelector('[onerror="a++"]'));
+ video.insertBefore(br.cloneNode(false), video.querySelector('[onerror="b++"]'));
+ video.insertBefore(br.cloneNode(false), video.querySelector('[onerror="c++"]'));
+ video.appendChild(br.cloneNode(false));
+ window.onload = t.step_func(function() {
+ assert_equals(a, 1, 'error events on a');
+ assert_equals(b, 1, 'error events on b');
+ assert_equals(c, 1, 'error events on c');
+ t.done();
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-insert-source.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-insert-source.html
new file mode 100644
index 0000000000..2d32e6fca0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-insert-source.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<title>pointer updates (adding source elements)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var a = 0;
+var b = 0;
+var c = 0;
+var x1 = 0;
+var x2 = 0;
+var x3 = 0;
+var x4 = 0;
+</script>
+<video
+ ><source onerror=a++
+ ><source onerror=b++ src='resources/delayed-broken-video.py'
+ ><source onerror=c++
+ ></video
+>
+<script>
+async_test(function(t) {
+ var video = document.querySelector('video');
+ // add source elements
+ var source1 = document.createElement('source'); source1.onerror = function() { x1++; };
+ var source2 = document.createElement('source'); source2.onerror = function() { x2++; };
+ var source3 = document.createElement('source'); source3.onerror = function() { x3++; };
+ var source4 = document.createElement('source'); source4.onerror = function() { x4++; };
+ video.insertBefore(source1, video.querySelector('[onerror="a++"]'));
+ video.insertBefore(source2, video.querySelector('[onerror="b++"]'));
+ video.insertBefore(source3, video.querySelector('[onerror="c++"]'));
+ video.appendChild(source4);
+ window.onload = t.step_func(function() {
+ assert_equals(a, 1, 'error events on a');
+ assert_equals(b, 1, 'error events on b');
+ assert_equals(c, 1, 'error events on c');
+ assert_equals(x1, 0, 'error events on x1');
+ assert_equals(x2, 0, 'error events on x2');
+ assert_equals(x3, 1, 'error events on x3');
+ assert_equals(x4, 1, 'error events on x4');
+ t.done();
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-insert-text.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-insert-text.html
new file mode 100644
index 0000000000..15a4e4be06
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-insert-text.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>pointer updates (adding text nodes)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var a = 0;
+var b = 0;
+var c = 0;
+</script>
+<video
+ ><source onerror=a++
+ ><source onerror=b++ src='resources/delayed-broken-video.py'
+ ><source onerror=c++
+ ></video
+>
+<script>
+async_test(function(t) {
+ var video = document.querySelector('video');
+ // add text nodes
+ var text = document.createTextNode('x');
+ video.insertBefore(text, video.querySelector('[onerror="a++"]'));
+ video.insertBefore(text.cloneNode(false), video.querySelector('[onerror="b++"]'));
+ video.insertBefore(text.cloneNode(false), video.querySelector('[onerror="c++"]'));
+ video.appendChild(text.cloneNode(false));
+ window.onload = t.step_func(function() {
+ assert_equals(a, 1, 'error events on a');
+ assert_equals(b, 1, 'error events on b');
+ assert_equals(c, 1, 'error events on c');
+ t.done();
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-remove-source-after.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-remove-source-after.html
new file mode 100644
index 0000000000..0d1c940375
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-remove-source-after.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<title>pointer updates (removing source element after pointer)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var a = 0;
+var b = 0;
+var c = 0;
+var x1 = 0;
+var x2 = 0;
+var x3 = 0;
+var x4 = 0;
+</script>
+<video
+ ><source onerror=a++
+ ><source onerror=b++ src='resources/delayed-broken-video.py'
+ ><source onerror=x1++
+ ><source onerror=x2++
+ ><source onerror=x3++
+ ><source onerror=x4++
+ ><source onerror=c++
+ ></video
+>
+<script>
+var v;
+var t = async_test(function(t) {
+ v = document.querySelector('video');
+ v.removeChild(document.querySelector('[onerror="x1++"]'));
+ window.onload = t.step_func(function() {
+ assert_equals(a, 1, 'error events on a');
+ assert_equals(b, 1, 'error events on b');
+ assert_equals(c, 1, 'error events on c');
+ assert_equals(x1, 0, 'error events on x1');
+ assert_equals(x2, 0, 'error events on x2');
+ assert_equals(x3, 0, 'error events on x3');
+ assert_equals(x4, 0, 'error events on x4');
+ t.done();
+ });
+});
+</script>
+<script>
+t.step(function() {
+ v.removeChild(document.querySelector('[onerror="x2++"]'));
+});
+</script>
+<script>
+t.step(function() {
+ v.removeChild(document.querySelector('[onerror="x3++"]'));
+});
+</script>
+<script>
+t.step(function() {
+ v.removeChild(document.querySelector('[onerror="x4++"]'));
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-remove-source.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-remove-source.html
new file mode 100644
index 0000000000..191f9b5e21
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-remove-source.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<title>pointer updates (removing source elements)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var a = 0;
+var b = 0;
+var c = 0;
+var x1 = 0;
+var x2 = 0;
+var x3 = 0;
+var x4 = 0;
+</script>
+<video
+ ><source onerror=x1++
+ ><source onerror=a++
+ ><source onerror=x2++
+ ><source onerror=b++ src='resources/delayed-broken-video.py'
+ ><source onerror=x3++
+ ><source onerror=c++
+ ><source onerror=x4++
+ ></video
+>
+<script>
+async_test(function(t) {
+ var video = document.querySelector('video');
+ // remove the xn elements
+ [].forEach.call(document.querySelectorAll('[onerror^="x"]'), function(elm) {
+ video.removeChild(elm);
+ });
+ window.onload = t.step_func(function() {
+ assert_equals(a, 1, 'error events on a');
+ assert_equals(b, 1, 'error events on b');
+ assert_equals(c, 1, 'error events on c');
+ assert_equals(x1, 1, 'error events on x1');
+ assert_equals(x2, 1, 'error events on x2');
+ assert_equals(x3, 0, 'error events on x3');
+ assert_equals(x4, 0, 'error events on x4');
+ t.done();
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-remove-text.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-remove-text.html
new file mode 100644
index 0000000000..f0fe5da909
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-pointer-remove-text.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>pointer updates (removing text nodes)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var a = 0;
+var b = 0;
+var c = 0;
+</script>
+<video
+ >x<source onerror=a++
+ >x<source onerror=b++ src='resources/delayed-broken-video.py'
+ >x<source onerror=c++
+ >x</video
+>
+<script>
+async_test(function(t) {
+ var video = document.querySelector('video');
+ // remove the text nodes
+ [].forEach.call(video.childNodes, function(node) {
+ if (node.nodeType == node.TEXT_NODE) {
+ video.removeChild(node);
+ }
+ });
+ window.onload = t.step_func(function() {
+ assert_equals(a, 1, 'error events on a');
+ assert_equals(b, 1, 'error events on b');
+ assert_equals(c, 1, 'error events on c');
+ t.done();
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-remove-source.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-remove-source.html
new file mode 100644
index 0000000000..fbeead0191
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-remove-source.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>Changes to networkState when inserting and removing a &lt;source></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var v;
+var t = async_test(function(t) {
+ v = document.createElement('video');
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState when creating the element');
+ v.appendChild(document.createElement('source')); // runs resource selection algorithm
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState when inserting a source element');
+ v.removeChild(v.firstChild);
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState after removing the source element');
+});
+</script>
+<!-- now resource selection will continue its sync section (the </script> tag below provides a stable state) -->
+<!-- will find neither src nor source, so sets networkState to NETWORK_EMPTY -->
+<script>
+t.step(function() {
+ assert_equals(v.networkState, v.NETWORK_EMPTY, 'networkState after letting the sync section of resource selection run');
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-remove-src.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-remove-src.html
new file mode 100644
index 0000000000..4d78871823
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-remove-src.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>invoking resource selection by setting src; await stable state</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var v;
+var t = async_test(function(t) {
+ v = document.createElement('video');
+ v.onloadstart = t.step_func(function() { assert_unreached(); });
+ v.setAttribute('src', ''); // runs resource selection algorithm, but it will wait running the sync section until this script has finished
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE);
+ v.removeAttribute('src'); // will make resource selection algorithm revert to NETWORK_EMPTY and abort (in the sync section)
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE);
+ window.onload = t.step_func(function() { t.done(); });
+});
+</script>
+<script>
+t.step(function() {
+ assert_equals(v.networkState, v.NETWORK_EMPTY);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-resumes-onload.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-resumes-onload.html
new file mode 100644
index 0000000000..b166763d14
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-resumes-onload.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>resource selection should not delay the load event indefinitely</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<video></video>
+<script>
+async_test(function(t) {
+ const v = document.querySelector('video');
+ v.onloadstart = t.unreached_func("loadstart event should not be fired when the resource selection algorithm cannot determine mode");
+ const s = document.createElement('source');
+ v.appendChild(s); // this will trigger resource selection
+ v.removeChild(s); // force an early return in resource selection algorithm
+ window.onload = t.step_func_done(function() {
+ assert_equals(v.networkState, v.NETWORK_EMPTY);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-source-media-env-change.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-source-media-env-change.html
new file mode 100644
index 0000000000..67f2c8300d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-source-media-env-change.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>the &lt;source> media attribute: no reaction to environment change</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<iframe src="resources/media-min-width.html" width="300"></iframe>
+<script>
+// promises for the iframed test to resolve
+let beforeEnvChange = new Promise((resolve, reject) => {
+ window[0].resolveBeforeEnvChange = resolve;
+});
+let afterEnvChange = new Promise((resolve, reject) => {
+ window[0].resolveAfterEnvChange = resolve;
+});
+let afterLoadCalled = new Promise((resolve, reject) => {
+ window[0].resolveAfterLoadCalled = resolve;
+});
+const t = promise_test(async () => {
+ [beforeEnvChange, afterEnvChange, afterLoadCalled] = await Promise.all([ beforeEnvChange, afterEnvChange, afterLoadCalled ]);
+ assert_equals(beforeEnvChange, '#a', 'beforeEnvChange');
+ assert_equals(afterEnvChange, '#a', 'afterEnvChange');
+ assert_equals(afterLoadCalled, '#b', 'afterLoadCalled');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-source-media.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-source-media.html
new file mode 100644
index 0000000000..df5f47add2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-source-media.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>the &lt;source> media attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<video><source src="resources/delayed-broken-video.py" media="not all"></video>
+<script>
+test(function() {
+ var v = document.querySelector('video');
+ var s = document.querySelector('source');
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE);
+ assert_equals(v.currentSrc, '');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resources/delayed-broken-video.py b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resources/delayed-broken-video.py
new file mode 100644
index 0000000000..4eae3261f7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resources/delayed-broken-video.py
@@ -0,0 +1,5 @@
+import time
+
+def main(request, response):
+ time.sleep(0.1)
+ return [(b"Content-Type", b"text/plain")], u"FAIL"
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resources/media-min-width.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resources/media-min-width.html
new file mode 100644
index 0000000000..8a4ad500cb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resources/media-min-width.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<video width="200"></video>
+<script>
+function createSource(src, media) {
+ var source = document.createElement('source');
+ source.src = src;
+ if (media) {
+ source.media = media;
+ }
+ return source;
+}
+const rAF = () => new Promise(resolve => requestAnimationFrame(resolve));
+const hash = str => str.substr(str.lastIndexOf('#'));
+(async () => {
+ const v = document.querySelector('video');
+ v.getBoundingClientRect(); // force layout flush. ensure viewport dimensions are up-to-date
+ v.append(createSource('/media-source/mp4/test.mp4#a', '(min-width: 200px)'));
+ v.append(createSource('/media-source/mp4/test.mp4#b'));
+ await rAF();
+ await rAF();
+ window.resolveBeforeEnvChange(hash(v.currentSrc));
+ window.frameElement.width = '150';
+ await rAF();
+ await rAF();
+ window.resolveAfterEnvChange(hash(v.currentSrc));
+ v.load()
+ await rAF();
+ await rAF();
+ window.resolveAfterLoadCalled(hash(v.currentSrc));
+})();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/stable-state-beforeunload-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/stable-state-beforeunload-manual.html
new file mode 100644
index 0000000000..61ed225fa1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/stable-state-beforeunload-manual.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<title>stable state in beforeunload</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<button>click this button and cancel navigation</button>
+<a href="data:text/plain,FAIL: did not cancel navigation"></a>
+<script>
+async_test(function(t) {
+ window.onbeforeunload = t.step_func(function(event) {
+ var message = "foo bar";
+ event.returnValue = message;
+ return message;
+ });
+ var button = document.querySelector('button');
+ var link = document.querySelector('a');
+ button.onclick = t.step_func(function() {
+ v = document.createElement('video');
+ v.src = 'data:,';
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState before dialog');
+ assert_equals(v.currentSrc, '', 'currentSrc before dialog');
+ link.click();
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState after dialog');
+ assert_equals(v.currentSrc, '', 'currentSrc after dialog');
+ t.done();
+ window.onbeforeonload = null;
+ button.remove();
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/stable-state-dialogs-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/stable-state-dialogs-manual.html
new file mode 100644
index 0000000000..267dde913c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/stable-state-dialogs-manual.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>stable state in dialogs</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+['alert', 'confirm', 'prompt'].forEach(function(dialog) {
+ test(function() {
+ v = document.createElement('video');
+ v.src = 'data:,';
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState before dialog');
+ assert_equals(v.currentSrc, '', 'currentSrc before dialog');
+ window[dialog]('dismiss this dialog');
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState after dialog');
+ assert_equals(v.currentSrc, '', 'currentSrc after dialog');
+ }, 'stable state in ' + dialog + '()');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/stable-state-print-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/stable-state-print-manual.html
new file mode 100644
index 0000000000..1261a00793
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/stable-state-print-manual.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>stable state in print()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<button>click this button and dismiss the print dialog</button>
+<script>
+async_test(function(t) {
+ var button = document.querySelector('button');
+ button.onclick = t.step_func(function() {
+ v = document.createElement('video');
+ v.src = 'data:,';
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState before dialog');
+ assert_equals(v.currentSrc, '', 'currentSrc before dialog');
+ print();
+ assert_equals(v.networkState, v.NETWORK_NO_SOURCE, 'networkState after dialog');
+ assert_equals(v.currentSrc, '', 'currentSrc after dialog');
+ t.done();
+ button.remove();
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/location-of-the-media-resource/currentSrc.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/location-of-the-media-resource/currentSrc.html
new file mode 100644
index 0000000000..be4d09f739
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/location-of-the-media-resource/currentSrc.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<title>currentSrc</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+['audio', 'video'].forEach(function(tagName) {
+ test(function() {
+ assert_equals(document.createElement(tagName).currentSrc, '');
+ }, tagName + '.currentSrc initial value');
+
+ ['', '.', ' ', 'data:,'].forEach(function(src) {
+ async_test(function(t) {
+ var e = document.createElement(tagName);
+ e.src = src;
+ assert_equals(e.currentSrc, '');
+ e.addEventListener('loadstart', function () {
+ t.step_timeout(function () {
+ if (src == '') {
+ assert_equals(e.currentSrc, '');
+ } else {
+ assert_equals(e.currentSrc, e.src);
+ }
+ t.done();
+ }, 0);
+ })
+ }, tagName + '.currentSrc after setting src attribute "' + src + '"');
+
+ async_test(function(t) {
+ var e = document.createElement(tagName);
+ var s = document.createElement('source');
+ s.src = src;
+ e.appendChild(s);
+ assert_equals(e.currentSrc, '');
+ e.addEventListener('loadstart', function() {
+ t.step_timeout(function () {
+ if (src == '') {
+ assert_equals(e.currentSrc, '');
+ } else {
+ assert_equals(e.currentSrc, s.src);
+ }
+ t.done();
+ }, 0);
+ });
+ }, tagName + '.currentSrc after adding source element with src attribute "' + src + '"');
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/media_fragment_seek.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/media_fragment_seek.html
new file mode 100644
index 0000000000..2a0106ce16
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/media_fragment_seek.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Video should seek to time specified in media fragment syntax</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id="log"></div>
+<video id="video"></video>
+<script>
+async_test(function () {
+ let video = document.getElementById("video");
+ video.src = getVideoURI('/media/movie_5') + "#t=4,7";
+ video.load();
+ this.step_timeout(function () {
+ assert_equals(video.currentTime, 4.0);
+
+ video.src = getVideoURI('/media/movie_5') + "#t=%6Ept:3";
+ video.load();
+ this.step_timeout(function () {
+ assert_true(video.src.endsWith("t=%6Ept:3"));
+ assert_equals(video.currentTime, 3.0);
+
+ video.src = getVideoURI('/media/movie_5') + "#t=00:00:01.00";
+ video.load();
+ this.step_timeout(function () {
+ assert_true(video.src.endsWith("t=00:00:01.00"));
+ assert_equals(video.currentTime, 1.0);
+
+ video.src = getVideoURI('/media/movie_5') + "#u=12&t=3";
+ video.load();
+ this.step_timeout(function () {
+ assert_true(video.src.endsWith("#u=12&t=3"));
+ assert_equals(video.currentTime, 3.0);
+
+ video.src = getVideoURI('/media/movie_5') + "#t=npt%3A3";
+ video.load();
+ this.step_timeout(function () {
+ assert_true(video.src.endsWith("t=npt%3A3"));
+ assert_equals(video.currentTime, 3.0);
+ this.done();
+ }, 1000);
+ }, 1000);
+ }, 1000);
+ }, 1000);
+ }, 1000);
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/mime-types/canPlayType.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/mime-types/canPlayType.html
new file mode 100644
index 0000000000..56edf25aa8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/mime-types/canPlayType.html
@@ -0,0 +1,123 @@
+<!doctype html>
+<title>canPlayType</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<audio id="audio"></audio>
+<video id="video"></video>
+<div id="log"></div>
+<script>
+let VIDEO_ELEM = document.getElementById('video');
+let AUDIO_ELEM = document.getElementById('audio');
+
+function t(type, expected) {
+ assert_equals(canPlayType(type), expected, type);
+}
+
+function mime(type, codecs) {
+ if (codecs.length) {
+ return type + '; codecs="' + codecs.join(', ') + '"';
+ }
+ return type;
+}
+
+test(function() {
+ assert_equals(mime('video/webm', []), 'video/webm');
+ assert_equals(mime('video/webm', ['vp8']), 'video/webm; codecs="vp8"');
+ assert_equals(mime('video/webm', ['vp8', 'vorbis']), 'video/webm; codecs="vp8, vorbis"');
+}, 'utility code');
+
+function canPlayType(type) {
+ let audioCanPlay = AUDIO_ELEM.canPlayType(type);
+ let videoCanPlay = VIDEO_ELEM.canPlayType(type);
+ assert_equals(audioCanPlay, videoCanPlay,
+ 'audio.canPlayType() and video.canPlayType() agree');
+ assert_in_array(audioCanPlay, ['', 'maybe', 'probably'],
+ 'return value is one of "", "maybe" and "probably"');
+ return audioCanPlay;
+}
+
+test(function() {
+ t('application/octet-stream', '');
+ t('application/octet-stream; codecs="vorbis"', '');
+ t('application/octet-stream; codecs="vp8, vorbis"', '');
+ t('application/octet-stream; codecs="mp4a.40.2"', '');
+ t('application/octet-stream; codecs="theora, vorbis"', '');
+ t('application/octet-stream; codecs="avc1.42E01E, mp4a.40.2"', '');
+}, 'application/octet-stream not supported');
+
+test(function() {
+ t('application/marks-fantasmagorical-format', '');
+ t('video/x-new-fictional-format', '');
+ t('video/x-new-fictional-format;codecs="kittens,bunnies"', '');
+}, 'fictional formats and codecs not supported');
+
+function type_codecs_test(type, audioCodecs, videoCodecs) {
+ var typeSupported = false;
+ var codecSupported = false;
+
+ // Test 'type' without codecs.
+ // Spec: Generally, a user agent should never return "probably" for a type
+ // that allows the codecs parameter if that parameter is not present.
+ test(function() {
+ t(type, 'maybe');
+ t(type + ';', 'maybe');
+ t(type + ';codecs', 'maybe');
+ t(type + ';codecs=', 'maybe');
+ typeSupported = true;
+ }, type + ' (optional)');
+
+ function test_codec(codec) {
+ var typeWithCodec = mime(type, [codec]);
+ test(function() {
+ t(typeWithCodec, 'probably');
+ codecSupported = true;
+ }, typeWithCodec + ' (optional)');
+ }
+
+ // Test each audio and video codec separately.
+ audioCodecs.forEach(test_codec);
+ videoCodecs.forEach(test_codec);
+
+ // Test different pairings and orderings of audio+video codecs.
+ if (audioCodecs.length > 0 && videoCodecs.length > 0) {
+ test(function() {
+ audioCodecs.forEach(function(ac) {
+ videoCodecs.forEach(function(vc) {
+ var canPlayBoth = canPlayType(mime(type, [ac, vc]));
+ if (canPlayBoth) {
+ t(mime(type, [ac]), canPlayBoth);
+ t(mime(type, [vc]), canPlayBoth);
+ }
+ });
+ });
+ }, type + ' codecs subset');
+
+ test(function() {
+ audioCodecs.forEach(function(ac) {
+ videoCodecs.forEach(function(vc) {
+ assert_equals(canPlayType(mime(type, [ac, vc])),
+ canPlayType(mime(type, [vc, ac])));
+ });
+ });
+ }, type + ' codecs order');
+ }
+
+ test(function() {
+ t(mime(type, ['bogus']), '');
+ }, type + ' with bogus codec');
+
+ test(function() {
+ // At least one known codec must be supported if the container format is.
+ assert_equals(typeSupported, codecSupported);
+ }, type + ' with and without codecs');
+}
+
+type_codecs_test('audio/mp4', ['mp4a.40.2'], []);
+type_codecs_test('audio/ogg', ['opus', 'vorbis'], []);
+type_codecs_test('audio/wav', ['1'], []);
+type_codecs_test('audio/webm', ['opus', 'vorbis'], []);
+type_codecs_test('video/3gpp', ['samr'], ['mp4v.20.8']);
+type_codecs_test('video/mp4', ['mp4a.40.2'], ['avc1.42E01E', 'avc1.4D401E', 'avc1.58A01E', 'avc1.64001E', 'mp4v.20.8', 'mp4v.20.240']);
+type_codecs_test('video/ogg', ['opus', 'vorbis'], ['theora']);
+type_codecs_test('video/webm', ['opus', 'vorbis'], ['vp8', 'vp8.0', 'vp9', 'vp9.0']);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/networkState_during_loadstart.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/networkState_during_loadstart.html
new file mode 100644
index 0000000000..8570bfd111
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/networkState_during_loadstart.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video}.networkState - NETWORK_LOADING</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#dom-media-networkstate">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+var ta = async_test("audioElement.networkState should be NETWORK_LOADING during loadstart event");
+var a = document.getElementById("a");
+a.addEventListener("loadstart", function() {
+ ta.step(function() {
+ assert_equals(a.networkState,
+ a.NETWORK_LOADING);
+ });
+ ta.done();
+ a.pause();
+}, false);
+a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+
+var tv = async_test("videoElement.networkState should be NETWORK_LOADING during loadstart event");
+var v = document.getElementById("v");
+v.addEventListener("loadstart", function() {
+ tv.step(function() {
+ assert_equals(v.networkState,
+ v.NETWORK_LOADING);
+ });
+ tv.done();
+ v.pause();
+}, false);
+v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/networkState_during_progress.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/networkState_during_progress.html
new file mode 100644
index 0000000000..db9df23cb6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/networkState_during_progress.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video}.networkState - NETWORK_LOADING</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#dom-media-networkstate">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var ta = async_test("audioElement.networkState should be NETWORK_LOADING during progress event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", ta.unreached_func());
+ a.addEventListener("progress", ta.step_func(function() {
+ assert_equals(a.networkState, a.NETWORK_LOADING);
+ ta.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - networkState during progress");
+
+test(function() {
+ var tv = async_test("videoElement.networkState should be NETWORK_LOADING during progress event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", tv.unreached_func());
+ v.addEventListener("progress", tv.step_func(function() {
+ assert_equals(v.networkState, v.NETWORK_LOADING);
+ tv.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - networkState during progress");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/networkState_initial.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/networkState_initial.html
new file mode 100644
index 0000000000..0a203e6542
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/networkState_initial.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video}.networkState - default state</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#dom-media-networkstate">spec reference</a></p>
+ <audio id="a">
+ </audio>
+ <video id="v">
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var a = document.getElementById("a");
+ assert_equals(
+ a.networkState,
+ a.NETWORK_EMPTY,
+ "audioElement.networkState should be NETWORK_EMPTY to begin with");
+}, "audio.networkState - default state");
+
+test(function() {
+ var v = document.getElementById("v");
+ assert_equals(
+ v.networkState,
+ v.NETWORK_EMPTY,
+ "videoElement.networkState should be NETWORK_EMPTY to begin with");
+}, "video.networkState - default state");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/offsets-into-the-media-resource/currentTime.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/offsets-into-the-media-resource/currentTime.html
new file mode 100644
index 0000000000..e9b6589941
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/offsets-into-the-media-resource/currentTime.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<title>currentTime</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var v = document.createElement('video');
+ assert_equals(v.currentTime, 0);
+}, 'currentTime initial value');
+
+test(function() {
+ var v = document.createElement('video');
+ assert_equals(v.readyState, v.HAVE_NOTHING);
+ v.currentTime = Number.MAX_VALUE;
+ assert_equals(v.currentTime, Number.MAX_VALUE);
+ assert_false(v.seeking);
+}, 'setting currentTime when readyState is HAVE_NOTHING');
+
+async_test(function(t) {
+ var v = document.createElement('video');
+ v.src = getVideoURI('/media/movie_5');
+ v.onloadedmetadata = t.step_func(function() {
+ assert_greater_than(v.readyState, v.HAVE_NOTHING);
+ assert_false(v.seeking);
+ v.currentTime = 1;
+ assert_true(v.seeking);
+ t.done();
+ });
+}, 'setting currentTime when readyState is greater than HAVE_NOTHING');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/offsets-into-the-media-resource/duration.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/offsets-into-the-media-resource/duration.html
new file mode 100644
index 0000000000..0ac26eddb9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/offsets-into-the-media-resource/duration.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>duration</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var v = document.createElement('video');
+ assert_true(isNaN(v.duration));
+}, 'duration initial value');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/paused_false_during_play.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/paused_false_during_play.html
new file mode 100644
index 0000000000..946deecf43
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/paused_false_during_play.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - paused property</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("audio.paused should be false during play event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("play", t.step_func(function() {
+ assert_false(a.paused);
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - paused property");
+
+test(function() {
+ var t = async_test("video.paused should be false during play event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("play", t.step_func(function() {
+ assert_false(v.paused);
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - paused property");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/paused_true_during_pause.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/paused_true_during_pause.html
new file mode 100644
index 0000000000..817615c5cb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/paused_true_during_pause.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - paused property</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" controls>
+ </audio>
+ <video id="v" controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("audio.paused should be true during pause event");
+ var a = document.getElementById("a");
+ a.addEventListener("pause", function() {
+ t.step(function() {
+ assert_true(a.paused);
+ });
+
+ t.done();
+ }, false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+ a.play().catch(() => {});
+ a.pause();
+}, "audio events - paused property");
+
+test(function() {
+ var t = async_test("video.paused should be true during pause event");
+ var v = document.getElementById("v");
+ v.addEventListener("pause", function() {
+ t.step(function() {
+ assert_true(v.paused);
+ });
+
+ t.done();
+ }, false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+ v.play().catch(() => {});
+ v.pause();
+}, "video events - paused property");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/pitch-detector.js b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/pitch-detector.js
new file mode 100644
index 0000000000..78f22ccd85
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/pitch-detector.js
@@ -0,0 +1,58 @@
+// This should be removed when the webaudio/historical.html tests are passing.
+// Tracking bug: https://bugs.webkit.org/show_bug.cgi?id=204719
+window.AudioContext = window.AudioContext || window.webkitAudioContext;
+
+var FFT_SIZE = 2048;
+
+var audioContext;
+var sourceNode;
+
+function getPitchDetector(media) {
+ if(!audioContext) {
+ audioContext = new AudioContext();
+ sourceNode = audioContext.createMediaElementSource(media);
+ }
+
+ var analyser = audioContext.createAnalyser();
+ analyser.fftSize = FFT_SIZE;
+
+ sourceNode.connect(analyser);
+ analyser.connect(audioContext.destination);
+
+ return {
+ ensureStart() { return audioContext.resume(); },
+ detect() { return getPitch(analyser); },
+ cleanup() {
+ sourceNode.disconnect();
+ analyser.disconnect();
+ },
+ };
+}
+
+function getPitch(analyser) {
+ // Returns the frequency value for the nth FFT bin.
+ var binConverter = (bin) =>
+ (audioContext.sampleRate/2)*((bin)/(analyser.frequencyBinCount-1));
+
+ var buf = new Uint8Array(analyser.frequencyBinCount);
+ analyser.getByteFrequencyData(buf);
+ return findDominantFrequency(buf, binConverter);
+}
+
+// Returns the dominant frequency, +/- a certain margin.
+function findDominantFrequency(buf, binConverter) {
+ var max = 0;
+ var bin = 0;
+
+ for (var i=0;i<buf.length;i++) {
+ if(buf[i] > max) {
+ max = buf[i];
+ bin = i;
+ }
+ }
+
+ // The spread of frequencies within bins is constant and corresponds to
+ // (1/(FFT_SIZE-1))th of the sample rate. Use the value of bin #1 as a
+ // shorthand for that value.
+ return { value:binConverter(bin), margin:binConverter(1) };
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html
new file mode 100644
index 0000000000..d099a8a0f8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<title>play() with loop set to true after playback ended</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<video></video>
+<script>
+// Seek towards end of video (for faster testing).
+// Play video to end with "loop" set to false.
+// Once ended, set "loop" to true. Call play.
+// Verify that "seeked" event fires, seeking back to the beginning.
+// Pause video and end test.
+// Chromium bug: https://crbug.com/364442
+// Spec issue: https://github.com/whatwg/html/issues/4487
+async_test(function(t) {
+ var video = document.querySelector("video");
+
+ video.onloadedmetadata = t.step_func(function() {
+ // Video is initially paused and "loop" unset.
+ assert_true(video.paused, "paused initially ");
+ assert_false(video.loop, "loop initially");
+ // Seek to just before the end of the video and play.
+ video.currentTime = video.duration - 0.5;
+ video.onended = t.step_func(function() {
+ // Verify played to end and stopped.
+ assert_true(video.ended, "ended at ended event");
+ assert_true(video.paused, "paused at ended event");
+ assert_equals(video.currentTime, video.duration, "currentTime at ended event");
+
+ // With playback ended, set "loop" attribute. This will cause ended == false.
+ // looping video cannot be "ended", only paused.
+ assert_false(video.loop, "loop at ended event");
+ video.loop = true;
+ assert_true(video.loop, "loop after seek");
+ assert_false(video.ended, "ended after seek");
+ assert_true(video.paused, "paused after seek");
+
+ video.onseeked = t.step_func_done(function() {
+ // Observed seek. Verify current time decreased and still playing.
+ assert_true(video.loop, "loop at seeked event")
+ assert_false(video.paused, "paused at seeked event");
+ assert_false(video.ended, "ended at seeked event");
+ assert_less_than(video.currentTime, video.duration, "currentTime at seeked event");
+ // Pausing now that test is over to prevent additional unwanted looping.
+ video.pause();
+ });
+
+ // Play video with "loop" set. Expect seek back to start.
+ video.play();
+ });
+
+ video.play();
+ });
+
+ video.src = getVideoURI("/media/movie_5");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-move-to-other-document.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-move-to-other-document.html
new file mode 100644
index 0000000000..77b4a288d8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-move-to-other-document.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>paused state when moving to other document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id="log"></div>
+<video hidden></video>
+<iframe hidden></iframe>
+<script>
+async_test(function(t) {
+ var v = document.querySelector('video');
+ v.src = getVideoURI('/media/movie_300');
+ v.play();
+ v.onplaying = t.step_func(function() {
+ assert_false(v.paused, 'paused after playing');
+ document.querySelector('iframe').contentDocument.body.appendChild(v);
+ assert_false(v.paused, 'paused after moving');
+ t.step_timeout(function() {
+ assert_false(v.paused, 'paused after stable state')
+ t.done();
+ }, 0);
+ });
+ v.onpause = t.step_func(function() { assert_unreached(); });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-move-within-document.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-move-within-document.html
new file mode 100644
index 0000000000..911aa7b5c9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-move-within-document.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>paused state when moving within a document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id="log"></div>
+<video hidden></video>
+<div id="elsewhere"></div>
+<script>
+async_test(function(t) {
+ var v = document.querySelector('video');
+ v.src = getVideoURI('/media/movie_300');
+ v.play();
+ v.onplaying = t.step_func(function() {
+ assert_false(v.paused, 'paused after playing');
+ document.getElementById('elsewhere').appendChild(v);
+ assert_false(v.paused, 'paused after moving');
+ t.step_timeout(function() {
+ assert_false(v.paused, 'paused after stable state')
+ t.done();
+ }, 0);
+ });
+ v.onpause = t.step_func(function() { assert_unreached(); });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-remove-from-document-different-load.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-remove-from-document-different-load.html
new file mode 100644
index 0000000000..4802665cdd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-remove-from-document-different-load.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<title>paused state when removing from a document</title>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1583052">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id="log"></div>
+<div>
+ <video hidden></video>
+</div>
+<script>
+function afterStableState(func) {
+ var a = new Audio();
+ a.volume = 0;
+ a.addEventListener('volumechange', func);
+}
+
+async_test(function(t) {
+ var v = document.querySelector('video');
+
+ // Much like pause-remove-from-document.html, modulo this call.
+ document.body.appendChild(v);
+
+ v.src = getVideoURI('/media/movie_300');
+ v.play();
+ v.onplaying = t.step_func(function() {
+ assert_false(v.paused, 'paused after playing');
+ v.parentNode.removeChild(v);
+ assert_false(v.paused, 'paused after removing');
+ afterStableState(t.step_func(function() {
+ assert_true(v.paused, 'paused after stable state');
+ v.onpause = t.step_func(function() {
+ assert_true(v.paused, 'paused in pause event');
+ // re-insert and verify that it stays paused
+ document.body.appendChild(v);
+ t.step_timeout(function() {
+ assert_true(v.paused, 'paused after re-inserting');
+ t.done();
+ }, 0);
+ });
+ }));
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-remove-from-document-networkState.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-remove-from-document-networkState.html
new file mode 100644
index 0000000000..5140ea5611
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-remove-from-document-networkState.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<title>paused state when removing from a document when networkState is NETWORK_EMPTY</title>
+<meta name="timeout" content="long" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<video hidden></video>
+<script>
+// Negative test for the specified behavior prior to HTML r8447.
+promise_test(async function(t) {
+ var v = document.querySelector('video');
+ var watcher = new EventWatcher(t, v, [ 'pause' ]);
+ var p = v.play();
+
+ await new Promise(resolve => t.step_timeout(resolve, 0));
+ assert_equals(v.networkState, v.NETWORK_EMPTY,
+ 'networkState after stable state');
+ assert_false(v.paused, 'paused after stable state');
+ v.parentNode.removeChild(v);
+ assert_false(v.paused, 'paused after removing');
+
+ await watcher.wait_for('pause');
+
+ await promise_rejects_dom(t, 'AbortError', p, 'We expect promise being rejected');
+ assert_true(v.paused, 'paused after removing and stable state');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-remove-from-document.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-remove-from-document.html
new file mode 100644
index 0000000000..5425844037
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-remove-from-document.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<title>paused state when removing from a document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id="log"></div>
+<video hidden></video>
+<script>
+function afterStableState(func) {
+ var a = new Audio();
+ a.volume = 0;
+ a.addEventListener('volumechange', func);
+}
+
+async_test(function(t) {
+ var v = document.querySelector('video');
+ v.src = getVideoURI('/media/movie_300');
+ v.play();
+ v.onplaying = t.step_func(function() {
+ assert_false(v.paused, 'paused after playing');
+ v.parentNode.removeChild(v);
+ assert_false(v.paused, 'paused after removing');
+ afterStableState(t.step_func(function() {
+ assert_true(v.paused, 'paused after stable state');
+ v.onpause = t.step_func(function() {
+ assert_true(v.paused, 'paused in pause event');
+ // re-insert and verify that it stays paused
+ document.body.appendChild(v);
+ t.step_timeout(function() {
+ assert_true(v.paused, 'paused after re-inserting');
+ t.done();
+ }, 0);
+ });
+ }));
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/play-in-detached-document.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/play-in-detached-document.html
new file mode 100644
index 0000000000..8e9a7843b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/play-in-detached-document.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>play() in detached document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id="log"></div>
+<script>
+// Negative test for failure to play in a detached document.
+async_test(function(t)
+{
+ var doc = document.implementation.createHTMLDocument("");
+ var v = doc.createElement("video");
+ doc.body.appendChild(v);
+ v.src = getVideoURI("/media/movie_5");
+ v.play().catch(() => {});
+
+ v.addEventListener("timeupdate", t.step_func(function() {
+ assert_false(v.paused);
+ if (v.currentTime > 0) {
+ t.done();
+ }
+ }));
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/playbackRate.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/playbackRate.html
new file mode 100644
index 0000000000..d8e14b5fc1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/playing-the-media-resource/playbackRate.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<title>playbackRate</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var v = document.createElement('video');
+ assert_equals(v.playbackRate, 1);
+}, 'playbackRate initial value');
+
+function testPlaybackRateHelper(t, newPlaybackRate) {
+ var v = document.createElement('video');
+ var initialRate = v.playbackRate;
+
+ v.addEventListener('ratechange', t.step_func_done(function() {
+ assert_equals(v.playbackRate, newPlaybackRate);
+ }));
+
+ try {
+ v.playbackRate = newPlaybackRate;
+ } catch(e) {
+ assert_equals(e.name, 'NotSupportedError');
+ assert_equals(v.playbackRate, initialRate);
+ t.done();
+ }
+}
+
+async_test(function(t) {
+ testPlaybackRateHelper(this, 3);
+}, "playbackRate set to small positive value");
+
+async_test(function(t) {
+ testPlaybackRateHelper(this, 100);
+}, "playbackRate set to large positive value");
+
+async_test(function(t) {
+ testPlaybackRateHelper(this, -3);
+}, "playbackRate set to small negative value");
+
+async_test(function(t) {
+ testPlaybackRateHelper(this, -100);
+}, "playbackRate set to large negative value");
+
+async_test(function(t) {
+ testPlaybackRateHelper(this, 0);
+}, "playbackRate set to 0");
+
+async_test(function(t) {
+ testPlaybackRateHelper(this, -1);
+}, "playbackRate set to -1");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/preload_reflects_none_autoplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/preload_reflects_none_autoplay.html
new file mode 100644
index 0000000000..2670b0dd81
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/preload_reflects_none_autoplay.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video}.preload - reflection test</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#dom-media-preload">spec reference</a></p>
+ <audio id="audio" autoplay preload="none">
+ </audio>
+ <video id="video" autoplay preload="none">
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ assert_equals(
+ document.getElementById("audio").preload,
+ "none",
+ "audioElement.preload reflects 'none' value even if autoplay attribute is present");
+}, "audio.preload - reflection test");
+
+test(function() {
+ assert_equals(
+ document.getElementById("video").preload,
+ "none",
+ "videoElement.preload reflects 'none' value even if autoplay attribute is present");
+}, "video.preload - reflection test");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/preserves-pitch.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/preserves-pitch.html
new file mode 100644
index 0000000000..ba76f51d47
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/preserves-pitch.html
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<title>Test preservesPitch.</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>
+<script src="pitch-detector.js"></script>
+<script>
+
+// Remove when media-elements/historical.html's preservePitch prefix tests are are passing.
+function getPreservesPitch(audio) {
+ if ("preservesPitch" in HTMLAudioElement.prototype) {
+ return audio.preservesPitch;
+ }
+ if ("webkitPreservesPitch" in HTMLAudioElement.prototype) {
+ return audio.webkitPreservesPitch;
+ }
+ return undefined;
+}
+
+// Remove when media-elements/historical.html's preservePitch prefix tests are are passing.
+function setPreservesPitch(audio, value) {
+ if ("preservesPitch" in HTMLAudioElement.prototype) {
+ audio.preservesPitch = value;
+ } else if ("webkitPreservesPitch" in HTMLAudioElement.prototype) {
+ audio.webkitPreservesPitch = value;
+ }
+}
+
+test(function(t) {
+ assert_true("preservesPitch" in HTMLAudioElement.prototype);
+}, "Test that preservesPitch is present and unprefixed.");
+
+test(function(t) {
+ let defaultAudio = document.createElement('audio');
+ assert_true(getPreservesPitch(defaultAudio));
+
+ setPreservesPitch(defaultAudio, false);
+ assert_false(getPreservesPitch(defaultAudio));
+}, "Test that preservesPitch is on by default");
+
+
+var audio;
+
+function addTestCleanups(t, detector) {
+ t.add_cleanup(() => {
+ audio.pause();
+ audio.currentTime = 0;
+ });
+ t.add_cleanup(() => detector.cleanup());
+}
+
+function testPreservesPitch(preservesPitch, playbackRate, expectedPitch, description) {
+ promise_test(async t => {
+ let detector = getPitchDetector(audio);
+ addTestCleanups(t, detector);
+
+ audio.playbackRate = playbackRate;
+ setPreservesPitch(audio, preservesPitch);
+
+ function waitUntil(time) {
+ return new Promise((resolve) => {
+ audio.ontimeupdate = () => {
+ if (audio.currentTime >= time) {
+ resolve();
+ }
+ };
+ });
+ }
+
+ // Wait until we have played some audio. Otherwise, the detector
+ // might return a pitch of 0Hz.
+ audio.play();
+ await waitUntil(0.5);
+
+ var pitch = detector.detect();
+
+ // 25Hz is larger than the margin we get from 48kHz and 44.1kHz
+ // audio being analyzed by a FFT of size 2048. If we get something
+ // different, there is an error within the test's calculations (or
+ // we might be dealing a larger sample rate).
+ assert_less_than(pitch.margin, 25,
+ "Test error: the margin should be reasonably small.")
+
+ // Allow for a 15% margin of error in the pitch detector, to reduce test
+ // flakiness. Since our tests speed up and slow down by a factor of 2,
+ // this should be plenty of leeway, without causing false negatives.
+ assert_approx_equals(pitch.value, expectedPitch, expectedPitch*0.15,
+ "The actual pitch should be close to the expected pitch.");
+
+ }, description);
+}
+
+var REFERENCE_PITCH = 440;
+
+promise_test(async t => {
+ // Create the audio element only once, in order to lower the chances of
+ // tests timing out.
+ audio = document.createElement('audio');
+
+ // This file contains 5 seconds of a 440hz sine wave.
+ audio.src = "/media/sine440.mp3";
+
+ let detector = getPitchDetector(audio);
+ addTestCleanups(t, detector);
+
+ // The first time we run the test, we need to interact with the
+ // AudioContext and Audio element via user gestures.
+ await test_driver.bless("Play audio element", () => {
+ return Promise.all([audio.play(), detector.ensureStart()]);
+ });
+}, "Setup Audio element and AudioContext")
+
+testPreservesPitch(true, 1.0, REFERENCE_PITCH,
+ "The default playbackRate should not affect pitch")
+
+testPreservesPitch(false, 1.0, REFERENCE_PITCH,
+ "The default playbackRate should not affect pitch, even with preservesPitch=false")
+
+testPreservesPitch(true, 2.0, REFERENCE_PITCH,
+ "Speed-ups should not change the pitch when preservesPitch=true")
+
+testPreservesPitch(true, 0.5, REFERENCE_PITCH,
+ "Slow-downs should not change the pitch when preservesPitch=true")
+
+testPreservesPitch(false, 2.0, REFERENCE_PITCH*2.0,
+ "Speed-ups should change the pitch when preservesPitch=false")
+
+testPreservesPitch(false, 0.5, REFERENCE_PITCH*0.5,
+ "Slow-downs should change the pitch when preservesPitch=false")
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/ready-states/autoplay-hidden.optional.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/ready-states/autoplay-hidden.optional.html
new file mode 100644
index 0000000000..16c6e29be9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/ready-states/autoplay-hidden.optional.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<title>autoplay hidden</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/media.html#ready-states"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id="log"></div>
+<script>
+
+// https://html.spec.whatwg.org/multipage/media.html#ready-states:eligible-for-autoplay-2
+
+promise_test(async t => {
+ let video = document.createElement("video");
+ video.src = getVideoURI("/media/movie_5");
+ video.autoplay = true;
+ // In Safari, Chrome and Firefox, the video needs to be muted in order to be
+ // paused when hidden. They decided to do this in order to save resources when
+ // a video goes out of view and isn't expected to make any sound.
+ video.muted = true;
+ video.loop = true;
+ let watcher = new EventWatcher(t, video, ["playing", "pause"]);
+ document.body.appendChild(video);
+
+ await watcher.wait_for("playing");
+ assert_false(video.paused, "paused when video is display");
+ video.hidden = true;
+
+ await watcher.wait_for("pause");
+ assert_true(video.paused, "paused when video is hidden");
+ video.hidden = false;
+
+ await watcher.wait_for("playing");
+ assert_false(video.paused, "paused when video is display");
+}, "Allow delaying autoplay until video elements become visible");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/ready-states/autoplay-with-slow-text-tracks.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/ready-states/autoplay-with-slow-text-tracks.html
new file mode 100644
index 0000000000..930d9cbd5b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/ready-states/autoplay-with-slow-text-tracks.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<title>autoplay with slow text tracks</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id="log"></div>
+<script>
+setup({ single_test: true });
+// https://html.spec.whatwg.org/#ready-states says:
+//
+// HAVE_FUTURE_DATA: "the text tracks are ready".
+// HAVE_ENOUGH_DATA: All the conditions described for the HAVE_FUTURE_DATA state are met, ...
+//
+// When the ready state of a media element whose networkState is not NETWORK_EMPTY changes,
+// the user agent must follow the steps given below:
+// If the new ready state is HAVE_ENOUGH_DATA
+// (autoplay)
+//
+// So if the text tracks are not yet ready, we can't autoplay.
+
+var started = 0;
+var numOfTests = 5;
+
+function createTest() {
+ var video = document.createElement('video');
+ video.src = getVideoURI('/media/movie_5');
+ video.autoplay = true;
+ video.muted = true;
+ video.controls = true;
+ video.onplaying = function() {
+ started++;
+ assert_equals(track.track.cues.length, 1);
+ if (started === numOfTests) {
+ done();
+ }
+ };
+ var track = document.createElement('track');
+ track.src = '/media/foo.vtt?pipe=trickle(d2)';
+ track.default = true;
+ video.appendChild(track);
+ document.body.appendChild(video);
+}
+for (var i = 0; i < numOfTests; ++i) {
+ createTest();
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/ready-states/autoplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/ready-states/autoplay.html
new file mode 100644
index 0000000000..b60b58a421
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/ready-states/autoplay.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<title>autoplay</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id="log"></div>
+<script>
+function autoplay_test(tagName, src) {
+ function expect_events(t, e, expected_events) {
+ var actual_events = [];
+ var callback = t.step_func(function(ev) {
+ actual_events.push(ev.type);
+ assert_array_equals(actual_events,
+ expected_events.slice(0, actual_events.length));
+ if (expected_events.length == actual_events.length) {
+ t.done();
+ }
+ });
+ ['canplay', 'canplaythrough',
+ 'pause', 'play', 'playing', 'error'].forEach(function(type) {
+ e.addEventListener(type, callback);
+ });
+ }
+
+ async_test(function(t) {
+ var e = document.createElement(tagName);
+ e.src = src;
+ e.autoplay = true;
+ expect_events(t, e, ['canplay', 'play', 'playing', 'canplaythrough']);
+ }, tagName + '.autoplay');
+
+ async_test(function(t) {
+ var e = document.createElement(tagName);
+ e.src = src;
+ e.autoplay = true;
+ e.pause(); // sets the autoplaying flag to false
+ e.load(); // sets the autoplaying flag to true
+ expect_events(t, e, ['canplay', 'play', 'playing', 'canplaythrough']);
+ }, tagName + '.autoplay and load()');
+
+ async_test(function(t) {
+ var e = document.createElement(tagName);
+ e.src = src;
+ e.autoplay = true;
+ e.play(); // sets the autoplaying flag to false
+ // play() also sets the paused attribute to false; there is no way for the
+ // autoplaying flag to be true when the paused attribute is false.
+ assert_equals(e.paused, false);
+ expect_events(t, e, ['play', 'canplay', 'playing', 'canplaythrough']);
+ }, tagName + '.autoplay and play()');
+
+ async_test(function(t) {
+ var e = document.createElement(tagName);
+ e.src = src;
+ e.autoplay = true;
+ e.pause(); // sets the autoplaying flag to false
+ expect_events(t, e, ['canplay', 'canplaythrough']);
+ }, tagName + '.autoplay and pause()');
+
+ async_test(function(t) {
+ var e = document.createElement(tagName);
+ e.src = src;
+ e.autoplay = true;
+ document.body.appendChild(e);
+ document.body.removeChild(e);
+ // in stable state, internal pause steps sets the autoplaying flag to false
+ expect_events(t, e, ['canplay', 'canplaythrough']);
+ }, tagName + '.autoplay and internal pause steps');
+}
+
+autoplay_test('audio', getAudioURI('/media/sound_5'));
+autoplay_test('video', getVideoURI('/media/movie_5'));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_canplay.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_canplay.html
new file mode 100644
index 0000000000..358a87fe21
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_canplay.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - readyState property during canplay</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("audio.readyState should be >= HAVE_FUTURE_DATA during canplay event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("canplay", t.step_func(function() {
+ assert_greater_than_equal(a.readyState, a.HAVE_FUTURE_DATA);
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - readyState property during canplay");
+
+test(function() {
+ var t = async_test("video.readyState should be >= HAVE_FUTURE_DATA during canplay event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("canplay", t.step_func(function() {
+ assert_greater_than_equal(v.readyState, v.HAVE_FUTURE_DATA);
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - readyState property during canplay");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_canplaythrough.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_canplaythrough.html
new file mode 100644
index 0000000000..2721d18633
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_canplaythrough.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - readyState property during canplaythrough</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("audio.readyState should be HAVE_ENOUGH_DATA during canplaythrough event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("canplaythrough", t.step_func(function() {
+ assert_equals(a.readyState, a.HAVE_ENOUGH_DATA);
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - readyState property during canplaythrough");
+
+test(function() {
+ var t = async_test("video.readyState should be HAVE_ENOUGH_DATA during canplaythrough event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("canplaythrough", t.step_func(function() {
+ assert_equals(v.readyState, v.HAVE_ENOUGH_DATA);
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - readyState property during canplaythrough");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_loadeddata.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_loadeddata.html
new file mode 100644
index 0000000000..f237b1fbd3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_loadeddata.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - readyState property during loadeddata</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("audio.readyState should be >= HAVE_CURRENT_DATA during loadeddata event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("loadeddata", t.step_func(function() {
+ assert_greater_than_equal(a.readyState, a.HAVE_CURRENT_DATA);
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - readyState property during loadeddata");
+
+test(function() {
+ var t = async_test("video.readyState should be >= HAVE_CURRENT_DATA during loadeddata event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("loadeddata", t.step_func(function() {
+ assert_greater_than_equal(v.readyState, v.HAVE_CURRENT_DATA);
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - readyState property during loadeddata");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_loadedmetadata.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_loadedmetadata.html
new file mode 100644
index 0000000000..73f33f0b98
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_loadedmetadata.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - readyState property during loadedmetadata</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("audio.readyState should be >= HAVE_METADATA during loadedmetadata event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("loadedmetadata", t.step_func(function() {
+ assert_greater_than_equal(a.readyState, a.HAVE_METADATA);
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - readyState property during loadedmetadata");
+
+test(function() {
+ var t = async_test("video.readyState should be >= HAVE_METADATA during loadedmetadata event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("loadedmetadata", t.step_func(function() {
+ assert_greater_than_equal(v.readyState, v.HAVE_METADATA);
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - readyState property during loadedmetadata");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_playing.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_playing.html
new file mode 100644
index 0000000000..663bad701b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_during_playing.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video} events - readyState property during playing</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+ <audio id="a" autoplay controls>
+ </audio>
+ <video id="v" autoplay controls>
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var t = async_test("audio.readyState should be >= HAVE_FUTURE_DATA during playing event");
+ var a = document.getElementById("a");
+ a.addEventListener("error", t.unreached_func());
+ a.addEventListener("playing", t.step_func(function() {
+ assert_greater_than_equal(a.readyState, a.HAVE_FUTURE_DATA);
+ t.done();
+ a.pause();
+ }), false);
+ a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - readyState property during playing");
+
+test(function() {
+ var t = async_test("video.readyState should be >= HAVE_FUTURE_DATA during playing event");
+ var v = document.getElementById("v");
+ v.addEventListener("error", t.unreached_func());
+ v.addEventListener("playing", t.step_func(function() {
+ assert_greater_than_equal(v.readyState, v.HAVE_FUTURE_DATA);
+ t.done();
+ v.pause();
+ }), false);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - readyState property during playing");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_initial.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_initial.html
new file mode 100644
index 0000000000..e9c112bd24
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/readyState_initial.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video}.readyState - default state</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#dom-media-networkstate">spec reference</a></p>
+ <audio id="a">
+ </audio>
+ <video id="v">
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var a = document.getElementById("a");
+ assert_equals(
+ a.readyState,
+ a.HAVE_NOTHING,
+ "audioElement.readyState should be HAVE_NOTHING to begin with");
+}, "audio.readyState - default state");
+
+test(function() {
+ var v = document.getElementById("v");
+ assert_equals(
+ v.readyState,
+ v.HAVE_NOTHING,
+ "videoElement.readyState should be HAVE_NOTHING to begin with");
+}, "video.readyState - default state");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/seeking/seek-to-currentTime.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/seeking/seek-to-currentTime.html
new file mode 100644
index 0000000000..82b27bf87d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/seeking/seek-to-currentTime.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<title>seek to currentTime</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id=log></div>
+<script>
+async_test(function(t) {
+ var v = document.createElement('video');
+ v.src = getVideoURI('/media/movie_5');
+ v.onloadedmetadata = t.step_func(function() {
+ assert_greater_than(v.readyState, v.HAVE_NOTHING, 'readyState');
+ assert_greater_than(v.seekable.length, 0, 'seekable ranges');
+ assert_false(v.seeking, 'seeking before setting currentTime');
+ v.currentTime = v.currentTime;
+ assert_true(v.seeking, 'seeking after setting currentTime');
+ var events = [];
+ v.onseeking = v.ontimeupdate = v.onseeked = t.step_func(function(e) {
+ events.push(e.type);
+ // v.seeking can be true or false in the seeking event, see
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=24774
+ if (e.type != 'seeking') {
+ assert_equals(v.seeking, false, 'seeking in ' + e.type + ' event');
+ }
+ if (e.type == 'seeked') {
+ assert_array_equals(events, ['seeking', 'timeupdate', 'seeked'],
+ 'fired events');
+ t.done();
+ }
+ });
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/seeking/seek-to-max-value.htm b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/seeking/seek-to-max-value.htm
new file mode 100644
index 0000000000..a31f6c07ab
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/seeking/seek-to-max-value.htm
@@ -0,0 +1,23 @@
+<!doctype html>
+<title>seek to Number.MAX_VALUE</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id=log></div>
+<script>
+async_test(function(t) {
+ var v = document.createElement('video');
+ v.src = getVideoURI('/media/movie_5');
+ v.onloadedmetadata = t.step_func(function() {
+ assert_equals(v.seekable.length, 1);
+ v.currentTime = Number.MAX_VALUE;
+ assert_true(v.seeking, 'seeking after setting');
+ assert_equals(v.currentTime, v.seekable.end(0), 'currentTime after setting');
+ v.onseeked = t.step_func(function(e) {
+ assert_false(v.seeking, 'seeking in seeked event');
+ assert_equals(v.currentTime, v.seekable.end(0), 'currentTime in seeked event');
+ t.done();
+ });
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/seeking/seek-to-negative-time.htm b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/seeking/seek-to-negative-time.htm
new file mode 100644
index 0000000000..56a99028de
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/seeking/seek-to-negative-time.htm
@@ -0,0 +1,23 @@
+<!doctype html>
+<title>seek to negative time</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id=log></div>
+<script>
+async_test(function(t) {
+ var v = document.createElement('video');
+ v.src = getVideoURI('/media/movie_5');
+ v.onloadedmetadata = t.step_func(function() {
+ assert_equals(v.seekable.start(0), 0, 'earliest seekable time');
+ v.currentTime = -1;
+ assert_true(v.seeking, 'seeking after setting');
+ assert_equals(v.currentTime, 0, 'currentTime after setting');
+ v.onseeked = t.step_func(function(e) {
+ assert_false(v.seeking, 'seeking in seeked event');
+ assert_equals(v.currentTime, 0, 'currentTime in seeked event');
+ t.done();
+ });
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/src_object_blob.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/src_object_blob.html
new file mode 100644
index 0000000000..ae2bb76b26
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/src_object_blob.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>HTMLMediaElement.srcObject blob</title>
+<script src='/common/media.js'></script>
+<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>
+<video></video>
+<script>
+ const video = document.querySelector("video");
+ promise_test(async () => {
+ const blob = await fetch(getVideoURI('/media/movie_5'))
+ .then(r => r.blob());
+ try {
+ video.srcObject = blob;
+ } catch (error) {
+ assert_unreached(error);
+ }
+ const done = new Promise(res => video.addEventListener('ended', res));
+ test_driver.bless('initiate media playback', function () {
+ video.play();
+ });
+ return done;
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/src_reflects_attribute_not_source_elements.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/src_reflects_attribute_not_source_elements.html
new file mode 100644
index 0000000000..3dd43cc3f5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/src_reflects_attribute_not_source_elements.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{audio,video}.src - reflection test</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <p><a href="https://html.spec.whatwg.org/multipage/#dom-media-src">spec reference</a></p>
+ <audio id="audio" src="foo">
+ <source src="barbaz" />
+ </audio>
+ <video id="video" src="foo">
+ <source src="barbaz" />
+ </video>
+ <div id="log"></div>
+ <script>
+test(function() {
+ assert_equals(
+ document.getElementById("audio").src.indexOf("barbaz"),
+ -1,
+ "audioElement.src should reflect src attribute, not source child elements");
+}, "audio.src - reflection test");
+
+test(function() {
+ assert_equals(
+ document.getElementById("video").src.indexOf("barbaz"),
+ -1,
+ "videoElement.src should reflect src attribute, not source child elements");
+}, "video.src - reflection test");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cloneNode.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cloneNode.html
new file mode 100644
index 0000000000..9e0f0bf900
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cloneNode.html
@@ -0,0 +1,87 @@
+<!doctype html>
+<title>track element cloneNode</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var elm = document.createElement('track');
+ assert_equals(elm.readyState, elm.NONE, 'elm.readyState after element creation');
+ var clone = elm.cloneNode(true);
+ assert_equals(clone.readyState, clone.NONE, 'clone.readyState after element creation');
+ assert_not_equals(clone.track, elm.track, 'clone.track and elm.track');
+}, document.title+', not loaded');
+
+async_test(function(t) {
+ var elm = document.createElement('track');
+ var video = document.createElement('video');
+ video.appendChild(elm);
+ elm.track.mode = 'showing';
+ assert_equals(elm.readyState, elm.NONE, 'elm.readyState after appening to video setting mode');
+ elm.src = 'resources/track.vtt?pipe=trickle(d1)';
+ assert_equals(elm.readyState, elm.NONE, 'elm.readyState after setting src');
+ t.step_timeout(function() {
+ assert_equals(elm.readyState, elm.LOADING, 'elm.readyState in setTimeout');
+ var clone = elm.cloneNode(true);
+ assert_equals(clone.readyState, clone.NONE, 'clone.readyState after element creation');
+ video.appendChild(clone);
+ clone.track.mode = 'showing';
+ assert_equals(clone.readyState, clone.NONE, 'clone.readyState after appending to video and setting mode');
+ assert_not_equals(clone.track, elm.track, 'clone.track and elm.track');
+ t.done();
+ }, 0);
+}, document.title+', loading');
+
+async_test(function(t) {
+ var elm = document.createElement('track');
+ var video = document.createElement('video');
+ video.appendChild(elm);
+ elm.track.mode = 'showing';
+ elm.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00:00.000 --> 00:00:01.000\nfoo');
+ assert_equals(elm.readyState, elm.NONE, 'elm.readyState after setting src');
+ elm.onload = this.step_func(function() {
+ assert_equals(elm.readyState, elm.LOADED, 'elm.readyState');
+ assert_equals(elm.track.cues.length, 1, 'elm.track.cues.length');
+ assert_equals(elm.track.cues[0].startTime, 0, 'elm.track.cues[0].startTime');
+ assert_equals(elm.track.cues[0].endTime, 1, 'elm.track.cues[0].endTime');
+ assert_equals(elm.track.cues[0].text, 'foo', 'elm.track.cues[0].text');
+ var clone = elm.cloneNode(true);
+ assert_equals(clone.readyState, clone.NONE, 'clone.readyState after element creation');
+ video.appendChild(clone);
+ clone.track.mode = 'showing';
+ assert_equals(clone.readyState, clone.NONE, 'clone.readyState after appending to video and setting mode');
+ assert_not_equals(clone.track, elm.track, 'clone.track and elm.track');
+ clone.onload = this.step_func(function(){
+ assert_equals(clone.readyState, clone.LOADED, 'clone.readyState');
+ assert_not_equals(clone.track, elm.track, 'clone.track and elm.track');
+ assert_not_equals(clone.track.cues, elm.track.cues, 'clone.track.cues and elm.track.cues');
+ assert_equals(clone.track.cues.length, 1, 'clone.track.cues.length');
+ assert_not_equals(clone.track.cues[0], elm.track.cues[0], 'cues[0]');
+ assert_equals(clone.track.cues[0].startTime, 0, 'clone.track.cues[0].startTime');
+ assert_equals(clone.track.cues[0].endTime, 1, 'clone.track.cues[0].endTime');
+ assert_equals(clone.track.cues[0].text, 'foo', 'clone.track.cues[0].text');
+ this.done();
+ });
+ clone.onerror = this.step_func(function() { assert_unreached('clone got error'); });
+ });
+ elm.onerror = this.step_func(function() { assert_unreached('elm got error'); });
+}, document.title+', loaded');
+
+async_test(function(t) {
+ var elm = document.createElement('track');
+ var video = document.createElement('video');
+ video.appendChild(elm);
+ elm.track.mode = 'showing';
+ elm.onerror = t.step_func(function() {
+ assert_equals(elm.readyState, elm.ERROR, 'elm.readyState in onerror');
+ var clone = elm.cloneNode(true);
+ assert_equals(clone.readyState, clone.NONE, 'clone.readyState after element creation');
+ video.appendChild(clone);
+ clone.track.mode = 'showing';
+ assert_equals(clone.readyState, clone.NONE, 'clone.readyState after appending to video and setting mode');
+ assert_not_equals(clone.track, elm.track, 'clone.track and elm.track');
+ clone.onerror = t.step_func_done();
+ });
+ elm.src = 'javascript:"network error"';
+}, document.title+', failed to load');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/003.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/003.html
new file mode 100644
index 0000000000..4236df29b9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/003.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: No CORS, same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/004.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/004.html
new file mode 100644
index 0000000000..4f86d011a3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/004.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: No CORS, same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/005.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/005.html
new file mode 100644
index 0000000000..e6a693400c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/005.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/006.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/006.html
new file mode 100644
index 0000000000..351b97d677
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/006.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/007.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/007.html
new file mode 100644
index 0000000000..4ccc6b66ac
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/007.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/008.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/008.html
new file mode 100644
index 0000000000..0444a83085
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/008.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/009.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/009.html
new file mode 100644
index 0000000000..dd62232755
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/009.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: No CORS, not same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/010.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/010.html
new file mode 100644
index 0000000000..d75d6f4d6d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/010.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, not same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:origin, cookie:'no'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/011.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/011.html
new file mode 100644
index 0000000000..6d0fae6de7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/011.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, not same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:origin, cookie:'no'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/012.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/012.html
new file mode 100644
index 0000000000..110497b494
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/012.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, not same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:origin, cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/013.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/013.html
new file mode 100644
index 0000000000..d2a9ddb193
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/013.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, not same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:origin, cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/014.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/014.html
new file mode 100644
index 0000000000..a1d6a8b295
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/014.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: No CORS, same-origin, no headers, redirects to same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}, {cors:'no', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/015.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/015.html
new file mode 100644
index 0000000000..2850a24e17
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/015.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: No CORS, same-origin, with headers, redirects to same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}, {cors:'no', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/016.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/016.html
new file mode 100644
index 0000000000..5cd5a85d43
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/016.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, same-origin, no headers, redirects to same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}, {cors:'no', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/017.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/017.html
new file mode 100644
index 0000000000..0ec5bc3291
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/017.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, same-origin, no headers, redirects to same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}, {cors:'no', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/018.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/018.html
new file mode 100644
index 0000000000..f639d043a3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/018.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, same-origin, no headers, redirects to same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}, {cors:'no', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/019.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/019.html
new file mode 100644
index 0000000000..45e1291c92
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/019.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, same-origin, with headers, redirects to same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}, {cors:'no', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/020.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/020.html
new file mode 100644
index 0000000000..e1153b6813
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/020.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, not same-origin, no headers, redirects to same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:origin, cookie:'no'}]}; // redirect not followed
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/021.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/021.html
new file mode 100644
index 0000000000..ec2e9d8bb4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/021.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, not same-origin, with headers, redirects to same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:origin, cookie:'no'}, {cors:'null', cookie:'no'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/022.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/022.html
new file mode 100644
index 0000000000..e8fb0c3d43
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/022.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, not same-origin, with headers, redirects to same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:origin, cookie:'no'}, {cors:'null', cookie:'no'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/023.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/023.html
new file mode 100644
index 0000000000..ac9bb35465
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/023.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, not same-origin, no headers, redirects to same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:origin, cookie:'yes'}]}; // redirect not followed
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/024.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/024.html
new file mode 100644
index 0000000000..302340022d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/024.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, not same-origin, with headers, redirects to same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:origin, cookie:'yes'}, {cors:'null', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/025.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/025.html
new file mode 100644
index 0000000000..5cbe8528e2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/025.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, not same-origin, with headers, redirects to same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:origin, cookie:'yes'}, {cors:'null', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/026.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/026.html
new file mode 100644
index 0000000000..c8386ffff3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/026.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: No CORS, same-origin, with headers, redirects to not same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:'no', cookie:'yes'}]}; // redirect not followed
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/027.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/027.html
new file mode 100644
index 0000000000..5fe4760e66
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/027.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, same-origin, no headers, redirects to not same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:'no', cookie:'yes'}, {cors:origin, cookie:'no'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/028.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/028.html
new file mode 100644
index 0000000000..6019d37b63
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/028.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, same-origin, with headers, redirects to not same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}, {cors:origin, cookie:'no'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/029.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/029.html
new file mode 100644
index 0000000000..7fa85456de
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/029.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, same-origin, no headers, redirects to not same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:'no', cookie:'yes'}, {cors:origin, cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/030.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/030.html
new file mode 100644
index 0000000000..f7abf3b1ed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/030.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, same-origin, with headers, redirects to not same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}, {cors:origin, cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/031.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/031.html
new file mode 100644
index 0000000000..d709d0bc42
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/031.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, not same-origin, no headers, redirects to not same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:origin, cookie:'no'}]}; // redirect not followed
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/032.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/032.html
new file mode 100644
index 0000000000..62b1008a41
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/032.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, not same-origin, with headers, redirects to not same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:origin, cookie:'no'}, {cors:origin, cookie:'no'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/033.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/033.html
new file mode 100644
index 0000000000..215cae2419
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/033.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, not same-origin, with headers, redirects to not same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:origin, cookie:'no'}, {cors:origin, cookie:'no'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/034.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/034.html
new file mode 100644
index 0000000000..bebb43ba8c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/034.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, not same-origin, no headers, redirects to not same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:origin, cookie:'yes'}]}; // redirect not followed
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/035.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/035.html
new file mode 100644
index 0000000000..a17fb7dfc1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/035.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, not same-origin, with headers, redirects to not same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:origin, cookie:'yes'}, {cors:origin, cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/036.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/036.html
new file mode 100644
index 0000000000..52411177ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/036.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, not same-origin, with headers, redirects to not same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:origin, cookie:'yes'}, {cors:origin, cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/037.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/037.html
new file mode 100644
index 0000000000..675b913a13
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/037.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, same-origin, no headers, redirects to not same-origin, no headers, redirects to same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:'no', cookie:'yes'}, {cors:origin, cookie:'no'}]}; // second redirect not followed
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/038.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/038.html
new file mode 100644
index 0000000000..a29b2bdead
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/038.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, same-origin, with headers, redirects to not same-origin, with headers, redirects to same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}, {cors:origin, cookie:'no'}, {cors:'null', cookie:'no'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/039.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/039.html
new file mode 100644
index 0000000000..fcd4871ddb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/039.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, same-origin, no headers, redirects to not same-origin, with headers, redirects to same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:'no', cookie:'yes'}, {cors:origin, cookie:'no'}, {cors:'null', cookie:'no'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/040.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/040.html
new file mode 100644
index 0000000000..3c819684c4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/040.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, same-origin, no headers, redirects to not same-origin, no headers, redirects to same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:'no', cookie:'yes'}, {cors:origin, cookie:'yes'}]}; // second redirect not followed
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/041.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/041.html
new file mode 100644
index 0000000000..f0f81953fc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/041.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, same-origin, with headers, redirects to not same-origin, with headers, redirects to same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}, {cors:origin, cookie:'yes'}, {cors:'null', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/042.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/042.html
new file mode 100644
index 0000000000..c1ffa5f1ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/042.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, same-origin, no headers, redirects to not same-origin, with headers, redirects to same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:'no', cookie:'yes'}, {cors:origin, cookie:'yes'}, {cors:'null', cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/043.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/043.html
new file mode 100644
index 0000000000..09072a9895
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/043.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, same-origin, no headers, redirects to same-origin, no headers, redirects to not same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:'no', cookie:'yes'}, {cors:'no', cookie:'yes'}, {cors:origin, cookie:'no'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/044.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/044.html
new file mode 100644
index 0000000000..0d4a9fefbd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/044.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Anonymous, same-origin, no headers, redirects to same-origin, no headers, redirects to not same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}, {cors:'no', cookie:'yes'}, {cors:origin, cookie:'no'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/045.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/045.html
new file mode 100644
index 0000000000..7151364f9c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/045.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, same-origin, no headers, redirects to same-origin, no headers, redirects to not same-origin, no headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'error', requests:[{cors:'no', cookie:'yes'}, {cors:'no', cookie:'yes'}, {cors:origin, cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/046.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/046.html
new file mode 100644
index 0000000000..e286462814
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/046.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>track CORS: Use Credentials, same-origin, no headers, redirects to same-origin, no headers, redirects to not same-origin, with headers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src=/common/utils.js></script>
+<script src=support/common.js?pipe=sub></script>
+<script>
+var expected = {event:'load', requests:[{cors:'no', cookie:'yes'}, {cors:'no', cookie:'yes'}, {cors:origin, cookie:'yes'}]};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/common.js b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/common.js
new file mode 100644
index 0000000000..e30c627149
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/common.js
@@ -0,0 +1,144 @@
+setup(function(){
+ window.id = token();
+ var p = document.createElement('p');
+ p.innerHTML = 'Test id: <samp>'+id+'</samp>';
+ document.body.appendChild(p);
+ window.actual = {event:null, requests:[]};
+ window.errors = [];
+ window.origin = location.protocol+'//'+location.host;
+ window.escapedOrigin = encodeURIComponent(origin);
+ window.sameOriginURL = "http://{{domains[]}}:{{ports[http][0]}}" + location.pathname.replace(/\/[^\/]+$/, '/');
+ window.otherOriginURL = "http://{{domains[www1]}}:{{ports[http][0]}}" + location.pathname.replace(/\/[^\/]+$/, '/');
+}, {timeout:10000, explicit_done:true});
+
+onload = function() {
+ (async_test()).step(function() {
+ // fail early if track isn't supported
+ assert_true('HTMLTrackElement' in window, 'track not supported');
+ window.corsMode = document.title.match(/^track CORS: (No CORS|Anonymous|Use Credentials)/)[1];
+ var requests_tmp = document.title.substr(('track CORS: '+corsMode+', ').length).split(/, redirects to /g);
+ window.requests = [];
+ requests_tmp.forEach(function(r) {
+ var parts = r.split(', ');
+ requests.push({sameOrigin:parts[0] == 'same-origin', withHeaders:parts[1] == 'with headers'});
+ });
+ if (document.title.indexOf('not same-origin') > -1) {
+ window.hasCrossDomainCookie = true;
+ this.step(setCrossDomainCookie);
+ } else {
+ window.hasCrossDomainCookie = false;
+ this.step(loadTrack);
+ }
+ });
+ done();
+};
+
+function setCrossDomainCookie() {
+ var iframe = document.createElement('iframe');
+ iframe.onload = this.step_func(loadTrack);
+ iframe.src = otherOriginURL + 'support/set-cookie.html#'+id;
+ document.body.appendChild(iframe);
+}
+
+function loadTrack() {
+ var video = document.createElement('video');
+ window.track = document.createElement('track');
+ if (corsMode == 'Anonymous')
+ video.setAttribute('crossorigin', 'anonymous');
+ else if (corsMode == 'Use Credentials')
+ video.setAttribute('crossorigin', 'use-credentials');
+ // else No CORS, omit the crossorigin attribute
+ video.appendChild(track);
+ document.body.appendChild(video);
+ track.track.mode = 'showing';
+ document.cookie = id+'=yes;path=/;max-age=10';
+ var url = '';
+ var r;
+ while (r = requests.pop()) {
+ url = (r.sameOrigin ? sameOriginURL : otherOriginURL) +
+ 'support/cors-tester.py?id=' + id +
+ (r.withHeaders ? '&origin=' + escapedOrigin : '') +
+ (url === '' ? '' : '&redirect=' + encodeURIComponent(url));
+ }
+ track.src = url;
+ track.onerror = track.onload = this.step_func(function(e) {
+ actual.event = e.type;
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', 'support/cors-tester.py?read=true&id=' + id, true);
+ xhr.onload = this.step_func(function() {
+ if (xhr.status == 200) {
+ var lines = xhr.responseText.split('\n');
+ lines.forEach(function(line) {
+ var chunks = line.split(' | ');
+ var current = {};
+ actual.requests.push(current);
+ chunks.forEach(function(chunk) {
+ var nameval = chunk.split(' = ');
+ var name = nameval[0];
+ var value = nameval[1];
+ current[name] = value;
+ });
+ });
+ } else if (xhr.status == 404) {
+ //No stash was found
+ } else {
+ errors.push('got unexpected xhr status: '+xhr.status);
+ }
+ this.step(removeCookies);
+ });
+ xhr.onerror = this.step_func(function() {
+ errors.push('got xhr error');
+ this.step(removeCookies);
+ });
+ xhr.send();
+ });
+}
+
+function removeCookies() {
+ document.cookie = id+'=;path=/;max-age=0';
+ var nextStep = checkData;
+ if (hasCrossDomainCookie) {
+ var iframe = document.createElement('iframe');
+ iframe.onload = this.step_func(nextStep);
+ iframe.src = otherOriginURL + 'support/cors-tester.py?delete-cookie&id=' + id;
+ document.body.appendChild(iframe);
+ } else {
+ this.step(nextStep);
+ }
+}
+
+function removeLog() {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', 'support/cors-tester.py?cleanup&id='+id, true);
+ xhr.onload = this.step_func(function() {
+ assert_equals(xhr.responseText, 'OK', 'failed to clean up log: '+id);
+ this.step(checkData);
+ });
+ xhr.onerror = this.step_func(function() {
+ assert_unreached('failed to clean up log: '+id);
+ });
+ xhr.send();
+}
+
+function checkData() {
+ assert_equals(errors.length, 0, errors);
+ try {
+ if (actual.event == 'load' && expected.event == 'error')
+ assert_unreached('Security problem: got load event but expected error event');
+ assert_object_equals(actual, expected);
+ } catch(ex) {
+ var style = document.createElement('style');
+ style.textContent = '.json-diffs td { vertical-align:top } .json-diffs pre { margin:0 }';
+ document.head.appendChild(style);
+ var table = document.createElement('table');
+ table.border = "";
+ table.className = 'json-diffs';
+ table.innerHTML = '<tr><th>Actual<th>Expected<tr><td><pre></pre><td><pre></pre>';
+ table.getElementsByTagName('pre')[0].textContent = JSON.stringify(actual, null, 2);
+ table.getElementsByTagName('pre')[1].textContent = JSON.stringify(expected, null, 2);
+ document.body.insertBefore(table, document.getElementById('log'));
+ throw ex;
+ }
+ assert_equals(track.track.cues.length, expected.event == 'load' ? 1 : 0, 'track.track.cues.length');
+ this.done();
+}
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/cors-tester.py b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/cors-tester.py
new file mode 100644
index 0000000000..ad1cce1922
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/cors-tester.py
@@ -0,0 +1,50 @@
+from wptserve.handlers import HTTPException
+
+def main(request, response):
+ if request.method != u"GET":
+ raise HTTPException(400, message=u"Method was not GET")
+
+ if not b"id" in request.GET:
+ raise HTTPException(400, message=u"No id")
+
+ id = request.GET[b'id']
+ if b"read" in request.GET:
+ data = request.server.stash.take(id)
+ if data is None:
+ response.set_error(404, u"Tried to read data not yet set")
+ return
+ return [(b"Content-Type", b"text/plain")], data
+
+ elif b"cleanup" in request.GET:
+ request.server.stash.take(id)
+ return b"OK"
+
+ elif b"delete-cookie" in request.GET:
+ response.delete_cookie(id)
+ return [(b"Content-Type", b"text/plain")], b"OK"
+
+ if b"origin" in request.GET:
+ response.headers.set(b'Access-Control-Allow-Origin', request.GET[b'origin'])
+ response.headers.set(b'Access-Control-Allow-Credentials', b'true')
+
+ cors = request.headers.get(b"origin", b"no")
+
+ cookie = request.cookies.first(id, None)
+ cookie_value = cookie.value if cookie is not None else b"no"
+
+ line = b'cors = ' + cors + b' | cookie = ' + cookie_value
+
+ data = request.server.stash.take(id)
+ if data is not None:
+ line = data + b"\n" + line
+
+ request.server.stash.put(id, line)
+
+ if b"redirect" in request.GET:
+ response.status = 302
+ response.headers.set(b'Location', request.GET[b'redirect'])
+ else:
+ return b"""WEBVTT
+
+00:00:00.000 --> 00:00:10.000
+Test"""
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/remove-cookie.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/remove-cookie.html
new file mode 100644
index 0000000000..00430e3f0e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/remove-cookie.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<title>Remove cookie from location.hash</title>
+<script>
+if (location.hash)
+ document.cookie = decodeURIComponent(location.hash.substr(1))+'=yes;path=/;max-age=0';
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/set-cookie.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/set-cookie.html
new file mode 100644
index 0000000000..cc1c926386
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/cors/support/set-cookie.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<title>Set cookie from location.hash</title>
+<script>
+if (location.hash)
+ document.cookie = decodeURIComponent(location.hash.substr(1))+'=yes;path=/;max-age=15';
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/crashtests/track-element-src-aborted-load-onerror-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/crashtests/track-element-src-aborted-load-onerror-crash.html
new file mode 100644
index 0000000000..9db5ef0748
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/crashtests/track-element-src-aborted-load-onerror-crash.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>HTMLTrackElement 'src' attribute changed, load pending, 'error' handler mutates</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/media.html#start-the-track-processing-model">
+<link rel="help" href="https://crbug.com/1374341">
+<video></video>
+<script>
+ const video = document.querySelector('video');
+ video.style.visibility = 'collapse';
+ video.setAttribute('crossorigin', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+ const track = document.createElement('track');
+ track.src = 'x';
+ track.track.mode = 'hidden';
+ video.appendChild(track);
+ track.onerror = () => {
+ for (let i = 0; i < 10; ++i)
+ video.setAttribute('foo' + i, 'bar');
+ };
+ setTimeout(() => {
+ track.src = 'y';
+ }, 0);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html
new file mode 100644
index 0000000000..cd53914ecd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Ensure that the 'cuechange' event is not fired before video playback has begun.</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(function(t) {
+ let video = document.createElement('video');
+ video.src = getVideoURI('/media/movie_5');
+ video.preload = 'auto';
+
+ // Create a track element. The 'cuechange' event should not be fired.
+ let track = document.createElement('track');
+ track.oncuechange = t.unreached_func('The \`cuechange\` event should not be fired');
+
+ let videoWatcher = new EventWatcher(t, video, 'canplaythrough');
+ let trackWatcher = new EventWatcher(t, track, ['cuechange', 'load'])
+
+ track.src = 'resources/captions-fast.vtt';
+ track.kind = 'captions';
+ track.default = true;
+ track.track.mode = 'showing';
+ video.appendChild(track);
+
+ return Promise.all([videoWatcher.wait_for('canplaythrough'), trackWatcher.wait_for('load')]);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning-bad.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning-bad.vtt
new file mode 100644
index 0000000000..ff4c3fb5cd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning-bad.vtt
@@ -0,0 +1,20 @@
+WEBVTT
+Either one or both of positioning and alignment values are invalid.
+
+1
+00:00:00.000 --> 00:00:30.500 position:10% align: start
+Bear is Coming!!!!!
+Positioning on the left bottom, middle aligned,
+because the alignment is mistyped.
+
+2
+00:00:31.000 --> 00:00:45.500 position:200% align:middle
+I said Bear is coming!!!!
+Positioning on the bottom middle, middle aligned,
+because the positioning is off.
+
+3
+00:01:01.000 --> 00:02:00.500 position:-80% align:ends
+I said Bear is coming now!!!!
+Positioning on the bottom middle, middle aligned,
+because both the alignment and positioning don't apply.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning.vtt
new file mode 100644
index 0000000000..a6e6af2ef9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning.vtt
@@ -0,0 +1,20 @@
+WEBVTT
+Cues should position at different horizontal positions with different alignments.
+
+1
+00:00:00.000 --> 00:00:30.500 position:10% align:start
+Bear is Coming!!!!!
+Positioning on the left bottom, start aligned, and
+first character rendering position is at 10% of width.
+
+2
+00:00:31.000 --> 00:00:45.500 position:20% align:middle
+I said Bear is coming!!!!
+Positioning on the bottom left, middle aligned, and
+middle character rendering position of each line is at 20% of width.
+
+3
+00:01:01.000 --> 00:02:00.500 align:end position:80%
+I said Bear is coming now!!!!
+Positioning on the bottom right, end aligned, and
+last character rendering position of each line is at 80% of width.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position-bad.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position-bad.vtt
new file mode 100644
index 0000000000..b196f13a20
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position-bad.vtt
@@ -0,0 +1,21 @@
+WEBVTT
+One or more of line/text positioning and alignment values are invalid (settings are ignored).
+
+1
+00:00:00.000 --> 00:00:30.500 position: 0% align: start line: 0%
+Bear is Coming!!!!!
+None of the cue settings will be applied, just the default.
+
+2
+00:00:31.000 --> 00:00:01.500 position:0% align:end line:-30%
+I said Bear is coming!!!!
+The line position setting is ignored.
+No text is visible though because it's off-screen at position
+0 and the last character is at position 0%.
+
+3
+00:01:01.000 --> 00:01:30.000 line:-3 align:middler position:60%
+I said Bear is coming now!!!!
+Positioning on line 3 from the viewport bottom, middle aligned,
+with middle character of cue at 60% width.
+The alignment is ignored.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position.vtt
new file mode 100644
index 0000000000..dd3a6debb8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position.vtt
@@ -0,0 +1,28 @@
+WEBVTT
+Cues with valid alignment, line and text position settings.
+
+1
+00:00:00.000 --> 00:00:15.000 position:10% align:start line:0%
+Bear is Coming!!!!!
+Positioning on the top of the viewport at 10% horizontally,
+start aligned.
+
+00:00:15.500 --> 00:00:30.500 line:0 align:start
+Bear is Coming!!!!!
+This is line 0, middle aligned, first character at 50% width.
+
+2
+00:00:31.000 --> 00:00:45.500 position:80% line:80%
+I said Bear is coming!!!!
+Middle aligned, middle of cue's character is at 80% width and 80% height.
+
+00:00:46.000 --> 00:01:00.500 line:5 align:end position:30%
+I said Bear is coming!!!!
+This is line 6 from the top of the video viewport,
+end aligned with last character at 30% of viewport width.
+
+3
+00:01:01.000 --> 00:01:30.000 line:-3 align:middle position:60%
+I said Bear is coming now!!!!
+Positioning on line 3 from the viewport bottom, middle aligned,
+with middle character of cue at 60% width.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-bad.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-bad.vtt
new file mode 100644
index 0000000000..5beb376f45
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-bad.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+Cue alignment may only be start, middle, or end. These are all misspelled and so will default to middle.
+
+1
+00:00:00.000 --> 00:00:30.500 align:starta
+Bear is Coming!!!!!
+Erroneous alignment value -> middle.
+
+2
+00:00:31.000 --> 00:01:00.500 align:-start
+I said Bear is coming!!!!
+Erroneous alignment value --> middle.
+
+3
+00:01:01.000 --> 00:02:00.500 align: end
+I said Bear is coming now!!!!
+Erroneous alignment value with surplus whitespace --> middle.
+
+4
+00:02:01.000 --> 100:20:00.500 align:piugjk
+I said Bear is coming now!!!!
+Erroneous alignment value -> middle.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-ltr.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-ltr.vtt
new file mode 100644
index 0000000000..673b29ac85
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-ltr.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+Cue alignment may be start, middle, or end (default is middle).
+
+1
+00:00:00.000 --> 00:00:30.500 align:start
+الدب قادم!!!!!
+بدء محاذاته.
+
+2
+00:00:31.000 --> 00:01:00.500 align:middle
+قلت الدب قادم!!
+محاذاة الوسط.
+
+3
+00:01:01.000 --> 00:02:00.500 align:end
+قلت الدب قادم الآن!!
+محاذاة الغاية.
+
+4
+00:02:01.000 --> 100:20:00.500
+قلت الدب قادم الآن!!
+الافتراضية هي محاذاة الوسط. \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment.vtt
new file mode 100644
index 0000000000..ad7792f772
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+Cue alignment may be start, middle, or end (default is middle).
+
+1
+00:00:00.000 --> 00:00:30.500 align:start
+Bear is Coming!!!!!
+Start align.
+
+2
+00:00:31.000 --> 00:01:00.500 align:middle
+I said Bear is coming!!!!
+Middle align.
+
+3
+00:01:01.000 --> 00:02:00.500 align:end
+I said Bear is coming now!!!!
+End align.
+
+4
+00:02:01.000 --> 100:20:00.500
+I said Bear is coming now!!!!
+Default is middle alignment. \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/bom.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/bom.vtt
new file mode 100644
index 0000000000..0c8de32bcb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/bom.vtt
@@ -0,0 +1,10 @@
+WEBVTT FILE
+A BOM character at the start of a file should be ignored.
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:20:00.500
+I said Bear is coming!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-fast.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-fast.vtt
new file mode 100644
index 0000000000..7fe5b1241a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-fast.vtt
@@ -0,0 +1,13 @@
+WEBVTT
+
+1
+00:00:00.000 --> 00:00:00.300
+Lorem
+
+2
+00:00:00.300 --> 00:00:01.300
+ipsum
+
+3
+00:00:01.800 --> 00:00:02.800
+dolor
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-gaps.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-gaps.vtt
new file mode 100644
index 0000000000..44c74665c2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-gaps.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+
+1
+00:00:01.000 --> 00:00:02.000
+Lorem ipsum dolor sit amet,
+
+2
+00:00:03.000 --> 00:00:04.000
+consectetuer adipiscing elit,
+
+3
+00:00:05.000 --> 00:00:06.000
+sed diam nonummy nibh euismod tincidunt
+
+4
+00:00:07.000 --> 00:00:08.000
+ut laoreet dolore magna aliquam erat volutpat.
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-html.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-html.vtt
new file mode 100644
index 0000000000..0730f8bc40
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-html.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+
+1
+00:00:00.000 --> 00:00:01.000
+Lorem <b>ipsum</b> <u>dolor</u> <i.sit>sit</i> amet,
+
+2
+00:00:03.000 --> 00:00:04.000
+consectetuer adipiscing elit,
+
+3
+00:00:05.000 --> 00:00:06.000
+sed diam nonummy nibh euismod tincidunt
+
+4
+00:00:07.000 --> 00:00:08.000
+ut laoreet dolore magna aliquam erat volutpat.
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions.vtt
new file mode 100644
index 0000000000..787c430868
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/captions.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+
+1
+00:00:00.000 --> 00:00:01.000
+Lorem
+
+2
+00:00:01.000 --> 00:00:02.000
+ipsum
+
+3
+00:00:02.000 --> 00:00:03.000
+dolor
+
+4
+00:00:03.000 --> 00:00:04.000
+sit
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/class-bad.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/class-bad.vtt
new file mode 100644
index 0000000000..650ea2c496
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/class-bad.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+Invalid <c> class markup.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+<c .black>Bear is Coming!!!!!</c>
+The space signified an annotation start.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+<c.red&large>I said Bear is coming!!!!</c>
+Probably should only allow characters that CSS allows in class names.
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+I said <c.9red.upper+case>Bear is coming now</c>!!!!
+Probably should only allow characters that CSS allows in class names.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/class.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/class.vtt
new file mode 100644
index 0000000000..ea3ef623f5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/class.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Cue text fragment with <c> class markup is mapped to HTML <span> element with CSS classes.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+<c.black>Bear is Coming!!!!!</c>
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+<c.green>I said Bear is coming!!!!</c>
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+I said <c.red.uppercase>Bear is coming now</c>!!!!
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id-error.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id-error.vtt
new file mode 100644
index 0000000000..2b5db0c1da
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id-error.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Cue identifiers cannot contain the string "-->".
+
+-->random_id
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+another random identifier-->
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+
+identifier-->too
+00:01:01.000 --> 00:20:00.500
+I said Bear is coming now!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id.vtt
new file mode 100644
index 0000000000..3902118620
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+Random text is accepted for cue identifiers.
+
+random_id
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+another random identifier
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+
+identifier--too
+00:01:01.000 --> 00:02:00.500
+I said Bear is coming now!!!!
+
+identifier--too
+00:02:01.000 --> 00:03:00.500
+Duplicate identifier \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id-error.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id-error.vtt
new file mode 100644
index 0000000000..111bae6344
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id-error.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Cue identifiers cannot contain "-->". Whole cue is ignored.
+
+-->
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+-->
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+
+-->
+00:01:01.000 --> 00:20:00.500
+I said Bear is coming now!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id.vtt
new file mode 100644
index 0000000000..0d52a70ee4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id.vtt
@@ -0,0 +1,11 @@
+WEBVTT
+Cues don't have to have identifiers.
+
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+
+00:01:01.000 --> 00:20:00.500
+I said Bear is coming now!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-cuetext.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-cuetext.vtt
new file mode 100644
index 0000000000..88f56cceca
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-cuetext.vtt
@@ -0,0 +1,6 @@
+WEBVTT
+
+00:00.000 --> 00:01.000
+Valid cue 1
+00:02.000 --> 00:03.000
+Valid cue 2
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-header.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-header.vtt
new file mode 100644
index 0000000000..205955e3e4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-header.vtt
@@ -0,0 +1,6 @@
+WEBVTT
+00:00.000 --> 00:01.000
+Valid cue 1
+
+00:02.000 --> 00:03.000
+Valid cue 2
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-note.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-note.vtt
new file mode 100644
index 0000000000..56defcc48b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-note.vtt
@@ -0,0 +1,9 @@
+WEBVTT
+
+00:00.000 --> 00:01.000
+Valid cue 1
+
+NOTE about something
+NOTE or something else - maybe an identifier
+00:02.000 --> 00:03.000
+Valid cue 2
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align-bad.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align-bad.vtt
new file mode 100644
index 0000000000..5e4a61a5e4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align-bad.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+Either size or alignment are invalid.
+
+1
+00:00:00.000 --> 00:00:30.500 size:100% align:@start
+Bear is Coming!!!!!
+Box for the cue is 100% of the video viewport width, alignment is ignored.
+
+2
+00:00:31.000 --> 00:01:00.500 size:-10% align:end
+I said Bear is coming!!!!
+Box for the cue is as big as the text, no line wrapping,
+(except if viewport is too small) and end aligned.
+
+3
+00:01:01.000 --> 00:02:00.500 size:110% align:@end
+I said Bear is coming now!!!!
+Both cue size and alignment are ignored.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align.vtt
new file mode 100644
index 0000000000..6d36536539
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align.vtt
@@ -0,0 +1,19 @@
+WEBVTT
+Valid cue size with alignment settings.
+
+1
+00:00:00.000 --> 00:00:30.500 size:100% align:start
+Bear is Coming!!!!!
+Box for the cue is 100% of the video viewport width
+and because of the start align, all text is left aligned on the video viewport.
+
+2
+00:00:31.000 --> 00:01:00.500 size:10% align:end
+I said Bear is coming!!!!
+Box for the cue is 10% of the video viewport width, which will mean that automatic line wrapping will happen
+and the text is aligned to the end.
+
+3
+00:01:01.000 --> 00:02:00.500 size:0% align:middle
+I said Bear is coming now!!!!
+Cue text box size of 0 is acceptable, even if not visible.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-bad.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-bad.vtt
new file mode 100644
index 0000000000..700600d7a7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-bad.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+Invalid cue sizes (all settings are ignored).
+
+1
+00:00:00.000 --> 00:00:30.500 size: 50%
+Bear is Coming!!!!!
+Cue size setting doesn't parse and is ignored.
+
+2
+00:00:31.000 --> 00:01:00.500 size:-10%
+I said Bear is coming!!!!
+Negative cue size setting is not acceptable and is ignored.
+
+3
+00:01:01.000 --> 00:02:00.500 size:4000%
+I said Bear is coming now!!!!
+Cue size beyond 100% is not acceptable and is ignored.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size.vtt
new file mode 100644
index 0000000000..017d59a18b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size.vtt
@@ -0,0 +1,19 @@
+WEBVTT
+Valid cue size values.
+
+1
+00:00:00.000 --> 00:00:30.500 size:100%
+Bear is Coming!!!!!
+Box for the cue is 100% of the video viewport width,
+exemplified through background color,
+even if the text needs less.
+
+2
+00:00:31.000 --> 00:01:00.500 size:10%
+I said Bear is coming!!!!
+Box for the cue is 10% of the video viewport width, which will mean that automatic line wrapping will happen.
+
+3
+00:01:01.000 --> 00:02:00.500 size:0%
+I said Bear is coming now!!!!
+Cue text box size of 0 is acceptable, even if not visible.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-chrono-order.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-chrono-order.vtt
new file mode 100644
index 0000000000..fd6d484f88
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-chrono-order.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Cues that have overlapping time ranges.
+
+1
+00:00:01.000 --> 00:00:02.000
+Bear is Coming!!!!!
+
+2
+00:00:02.500 --> 00:00:03.500
+I said Bear is coming!!!!
+
+3
+00:00:04.000 --> 00:00:05.000
+I said Bear is coming now!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-no-separation.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-no-separation.vtt
new file mode 100644
index 0000000000..9062c67ede
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-no-separation.vtt
@@ -0,0 +1,11 @@
+WEBVTT
+Cues must be separated by at least one blank line, otherwise treated like one big cue.
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+2
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+00:01:01.000 --> 100:20:00.500
+I said Bear is coming now!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-overlapping.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-overlapping.vtt
new file mode 100644
index 0000000000..3f035d331f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-overlapping.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Cues that have overlapping time ranges.
+
+1
+00:00:01.000 --> 00:00:06.000
+Bear is Coming!!!!!
+
+2
+00:00:01.500 --> 00:00:05.000
+I said Bear is coming!!!!
+
+3
+00:00:02.000 --> 00:00:05.000
+I said Bear is coming now!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues.vtt
new file mode 100644
index 0000000000..125ed66785
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/cues.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+Cues may be separated by one or more blank lines.
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+
+2
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+
+
+
+3
+00:01:01.000 --> 100:20:00.500
+I said Bear is coming now!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/default-styles.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/default-styles.vtt
new file mode 100644
index 0000000000..d890ca3f71
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/default-styles.vtt
@@ -0,0 +1,19 @@
+WEBVTT
+
+COMMENT-->
+this is a comment, that will parse as part of the header;
+the STYLE and DEFAULTS below are parsed as invalid cues
+
+STYLE-->
+::cue(.narration) { color: blue; }
+
+DEFAULTS -->
+line:-1 align:middle size:50%
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:20:00.500
+I said Bear is coming!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/degenerate-cues.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/degenerate-cues.vtt
new file mode 100644
index 0000000000..c04390420f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/degenerate-cues.vtt
@@ -0,0 +1,5 @@
+WEBVTT
+
+00:00.000 --> 00:01.000
+00:02.000 --> 00:03.000
+00:04.000 --> 00:05.000
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/empty-cue.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/empty-cue.vtt
new file mode 100644
index 0000000000..dbfde34b69
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/empty-cue.vtt
@@ -0,0 +1,11 @@
+WEBVTT
+Empty cues should not be discarded.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/entities-wrong.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/entities-wrong.vtt
new file mode 100644
index 0000000000..f45fee4793
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/entities-wrong.vtt
@@ -0,0 +1,15 @@
+WEBVTT
+Invalid use of < and > characters.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+This cue has a less than < character.
+It turns everything from there on into an annotation
+for an empty tag and ends only at the next &gt; or &amp; character.
+
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+This cue has a greater than > character.
+Since it's not related to a &lt; character,
+it's just interpreted as text.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/entities.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/entities.vtt
new file mode 100644
index 0000000000..a8817954a6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/entities.vtt
@@ -0,0 +1,30 @@
+WEBVTT
+Cue content with escape characters for &, <, >, LRM, RLM and non-breaking space.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+This cue has an ampersand &amp; character.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+This cue has a less than &lt; character.
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+This cue has a greater than &gt; character.
+
+4
+00:02:01.000 --> 00:02:30.500 align:start position:20%
+This cue has a Left-to-Right Mark &lrm;.
+
+5
+00:02:31.000 --> 00:03:00.500 align:start position:20%
+This cue has a Right-to-Left Mark &rlm;.
+
+6
+00:03:01.000 --> 00:03:30.500 align:start position:20%
+This cue has a non-breaking space &nbsp;.
+
+7
+00:03:31.000 --> 00:04:00.500
+This & is parsed to the same as &amp;.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/interspersed-non-cue.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/interspersed-non-cue.vtt
new file mode 100644
index 0000000000..c825ab32e2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/interspersed-non-cue.vtt
@@ -0,0 +1,9 @@
+WEBVTT
+
+00:00.000 --> 00:01.000
+First
+
+Stray Id or other non-cue content
+
+00:02.000 --> 00:03.000
+Second
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/iso2022jp3.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/iso2022jp3.vtt
new file mode 100644
index 0000000000..10a1624386
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/iso2022jp3.vtt
@@ -0,0 +1,10 @@
+WEBVTT FILE
+Different encodings (iconv) should not be recognized as WebVTT a file.
+
+1
+00:00:00.000 --> 00:00:30.500
+$B7J5$H=CG(B
+
+2
+00:00:31.000 --> 00:20:00.500
+$BEENOITB-(B
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/large-timestamp.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/large-timestamp.vtt
new file mode 100644
index 0000000000..e6c18ce3bd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/large-timestamp.vtt
@@ -0,0 +1,5 @@
+WEBVTT
+
+1
+1234567:00:00.000 --> 1234567890:00:00.000
+A very long cue.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position-bad.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position-bad.vtt
new file mode 100644
index 0000000000..3d52175729
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position-bad.vtt
@@ -0,0 +1,30 @@
+WEBVTT
+Invalid positioning values (all settings are ignored).
+
+1
+00:00:00.000 --> 00:00:15.000 line:-0%
+Bear is Coming!!!!!
+Negative percentages are not allowed.
+Line position is ignored.
+
+2
+00:00:31.000 --> 00:00:45.500 line:+50%
+I said Bear is coming!!!!
+Non-numbers are not allowed.
+Line position is ignored.
+
+00:00:46.000 --> 00:01:00.500 line:+5
+I said Bear is coming!!!!
+Plus sign is not allowed.
+Line position is ignored.
+
+3
+00:01:01.000 --> 00:01:30.000 line:10%0%
+I said Bear is coming now!!!!
+Doesn't parse into a percentage.
+Line position is ignored.
+
+00:01:31.000 --> 00:02:00.500 line:-10l
+I said Bear is coming now!!!!
+Doesn't parse into a number.
+Line position is ignored.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position.vtt
new file mode 100644
index 0000000000..82f7e2a523
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position.vtt
@@ -0,0 +1,37 @@
+WEBVTT
+Cues with valid vertical line positioning values.
+
+1
+00:00:00.000 --> 00:00:15.000 line:0%
+Bear is Coming!!!!!
+Positioning on the top of the viewport, in the middle.
+
+00:00:15.500 --> 00:00:30.500 line:0
+Bear is Coming!!!!!
+This is line 0.
+Positioning on the top of the viewport, in the middle.
+
+2
+00:00:31.000 --> 00:00:45.500 line:50%
+I said Bear is coming!!!!
+Positioning on the center of the video.
+
+
+00:00:46.000 --> 00:01:00.500 line:5
+I said Bear is coming!!!!
+This is line 6 from the top of the video viewport.
+
+3
+00:01:01.000 --> 00:01:30.000 line:100%
+I said Bear is coming now!!!!
+Positioning on the bottom middle.
+
+00:01:31.000 --> 00:02:00.500 line:-1
+I said Bear is coming now!!!!
+This is the first line at the bottom of the video viewport.
+Positioning on the bottom middle. Only 1 line shows.
+
+00:02:01.000 --> 00:02:30.000 line:500
+I said Bear is coming now!!!!
+This is legal,
+even though the line will likely not be within the video viewport.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/markup-bad.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/markup-bad.vtt
new file mode 100644
index 0000000000..4ff7add2d7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/markup-bad.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+Cue text has invalid markup of <b>, <i>, <u>, <rt> and <ruby>. Has a bad effect on the remainder of the cue.
+
+1
+00:00:00.000 --> 00:00:15.000 align:start position:20%
+The following bear starts bold but end is broken:
+<b>Bear</ b> is Coming!!!!!
+
+00:00:15.500 --> 00:00:30.500 align:start position:20%
+The following bear is not in italics but the markup is removed:
+< i>Bear</i> is Coming!!!!!
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+The following bear is not underlined and markup is removed:
+I said < u >Bear</u> is coming!!!!
+
+3
+00:01:01.000 --> 00:01:30.000 align:start position:20%
+The following bear is not ruby annotated and markup is removed:
+I said <ru by>Bear<rt>bear with me</rt></ruby> is coming!!!!
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/markup.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/markup.vtt
new file mode 100644
index 0000000000..252a599b5f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/markup.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+Cues with <b>, <i>, <u>, <rt> and <ruby> tags (all valid).
+
+1
+00:00:00.000 --> 00:00:15.000 align:start position:20%
+The following bear is bold:
+<b>Bear</b> is Coming!!!!!
+
+00:00:15.500 --> 00:00:30.500 align:start position:20%
+The following bear is in italics and has a class of "larger":
+<i.larger>Bear</i> is Coming!!!!!
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+The following bear is underlined even though the element has a blank:
+I said <u >Bear</u> is coming!!!!
+
+3
+00:01:01.000 --> 00:01:30.000 align:start position:20%
+The following bear is ruby annotated:
+I said <ruby>Bear<rt>bear with me</rt></ruby> is coming!!!!
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata-area.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata-area.vtt
new file mode 100644
index 0000000000..255298aeb0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata-area.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+This is where metadata would go and these lines should be skipped.
+author = silviapf@google.com
+COMMENT-->
+this is a comment, that will parse as part of the header;
+the STYLE and DEFAULTS below are parsed as invalid cues
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:20:00.500
+I said Bear is coming!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata.vtt
new file mode 100644
index 0000000000..03d8cf4a1c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata.vtt
@@ -0,0 +1,38 @@
+WEBVTT
+
+00:00:00.000 --> 00:00:01.000
+Lorem ipsum dolor sit amet,
+
+00:00:02.000 --> 00:00:03.000
+consectetuer adipiscing elit,
+
+00:00:04.000 --> 00:00:05.000
+sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+00:00:06.000 --> 00:00:07.000
+Ut wisi enim ad minim veniam,
+
+00:00:08.000 --> 00:00:09.000
+quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+00:00:10.000 --> 00:00:11.000
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat,
+
+00:00:12.000 --> 00:00:13.000
+vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
+
+00:00:14.000 --> 00:00:15.000
+dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+00:00:16.000 --> 00:00:17.000
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id
+
+00:00:18.000 --> 00:00:19.000
+quod mazim placerat facer possim assum.
+
+00:00:20.000 --> 00:00:21.000
+Typi non habent claritatem insitam;
+
+00:00:22.000 --> 00:00:23.000
+est usus legentis in iis qui facit eorum claritatem.
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/missed-cues.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/missed-cues.vtt
new file mode 100644
index 0000000000..36e8366e90
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/missed-cues.vtt
@@ -0,0 +1,31 @@
+WEBVTT
+Events should be triggered for missed (skipped) cues during normal playback.
+
+1
+00:00:00.000 --> 00:00:01.500 align:start position:20%
+Bear is Coming!!!!!
+And what kind of a bear it is - just have look.
+
+2
+00:00:02.000 --> 00:00:02.500 align:start position:20%
+I said Bear is coming!!!!
+
+3
+00:00:05.500 --> 00:00:05.501 align:start position:20%
+I said Bear is coming now!!!!
+
+4
+00:00:05.700 --> 00:00:05.701 align:start position:20%
+This is the second missed cue in the test.
+
+5
+00:00:05.800 --> 00:00:05.800 align:start position:20%
+Third missed cue - zero-length cue.
+
+6
+00:00:05.850 --> 00:00:05.851 align:start position:20%
+Fourth missed cue.
+
+7
+00:00:05.950 --> 00:00:01.100
+Negative length cue. Should be treated correctly.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/no-newline-at-eof.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/no-newline-at-eof.vtt
new file mode 100644
index 0000000000..49e4e9051a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/no-newline-at-eof.vtt
@@ -0,0 +1,6 @@
+WEBVTT
+A file with no line terminator at the end should be fine (last cue should be recognized).
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/no-timings.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/no-timings.vtt
new file mode 100644
index 0000000000..4cb85b6df2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/no-timings.vtt
@@ -0,0 +1,13 @@
+WEBVTT
+Cues without timings are ignored.
+
+1
+00:00:00.000
+Bear is Coming!!!!!
+
+2
+00h:00m:31s.000ms
+I said Bear is coming!!!!
+
+3
+I said Bear is coming now!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/no-webvtt.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/no-webvtt.vtt
new file mode 100644
index 0000000000..12053b2703
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/no-webvtt.vtt
@@ -0,0 +1,10 @@
+AWEBVTT FILE
+A file with wrong file header should not be recognized as a webvtt file.
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:20:00.500
+I said Bear is coming!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-bad.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-bad.vtt
new file mode 100644
index 0000000000..58ca6792be
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-bad.vtt
@@ -0,0 +1,39 @@
+WEBVTT
+Invalid horizontal positioning values (all settings are ignored).
+
+1
+00:00:00.000 --> 00:00:15.500 position:-5%
+Bear is Coming!!!!!
+This would be off screen -> ignored.
+
+00:00:16.000 --> 00:00:30.500 position:150%
+Bear is Coming!!!!!
+This would be off screen -> ignored.
+
+2
+00:00:31.000 --> 00:00:45.500 position:50
+I said Bear is coming!!!!
+Missing percent sign -> ignored.
+
+2
+00:00:46.000 --> 00:01:00.500 position:50a%
+I said Bear is coming!!!!
+Surplus character between number and percent sign -> ignored.
+
+3
+00:01:01.000 --> 00:01:30.500 position:100%-fj
+I said Bear is coming now!!!!
+Surplus characters after percent sign -> ignored.
+
+
+00:01:31.000 --> 00:02:00.500 position:100asdf
+I said Bear is coming now!!!!
+Surplus characters and no percent sign -> ignored.
+
+00:02:01.000 --> 00:02:02.000 position:e50%
+I said Bear is coming now!!!!
+Surplus characters at beginning of size string -> ignored.
+
+00:02:02.100 --> 00:02:02.500 position:5g0%
+I said Bear is coming now!!!!
+Surplus characters in middle of size string -> ignored.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-ltr.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-ltr.vtt
new file mode 100644
index 0000000000..b23a7446b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-ltr.vtt
@@ -0,0 +1,21 @@
+WEBVTT
+Valid horizontal positioning values.
+
+1
+00:00:00.000 --> 00:00:30.500 position:0%
+الدب قادم!!!!!
+تحديد المواقع في أسفل اليمين.
+
+2
+00:00:31.000 --> 00:00:45.500 position:50%
+قلت الدب قادم!!
+تحديد المواقع في منتصف القاع.
+
+00:00:46.000 --> 00:01:00.500
+قلت الدب قادم!!
+المواقع الافتراضية على منتصف أسفل تزال قائمة.
+
+3
+00:01:01.000 --> 00:02:00.500 position:100%
+قلت الدب قادم الآن!!
+غادر لتحديد المواقع في القاع.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning.vtt
new file mode 100644
index 0000000000..ccf6024da0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning.vtt
@@ -0,0 +1,21 @@
+WEBVTT
+Valid horizontal positioning values.
+
+1
+00:00:00.000 --> 00:00:30.500 position:0%
+Bear is Coming!!!!!
+Positioning on the left bottom.
+
+2
+00:00:31.000 --> 00:00:45.500 position:50%
+I said Bear is coming!!!!
+Positioning on the bottom middle.
+
+00:00:46.000 --> 00:01:00.500
+I said Bear is coming!!!!
+Default positioning on the bottom middle still.
+
+3
+00:01:01.000 --> 00:02:00.500 position:100%
+I said Bear is coming now!!!!
+Positioning on the bottom right.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/settings-bad-separation.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/settings-bad-separation.vtt
new file mode 100644
index 0000000000..cbfe6ea6e9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/settings-bad-separation.vtt
@@ -0,0 +1,20 @@
+WEBVTT
+Cues settings may only be separated by spaces or tabs, but illegal characters
+between settings are ignored.
+
+1
+00:00:00.000 --> 00:00:30.500 - line:43% position:10% -
+Bear is Coming!!!!! Bad separator ignored.
+
+2
+00:00:31.000 --> 00:01:00.500 --> position:50% Vertical:lr align:end
+I said Bear is coming!!!! Bad separator and setting ignored.
+
+3
+00:01:01.000 --> 00:02:00.500 <align:end> <position:90%>
+I said Bear is coming now!!!! Bad setting markup. Not ignored because the settings are
+not delimited by spaces or tabs.
+
+4
+00:02:01.000 --> 100:20:00.500 / vertical:lr | position:90%
+I said Bear is coming now!!!! Bad separator ignored.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/settings.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/settings.vtt
new file mode 100644
index 0000000000..dd6b02296a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/settings.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+Cue settings may be separated by spaces or tabs.
+
+1
+00:00:00.000 --> 00:00:30.500 line:100% align:start
+Bear is Coming!!!!! One blank.
+
+2
+00:00:31.000 --> 00:01:00.500 position:40% vertical:rl line:15%
+I said Bear is coming!!!! Several blanks.
+
+3
+00:01:01.000 --> 00:02:00.500 align:middle position:10%
+I said Bear is coming now!!!! Tab separator.
+
+4
+00:02:01.000 --> 100:20:00.500 line:95% vertical:lr align:end
+I said Bear is coming now!!!! Tab separators. \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/simple-captions.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/simple-captions.vtt
new file mode 100644
index 0000000000..9815b111da
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/simple-captions.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+
+0
+00:00:04.000 --> 00:00:04.500
+First cue
+
+1
+00:00:04.500 --> 00:00:05.000
+Lorem
+
+2
+00:00:05.000 --> 00:00:05.500
+ipsum
+
+3
+00:00:05.500 --> 00:00:05.501
+Missed cue with pause-on-exit
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/sorted-dispatch.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/sorted-dispatch.vtt
new file mode 100644
index 0000000000..438ea6abf9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/sorted-dispatch.vtt
@@ -0,0 +1,34 @@
+WEBVTT
+Enter and exit events should be dispatched in a sorted order according to their times.
+
+0
+00:00:04.000 --> 00:00:04.500
+Missed cue that should not be considered because of seeking.
+
+1
+00:00:05.100 --> 00:00:05.800 align:start position:20%
+Bear is Coming!!!!!
+
+2
+00:00:05.100 --> 00:00:05.101
+Missed cue 1
+
+3
+00:00:05.100 --> 00:00:05.301
+And what kind of a bear it is - just have look.
+
+4
+00:00:05.100 --> 00:00:05.101
+Missed Cue 2
+
+5
+00:00:05.300 --> 00:00:05.800 align:start position:20%
+I said Bear is coming!!!!
+
+6
+00:00:05.990 --> 00:00:05.993 align:start position:20%
+I said Bear is coming now!!!!
+
+7
+00:00:05.994 --> 00:00:05.998 align:start position:20%
+Bear is already here
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp-bad.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp-bad.vtt
new file mode 100644
index 0000000000..4479cdb722
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp-bad.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+Invalid <timestamp> markup.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+This <00:00:05.000>cue <00:00:10.000>is <00:00:12.000>painted <00:00:08.000>on.
+But since the last two timestamps are out of order, they are ignored.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+I <00:00:20.000>said <00:00:22.000>Bear <00:00:24.000>is <00:00:26.000>coming!!!!
+All of these timestamps are before the start of the cue, so get ignored.
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+I <00:02:05.000>said <00:02:10.000>Bear <00:02:15.000>is <00:02:20.000>coming <00:02:25.000>now!!!!
+All of these timestamps are after the end of the cue, so get ignored.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp.vtt
new file mode 100644
index 0000000000..17d464bfed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Paint-on text in cues with <timestamp> markup.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+This <00:00:05.000>cue <00:00:10.000>is <00:00:15.000>painted <00:00:20.000>on.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+I <00:00:35.000>said <00:00:40.000>Bear <00:00:45.000>is <00:00:50.000>coming!!!!
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+I <00:01:05.000>said <00:01:10.000>Bear <00:01:15.000>is <00:01:20.000>coming <00:01:25.000>now!!!!
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour-error.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour-error.vtt
new file mode 100644
index 0000000000..c33f8a96c3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour-error.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+These timings all have errors and all cues should be ignored.
+
+1
+00:00.00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:01:00:500
+I said Bear is coming!!!!
+
+3
+00:01:01.000 --> 00:120:00.500
+I said Bear is coming now!!!!
+
+4
+00:02:01.000 - 00:03:00.500
+I said Bear is coming now!!!!
+
+5
+00h:03m:01s.000ms --> 00h:03m:00s.500ms
+I said Bear is coming now!!!!
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour.vtt
new file mode 100644
index 0000000000..b708b83338
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Timings can optionally contain an hour.
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+
+3
+00:01:01.000 --> 100:20:00.500
+I said Bear is coming now!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour-errors.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour-errors.vtt
new file mode 100644
index 0000000000..e4bf27d4e6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour-errors.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+These timings all have errors and all cues should be ignored.
+
+1
+00.00.000 --> 00:30.500
+Bear is Coming!!!!!
+
+2
+00:31.000 --> 01:00:500
+I said Bear is coming!!!!
+
+3
+01:01.000 --> 120:00.500
+I said Bear is coming now!!!!
+
+4
+01:01.000 - 02:00.500
+I said Bear is coming now!!!!
+
+5
+02:01.000 --> 03m:00.500
+I said Bear is coming now!!!!
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour.vtt
new file mode 100644
index 0000000000..745c34ff9f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+The hour of a timestamp is optional.
+
+1
+00:00.000 --> 00:30.500
+Bear is Coming!!!!!
+
+2
+00:31.000 --> 01:00.500
+I said Bear is coming!!!!
+
+3
+01:01.000 --> 02:00.500
+I said Bear is coming now!!!!
+
+4
+02:01.000 --> 03:00.500
+tab separators \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-whitespace.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-whitespace.vtt
new file mode 100644
index 0000000000..9d9ac9a38a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-whitespace.vtt
@@ -0,0 +1,51 @@
+WEBVTT
+Whitespace (U+0020, U+0009, U+000C) surrounding cue-timings separator ("-->") is optional
+
+1
+00:00:00.100 -->00:00:01.500
+Single U+0020 SPACE left of cue-timings separator
+
+2
+00:00:00.100--> 00:00:01.500
+Single U+0020 SPACE right of cue-timings separator
+
+3
+00:00:00.100 -->00:00:01.500
+Single U+0009 TAB left of cue-timings separator
+
+4
+00:00:00.100--> 00:00:01.500
+Single U+0009 TAB right of cue-timings separator
+
+5
+00:00:00.100 -->00:00:01.500
+Single U+000C FORM FEED left of cue-timings separator
+
+6
+00:00:00.100--> 00:00:01.500
+Single U+000C FORM FEED right of cue-timings separator
+
+7
+00:00:00.100 -->00:00:01.500
+Several U+0020 SPACE left of cue-timings separator
+
+8
+00:00:00.100--> 00:00:01.500
+Several U+0020 SPACE right of cue-timings separator
+
+9
+00:00:00.100 -->00:00:01.500
+Several U+0009 TAB left of cue-timings separator
+
+10
+00:00:00.100--> 00:00:01.500
+Several U+0009 TAB right of cue-timings separator
+
+11
+00:00:00.100 -->00:00:01.500
+Several U+000C FORM FEED left of cue-timings separator
+
+12
+00:00:00.100--> 00:00:01.500
+Several U+000C FORM FEED right of cue-timings separator
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.de.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.de.vtt
new file mode 100644
index 0000000000..9eaf3d31e4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.de.vtt
@@ -0,0 +1,4 @@
+WEBVTT
+
+00:00:00.000 --> 00:00:01.000
+German
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.en.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.en.vtt
new file mode 100644
index 0000000000..4241f35b56
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.en.vtt
@@ -0,0 +1,4 @@
+WEBVTT
+
+00:00:00.000 --> 00:00:01.000
+English
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.fr.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.fr.vtt
new file mode 100644
index 0000000000..5523224e0d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.fr.vtt
@@ -0,0 +1,4 @@
+WEBVTT
+
+00:00:00.000 --> 00:00:01.000
+french
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.vtt
new file mode 100644
index 0000000000..c916c0983b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/track.vtt
@@ -0,0 +1,4 @@
+WEBVTT
+
+00:00:00.000 --> 00:00:01.000
+test
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/unsupported-markup.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/unsupported-markup.vtt
new file mode 100644
index 0000000000..b4ea7ea09b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/unsupported-markup.vtt
@@ -0,0 +1,23 @@
+WEBVTT
+Any HTML markup that is not supported should be ignored.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+<h1>Bear is Coming!!!!!</h1>
+<p>And what kind of a bear it is - just have <a href="webpage.html">look</a>.</p>
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+<ul>
+ <li>I said Bear is coming!!!!</li>
+ <li>I said Bear is still coming!!!!</li>
+</ul>
+
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+<ol>
+ <li>I said Bear is coming now!!!!</li>
+ <li><img src="bear.png" alt="mighty bear"></li>
+ <li><video src="bear_ad.webm" controls></video></li>
+</ol> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/utf8.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/utf8.vtt
new file mode 100644
index 0000000000..8dd8f27948
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/utf8.vtt
@@ -0,0 +1,10 @@
+WEBVTT
+UTF-8 encoded characters should be recognized.
+
+1
+00:00:00.000 --> 00:00:30.500
+景気判断
+
+2
+00:00:31.000 --> 00:20:00.500
+電力不足 \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-bad.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-bad.vtt
new file mode 100644
index 0000000000..8e7b3b738d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-bad.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+Invalid vertical direction settings (all settings are ignored).
+
+1
+00:00:00.000 --> 00:00:30.500 vertical:#vertical
+Bear is Coming!!!!!
+Normal rendering - direction setting is ignored.
+
+2
+00:00:31.000 --> 00:01:00.500 vertical:verticallr
+I said Bear is coming!!!!
+Normal rendering - direction setting is ignored.
+
+3
+00:01:01.000 --> 00:02:00.500 vertical:vertical-rl
+I said Bear is coming now!!!!
+Normal rendering - direction setting is ignored.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-ltr.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-ltr.vtt
new file mode 100644
index 0000000000..74838369d2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-ltr.vtt
@@ -0,0 +1,20 @@
+WEBVTT
+Valid vertical direction settings.
+
+1
+00:00:00.000 --> 00:00:30.500 vertical:rl
+الدب قادم!!!!!
+يجعل على الجانب الأيمن من المعاينة الفيديو والمتوسطة الانحياز ،
+أسفل إلى أعلى، وتزايد اليسار.
+
+2
+00:00:31.000 --> 00:01:00.500 vertical:lr
+قلت الدب قادم!!
+يجعل على الجانب الأيسر من المعاينة الفيديو والمتوسطة الانحياز ،
+أسفل إلى أعلى، وتنامي اليمين.
+
+3
+00:01:01.000 --> 00:02:00.500 vertical:rl align:start position:0%
+قلت الدب قادم الآن!!
+يجعل على الجانب الأيمن من المعاينة الفيديو ، على حد سواء أسفل محاذاة
+لمربع جديلة والنص داخل النص ، من أسفل إلى أعلى، وتزايد اليسار.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/valign.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/valign.vtt
new file mode 100644
index 0000000000..f757a365e3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/valign.vtt
@@ -0,0 +1,20 @@
+WEBVTT
+Valid vertical direction settings.
+
+1
+00:00:00.000 --> 00:00:30.500 vertical:rl
+Bear is Coming!!!!!
+Renders on the right side of the video viewport, middle aligned,
+top to bottom, growing left.
+
+2
+00:00:31.000 --> 00:01:00.500 vertical:lr
+I said Bear is coming!!!!
+Renders on the left side of the video viewport, middle aligned,
+top to bottom, growing right.
+
+3
+00:01:01.000 --> 00:02:00.500 vertical:rl align:start position:0%
+I said Bear is coming now!!!!
+Renders on the right side of the video viewport, top aligned both
+for the cue box and the text within, text from top to bottom, growing left.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/voice-bad.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/voice-bad.vtt
new file mode 100644
index 0000000000..12ffdeb82e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/voice-bad.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+Invalid <v> voice markup.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+< v Speaker>Bear is Coming!!!!!</v>
+This is two annotations for an empty tag.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+<v&Doe Hunter>I said Bear is coming!!!!</v>
+This does not parse as a voice tag.
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+I said <v-Speaker>Bear is coming now</v>!!!!
+This does not parse as a voice tag.
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/voice.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/voice.vtt
new file mode 100644
index 0000000000..d6cfc6887f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/voice.vtt
@@ -0,0 +1,15 @@
+WEBVTT
+Cue text fragment with <v> voice markup mapped to HTML <q> element with @title for annotation.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+<v.blue Speaker>Bear is Coming!!!!!</v>
+Text span with a class and an annotation.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+<v Doe Hunter>I said Bear is coming!!!!</v>
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+I said <v.blue Speaker>Bear is coming now</v>!!!!
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/vp8-vorbis-webvtt.webm b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/vp8-vorbis-webvtt.webm
new file mode 100644
index 0000000000..c626f86e33
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/vp8-vorbis-webvtt.webm
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-file.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-file.vtt
new file mode 100644
index 0000000000..0c1a5fb158
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-file.vtt
@@ -0,0 +1,9 @@
+WEBVTT FILE
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:20:00.500
+I said Bear is coming!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-rubbish.vtt b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-rubbish.vtt
new file mode 100644
index 0000000000..dacc215409
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-rubbish.vtt
@@ -0,0 +1,10 @@
+WEBVTT asdfasdfauhio
+Rubbish after the WEBVTT header should be ignored.
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:20:00.500
+I said Bear is coming!!!! \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/src-clear-cues.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/src-clear-cues.html
new file mode 100644
index 0000000000..3ba8c9db88
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/src-clear-cues.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<title>track element changing "track URL" and clearing cues</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+(async_test(document.title+', set mode, add cue, set src')).step(function(){
+ var track = document.createElement('track');
+ var c = new VTTCue(0, 1, 'foo');
+ c.id = 'id';
+ track.track.addCue(c);
+ assert_equals(track.track.cues, null, 'cues before setting src or mode');
+ track.track.mode = 'showing';
+ assert_equals(track.track.cues.length, 1, 'cues after setting mode');
+ var cues = track.track.cues;
+ track.src = 'data:,a';
+ assert_equals(track.track.cues.length, 0, 'cues.length after setting src');
+ assert_equals(track.track.cues, cues, 'track.track.cues sameness after setting src');
+ assert_equals(c.id, 'id', 'liveness of removed cue');
+ this.done();
+});
+
+(async_test(document.title+', set mode, set src, add cue, change src')).step(function(){
+ var track = document.createElement('track');
+ track.track.mode = 'showing';
+ track.src = 'data:,a';
+ var c = new VTTCue(0, 1, 'foo');
+ c.id = 'id';
+ track.track.addCue(c);
+ assert_equals(track.track.cues.length, 1, 'cues.length before changing src');
+ var cues = track.track.cues;
+ track.src = 'data:,b';
+ assert_equals(track.track.cues.length, 0, 'cues.length after changing src');
+ assert_equals(track.track.cues, cues, 'track.track.cues sameness after changing src');
+ assert_equals(c.id, 'id', 'liveness of removed cue');
+ this.done();
+});
+
+(async_test(document.title+', set mode, add cue, change mode to disabled, set src')).step(function(){
+ var track = document.createElement('track');
+ track.track.mode = 'showing';
+ var c = new VTTCue(0, 1, 'foo');
+ c.id = 'id';
+ track.track.addCue(c);
+ var cues = track.track.cues;
+ track.track.mode = 'disabled';
+ track.src = 'data:,a';
+ assert_equals(cues.length, 0, 'cues.length after changing src');
+ assert_equals(c.id, 'id', 'liveness of removed cue');
+ this.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/src-empty-string.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/src-empty-string.html
new file mode 100644
index 0000000000..27c76b6be4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/src-empty-string.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Setting HTMLTrackElement.src to the empty string fires 'error' and sets readyState to ERROR</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/media.html#sourcing-out-of-band-text-tracks">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video></video>
+<script>
+async_test(t => {
+ let track = document.createElement("track");
+ track.src = '';
+ track.default = true;
+ track.onerror = t.step_func_done(() => {
+ assert_equals(track.readyState, HTMLTrackElement.ERROR);
+ });
+ track.onload = t.unreached_func('fired load');
+
+ assert_equals(track.readyState, HTMLTrackElement.NONE);
+
+ document.querySelector('video').appendChild(track);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-active-cues.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-active-cues.html
new file mode 100644
index 0000000000..a7c08a2e3e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-active-cues.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Ensure that no text track cues are active after the video is unloaded</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+ var eventCount = 0;
+
+ function eventCallback() {
+ eventCount++;
+ if (eventCount == 3) {
+ assert_equals(trackElement.track.activeCues.length, 1);
+ video.src = '';
+ }
+ }
+
+ var video = document.createElement('video');
+ video.src = getVideoURI('/media/movie_5');
+ // uanset media element's `show-poster` flag in order to run `time marches on`
+ // when we add new cues into media element's cues list.
+ video.play();
+ var trackElement = document.createElement('track');
+
+ trackElement.onload = t.step_func(eventCallback);
+ trackElement.oncuechange = t.step_func(eventCallback);
+ video.oncanplaythrough = t.step_func(eventCallback);
+
+ video.onerror = t.step_func_done(function(event) {
+ assert_equals(event.target, video);
+ assert_not_equals(video.error, null);
+ assert_equals(video.error.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
+ assert_equals(video.networkState, HTMLMediaElement.NETWORK_NO_SOURCE);
+ assert_equals(trackElement.track.activeCues.length, 0);
+ });
+
+ trackElement.src = 'resources/captions-fast.vtt';
+ trackElement.kind = 'captions';
+ trackElement.default = true;
+ video.appendChild(trackElement);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-add-remove-cue.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-add-remove-cue.html
new file mode 100644
index 0000000000..e738964001
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-add-remove-cue.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<title>TextTrack's addCue and removeCue</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+ var video = document.createElement("video");
+ var trackElement = document.createElement("track");
+
+ trackElement.onload = t.step_func_done(function() {
+ var cues = trackElement.track.cues;
+ // Test cues loaded from the file.
+ assert_equals(cues.length, 4);
+ assert_equals(cues.getCueById("1").startTime, 0);
+ assert_equals(cues[1].startTime, 31);
+ assert_equals(cues[2].startTime, 61);
+ assert_equals(cues.getCueById("4").startTime, 121);
+ assert_equals(cues.getCueById("junk"), null);
+
+ // Create a new cue, check values.
+ var textCue = new VTTCue(33, 3.4, "Sausage?");
+ assert_equals(textCue.track, null);
+ assert_equals(textCue.id, "");
+ assert_equals(textCue.startTime, 33);
+ assert_equals(textCue.endTime, 3.4);
+ assert_equals(textCue.pauseOnExit, false);
+ assert_equals(textCue.vertical, "");
+ assert_equals(textCue.snapToLines, true);
+ assert_equals(textCue.line, "auto");
+ assert_equals(textCue.position, "auto");
+ assert_equals(textCue.size, 100);
+ assert_equals(textCue.align, "center");
+
+ // Remove the unadded track, make sure it throws correctly.
+ assert_throws_dom("NotFoundError", function() { trackElement.track.removeCue(textCue); });
+
+ // Add the new cue to a track, make sure it is inserted correctly.
+ trackElement.track.addCue(textCue);
+ assert_equals(textCue.track, trackElement.track);
+ assert_equals(cues[1].startTime, 31);
+ assert_equals(cues[2].startTime, 33);
+ assert_equals(cues[3].startTime, 61);
+
+ // create a new cue and add it to a track created with
+ // video.addTextTrack, make sure it is inserted correctly.
+ var newTrack = video.addTextTrack("subtitles", "French subtitles", "fr");
+ newTrack.mode = "showing";
+ var newCue = new VTTCue(0, 1, "Test!");
+ newTrack.addCue(newCue);
+ assert_equals(newCue, newTrack.cues[0])
+ assert_equals(newCue.track, newTrack);
+ assert_equals(newCue.id, "");
+ assert_equals(newCue.startTime, 0);
+ assert_equals(newCue.endTime, 1);
+ assert_equals(newCue.pauseOnExit, false);
+ assert_equals(newCue.vertical, "");
+ assert_equals(newCue.snapToLines, true);
+ assert_equals(newCue.line, "auto");
+ assert_equals(newCue.position, "auto");
+ assert_equals(newCue.size, 100);
+ assert_equals(newCue.align, "center");
+
+ trackElement.track.removeCue(textCue);
+ assert_equals(textCue.track, null);
+ assert_equals(cues[1].startTime, 31);
+ assert_equals(cues[2].startTime, 61);
+
+ // Remove a cue added from the WebVTT file.
+ textCue = cues[2];
+ trackElement.track.removeCue(textCue);
+ assert_equals(textCue.track, null);
+ assert_equals(cues[1].startTime, 31);
+ assert_equals(cues[2].startTime, 121);
+
+ // Try to remove the cue again.
+ assert_throws_dom("NotFoundError", function() { trackElement.track.removeCue(textCue); });
+
+ // Add a cue before all the existing cues.
+ trackElement.track.addCue(new VTTCue(0, 31, "I am first"));
+ assert_equals(cues[0].startTime, 0);
+ assert_equals(cues[0].endTime, 31);
+ assert_equals(cues[1].startTime, 0);
+ assert_equals(cues[1].endTime, 30.5);
+ assert_equals(cues[2].startTime, 31);
+ });
+
+ trackElement.src = "resources/settings.vtt";
+ trackElement.kind = "captions";
+ trackElement.default = true;
+ video.appendChild(trackElement);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-add-track.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-add-track.html
new file mode 100644
index 0000000000..c924c92da9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-add-track.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>'addtrack' event is fired when a TextTrack is created</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+ var video = document.createElement('video');
+
+ var trackElement = document.createElement('track');
+ video.appendChild(trackElement);
+ var tracks = [];
+ tracks.push(trackElement.track);
+
+ // Register the 'addtrack' listener after creating the element
+ // to make sure the event is dispatched asynchronously.
+ video.textTracks.onaddtrack = t.step_func(function(event) {
+ assert_equals(event.target, video.textTracks);
+ assert_true(event instanceof TrackEvent, 'instanceof');
+ assert_equals(event.track, tracks[video.textTracks.length - 1]);
+
+ if (video.textTracks.length == 1) {
+ tracks.push(video.addTextTrack('captions', 'Caption Track', 'en'));
+ assert_equals(video.textTracks.length, 2);
+ } else {
+ t.done();
+ }
+ });
+
+ trackElement.src = 'resources/webvtt-file.vtt';
+ trackElement.track.mode = 'hidden';
+ assert_equals(video.textTracks.length, 1);
+ assert_equals(trackElement.readyState, HTMLTrackElement.NONE);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-addtrack-kind.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-addtrack-kind.html
new file mode 100644
index 0000000000..d058bf2987
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-addtrack-kind.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>addTextTrack() only accepts known "kind" values</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+ var trackCount = 0;
+
+ function addTrack(type) {
+ video.addTextTrack(type);
+ assert_equals(video.textTracks.length, ++trackCount);
+ }
+
+ var video = document.createElement("video");
+ assert_equals(video.textTracks.length, 0);
+ assert_throws_js(TypeError, function() { video.addTextTrack("kaptions"); });
+ assert_equals(video.textTracks.length, 0);
+
+ addTrack("subtitles");
+ addTrack("captions");
+ addTrack("descriptions");
+ addTrack("chapters");
+ addTrack("metadata");
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-api-texttracks.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-api-texttracks.html
new file mode 100644
index 0000000000..b2840d235a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-api-texttracks.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Track element - text tracks API test</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#text-track-api">
+<link rel="author" title="Hyunjin Cho">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Track element and API Test</h1>
+<div style="display:none;">
+ <video id="tracktest" src="/media/movie_300.mp4">
+ <track kind="subtitles" src="resources/track.en.vtt" srclang="en" label="English">
+ <track kind="captions" src="resources/track.en.vtt" srclang="en" label="English with Captions">
+ <track id="french" kind="subtitles" src="resources/track.fr.vtt" srclang="fr" label="Francais">
+ <track kind="subtitles" src="resources/track.de.vtt" srclang="de" label="Deutsch">
+ </video>
+</div>
+<div id="log"></div>
+<script>
+test(function() {
+ var t1 = document.getElementById('tracktest').textTracks;
+ assert_not_equals(t1, undefined, "textTracks member should not be undefined");
+}, "Check the track elements");
+test(function() {
+ var t2 = document.getElementById('tracktest').textTracks.getTrackById("french");
+ assert_not_equals(t2, undefined, "textTracks member should not be undefined");
+}, "Check getTrackById method");
+test(function() {
+ var t3 = document.getElementById('tracktest').textTracks.length;
+ assert_equals(t3, 4, "textTracks List should be 4");
+}, "Count track list");
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-change-event.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-change-event.html
new file mode 100644
index 0000000000..7a17dee2a7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-change-event.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>A 'change' event is fired when a TextTrack's mode changes</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+ var video = document.createElement('video');
+ var track = video.addTextTrack('subtitles', 'test', 'en');
+
+ // addTextTrack() defaults to "hidden", so settings
+ // mode to "showing" should trigger a "change" event.
+ track.mode = 'showing';
+ assert_equals(video.textTracks.length, 1);
+
+ video.textTracks.onchange = t.step_func_done(function(event) {
+ assert_equals(event.target, video.textTracks);
+ assert_true(event instanceof Event, 'instanceof');
+ assert_false(event.hasOwnProperty('track'), 'unexpected property found: "track"');
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-css-cue-pseudo-class.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-css-cue-pseudo-class.html
new file mode 100644
index 0000000000..d18f8b55cd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-css-cue-pseudo-class.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+:cue { color: red; }
+:cue(i) { color: red; }
+</style>
+<script>
+test(function() {
+ assert_equals(document.styleSheets[0].cssRules.length, 0);
+}, ":cue pseudo-class is not supported and dropped during parsing");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-empty.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-empty.html
new file mode 100644
index 0000000000..59f8fc6c7b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-empty.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Invoke getCueAsHTML() on an empty cue</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+ var emptyCue = new VTTCue(0, 0, "");
+ var fragment = emptyCue.getCueAsHTML();
+
+ // The getCueAsHTML() method should return a document fragment.
+ assert_true(fragment instanceof DocumentFragment);
+
+ // The document fragment should have one child, an empty Text node.
+ assert_equals(fragment.childNodes.length, 1);
+ assert_equals(fragment.childNodes[0].constructor.name, Text.name);
+ assert_equals(fragment.childNodes[0].length, 0);
+ assert_equals(fragment.childNodes[0].data, "");
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-inline.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-inline.html
new file mode 100644
index 0000000000..3b4c3542a9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-inline.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Add a track and change its mode through JS</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <source src="/media/test.mp4" type="video/mp4">
+ <source src="/media/test.ogv" type="video/ogg">
+</video>
+<script>
+test(function() {
+ var video = document.querySelector('video');
+ var track = video.addTextTrack('captions', 'English', 'en');
+ track.addCue(new VTTCue(0.0, 10.0, 'wow wow'));
+ track.mode = 'showing';
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable-fragment.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable-fragment.html
new file mode 100644
index 0000000000..713e781996
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable-fragment.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<title>Cue fragment is mutable</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+p, div { display: none; }
+</style>
+<video>
+ <track src="resources/captions-html.vtt" kind="captions" default>
+ <script>
+ async_test(function(t) {
+ var video = document.querySelector("video");
+ var testTrack = document.querySelector("track");
+
+ video.oncanplaythrough = t.step_func(testMutability);
+ testTrack.onload = t.step_func(testMutability);
+
+ var fragment;
+ var eventCount = 0;
+ function testMutability() {
+ eventCount++;
+ if (eventCount != 2)
+ return;
+
+ var testCue = testTrack.track.cues[0];
+
+ // Test initial cue contents.
+ assert_equals(testCue.text, "Lorem <b>ipsum</b> <u>dolor</u> <i.sit>sit</i> amet,");
+
+ // Cue getCueAsHTML() should return a correct fragment.
+ createExpectedFragment(document.createDocumentFragment());
+ assert_true(fragment.isEqualNode(testCue.getCueAsHTML()));
+
+ // Appending getCuesAsHTML() twice to the DOM should be succesful.
+ document.getElementsByTagName("div")[0].appendChild(testCue.getCueAsHTML());
+ document.getElementsByTagName("div")[1].appendChild(testCue.getCueAsHTML());
+
+ createExpectedFragment(document.createElement("div"));
+ assert_true(fragment.isEqualNode(document.getElementsByTagName("div")[0]));
+ assert_true(fragment.isEqualNode(document.getElementsByTagName("div")[1]));
+
+ // The fragment returned by getCuesAsHTML() should be independently mutable.
+ document.getElementsByTagName("div")[0].firstChild.textContent = "Different text ";
+ assert_false(fragment.isEqualNode(document.getElementsByTagName("div")[0]));
+ assert_true(fragment.isEqualNode(document.getElementsByTagName("div")[1]));
+
+ // Calling twice getCueAsHTML() should not return the same fragment.
+ assert_not_equals(testCue.getCueAsHTML(), testCue.getCueAsHTML());
+
+ t.done();
+ }
+
+ function createExpectedFragment(rootNode) {
+ fragment = rootNode;
+ fragment.appendChild(document.createTextNode("Lorem "));
+
+ var bold = document.createElement("b");
+ bold.appendChild(document.createTextNode("ipsum"));
+ fragment.appendChild(bold);
+
+ fragment.appendChild(document.createTextNode(" "));
+
+ var underline = document.createElement("u");
+ underline.appendChild(document.createTextNode("dolor"));
+ fragment.appendChild(underline);
+
+ fragment.appendChild(document.createTextNode(" "));
+
+ var italics = document.createElement("i");
+ italics.className = "sit";
+ italics.appendChild(document.createTextNode("sit"));
+ fragment.appendChild(italics);
+
+ fragment.appendChild(document.createTextNode(" amet,"));
+ }
+
+ video.src = getVideoURI("/media/counting");
+ });
+ </script>
+</video>
+<p>Fragment 1</p>
+<div></div>
+<p>Fragment 2</p>
+<div></div> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable.html
new file mode 100644
index 0000000000..26a6b84f8a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<title>Modifying attributes of a VTTCue</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track id="captions" src="resources/captions.vtt" kind="captions" default>
+ <script>
+ async_test(function(t) {
+ var track = document.querySelector("track");
+
+ track.onload = t.step_func_done(function() {
+ var cues = track.track.cues;
+
+ // Test initial values.
+ textCue = cues.getCueById("1");
+
+ assert_equals(textCue.startTime, 0);
+ assert_equals(textCue.endTime, 1.0);
+ assert_equals(textCue.pauseOnExit, false);
+ assert_equals(textCue.vertical, "");
+ assert_equals(textCue.snapToLines, true);
+ assert_equals(textCue.line, "auto");
+ assert_equals(textCue.position, "auto");
+ assert_equals(textCue.size, 100);
+ assert_equals(textCue.align, "center");
+
+ // Modify cue values.
+ textCue.startTime = 1.1;
+ assert_equals(textCue.startTime, 1.1);
+
+ textCue.endTime = 3.9;
+ assert_equals(textCue.endTime, 3.9);
+
+ textCue.pauseOnExit = true;
+ assert_equals(textCue.pauseOnExit, true);
+
+ // http://dev.w3.org/html5/webvtt/#dfn-dom-vttcue-vertical
+ // On setting, the text track cue writing direction must be
+ // set to the value given in the first cell of the row in
+ // the table above whose second cell is a case-sensitive
+ // match for the new value.
+ textCue.vertical = "RL";
+ assert_equals(textCue.vertical, "");
+ textCue.vertical = "rl";
+ assert_equals(textCue.vertical, "rl");
+
+ textCue.snapToLines = false;
+ assert_equals(textCue.snapToLines, false);
+
+ // http://dev.w3.org/html5/webvtt/#dfn-vttcue-line
+ // On setting, the text track cue line position must be set
+ // to the new value; if the new value is the string "auto",
+ // then it must be interpreted as the special value auto.
+ assert_equals(textCue.line, "auto");
+ assert_throws_js(TypeError, function() { textCue.line = "gazonk"; });
+ assert_equals(textCue.line, "auto");
+ textCue.line = 42;
+ assert_equals(textCue.line, 42);
+ textCue.line = -2;
+ assert_equals(textCue.line, -2);
+ textCue.line = 102;
+ assert_equals(textCue.line, 102);
+ textCue.snapToLines = true;
+ textCue.line = -2;
+ assert_equals(textCue.line, -2);
+ textCue.line = 102;
+ assert_equals(textCue.line, 102);
+
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-line
+ // On setting, if the new value is negative or greater than 100,
+ // then throw an IndexSizeError exception.
+ // Otherwise, set the text track cue text position to the new value.
+ assert_throws_dom("IndexSizeError", function() { textCue.position = -200; });
+ assert_throws_dom("IndexSizeError", function() { textCue.position = 110; });
+ textCue.position = 11;
+ assert_equals(textCue.position, 11);
+
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-size
+ // On setting, if the new value is negative or greater than 100,
+ // then throw an IndexSizeError exception.
+ // Otherwise, set the text track cue size to the new value.
+ assert_throws_dom("IndexSizeError", function() { textCue.size = -200 });
+ assert_throws_dom("IndexSizeError", function() { textCue.size = 110 });
+ textCue.size = 57;
+ assert_equals(textCue.size, 57);
+
+ // http://dev.w3.org/html5/webvtt/#dfn-dom-vttcue-align
+ // On setting, the text track cue text alignment must be
+ // set to the value given in the first cell of the row
+ // in the table above whose second cell is a case-sensitive
+ // match for the new value.
+ textCue.align = "End";
+ assert_equals(textCue.align, "center");
+ textCue.align = "end";
+ assert_equals(textCue.align, "end");
+ });
+ });
+ </script>
+</video>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-duration.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-duration.html
new file mode 100644
index 0000000000..e2f78900a7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-duration.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Enter, Exit events for a cue with negative duration</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <script>
+ async_test(function(t) {
+ var video = document.querySelector("video");
+ var track = video.addTextTrack("subtitles");
+
+ // Add a cue with negative duration.
+ var cue = new VTTCue(1, -10, "Sausage?");
+ track.addCue(cue);
+ assert_equals(track.cues.length, 1);
+
+ // Verify that enter and exit events are fired.
+ var enterEvent = false;
+ cue.onenter = t.step_func(function() {
+ enterEvent = true;
+ });
+ cue.onexit = t.step_func_done(function() {
+ assert_true(enterEvent);
+ });
+
+ video.src = getVideoURI("/media/test");
+ video.play();
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp-events.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp-events.html
new file mode 100644
index 0000000000..ebd7877f78
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp-events.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Enter, Exit events for cues with negative timestamps</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <script>
+ async_test(function(t) {
+ var video = document.querySelector("video");
+ var track = video.addTextTrack("subtitles");
+
+ // Add cue with negative startTime.
+ var cue = new VTTCue(-10, 1, "Sausage?");
+ track.addCue(cue);
+ assert_equals(track.cues.length, 1);
+ cue.onenter = t.step_func(function() {
+ cue.onexit = t.step_func_done();
+ });
+
+ // Add cue with negative startTime and negative endTime.
+ // This cue should never be active.
+ var missedCue = new VTTCue(-110, -3.4, "Pepperoni?");
+ track.addCue(missedCue);
+ assert_equals(track.cues.length, 2);
+ missedCue.onenter = t.unreached_func();
+ missedCue.onexit = t.unreached_func();
+
+ video.src = getVideoURI("/media/test");
+ video.play();
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp.html
new file mode 100644
index 0000000000..5dc54ed25b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Negative timestamps</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/settings.vtt" default>
+ <script>
+ async_test(function(t) {
+ var testTrack = document.querySelector("track");
+
+ testTrack.onload = t.step_func_done(function() {
+ var cues = testTrack.track.cues;
+ assert_equals(testTrack.track.cues.length, 4);
+ // Add cue with negative startTime.
+ var cue = new VTTCue(-3439332606, 3.4, "Sausage?");
+ testTrack.track.addCue(cue);
+ assert_equals(cues.length, 5);
+
+ // Add cue with negative startTime and negative endTime.
+ cue = new VTTCue(-110, -3.4, "Pepperoni?");
+ testTrack.track.addCue(cue);
+ assert_equals(cues.length, 6);
+
+ // Set startTime and endTime to negative values.
+ var testCue = cues[2];
+ assert_equals(testCue.startTime, 0);
+ testCue.startTime = -5;
+ assert_equals(testCue.startTime, -5);
+ assert_equals(testCue.endTime, 30.5);
+ testCue.endTime = -3439332606;
+ assert_equals(testCue.endTime, -3439332606);
+
+ // Check negative cues ordering.
+ testCue = cues[3];
+ assert_equals(testCue.startTime, 31);
+ testCue.startTime = -200;
+ // Verify that this cue is moved to 2nd position.
+ assert_equals(cues[1].startTime, -200);
+ });
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-order.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-order.html
new file mode 100644
index 0000000000..58e11ebe70
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-order.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<title>Text track cue order</title>
+<link rel="help" href="https://html.spec.whatwg.org/#text-track-cue-order">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+function concat_cuetext(cues) {
+ return Array.prototype.reduce.call(cues, function(acc, value) {
+ return acc + value.text;
+ }, "");
+}
+
+setup(function() {
+ window.video = document.createElement('video');
+});
+
+test(function() {
+ let track = video.addTextTrack('subtitles');
+ track.addCue(new VTTCue(8, 9, '1'));
+ track.addCue(new VTTCue(4, 5, '2'));
+ track.addCue(new VTTCue(2, 3, '3'));
+ assert_equals(concat_cuetext(track.cues), '321');
+}, document.title + ', decreasing start times.');
+
+test(function() {
+ let track = video.addTextTrack('subtitles');
+ track.addCue(new VTTCue(2, 9, '1'));
+ track.addCue(new VTTCue(2, 3, '2'));
+ track.addCue(new VTTCue(2, 5, '3'));
+ assert_equals(concat_cuetext(track.cues), '132');
+}, document.title + ', equal start times varying end times.');
+
+test(function() {
+ let track = video.addTextTrack('subtitles');
+ track.addCue(new VTTCue(2, 3, '1'));
+ track.addCue(new VTTCue(2, 3, '2'));
+ track.addCue(new VTTCue(2, 3, '3'));
+ assert_equals(concat_cuetext(track.cues), '123');
+}, document.title + ', equal start and end times.');
+
+test(function() {
+ let track = video.addTextTrack('subtitles');
+ track.addCue(new VTTCue(2, 5, '1'));
+ track.addCue(new VTTCue(2, 5, '2'));
+ track.addCue(new VTTCue(2, 5, '3'));
+ assert_equals(concat_cuetext(track.cues), '123', 'initial order');
+
+ let cue = track.cues[0];
+ track.removeCue(cue);
+ assert_equals(concat_cuetext(track.cues), '23', '"1" removed');
+
+ track.addCue(cue);
+ assert_equals(concat_cuetext(track.cues), '231', '"1" reinserted');
+}, document.title + ', after re-insertion.');
+
+test(function() {
+ let track = video.addTextTrack('subtitles');
+ track.addCue(new VTTCue(2, 5, '1'));
+ track.addCue(new VTTCue(2, 5, '2'));
+ track.addCue(new VTTCue(2, 5, '3'));
+ assert_equals(concat_cuetext(track.cues), '123', 'initial order');
+
+ track.cues[0].startTime = 4;
+ assert_equals(concat_cuetext(track.cues), '231', '"1" moved last');
+
+ track.cues[2].startTime = 2;
+ assert_equals(concat_cuetext(track.cues), '123', '"1" moved first');
+}, document.title + ', equal start and end times with startTime mutations.');
+
+test(function() {
+ let track = video.addTextTrack('subtitles');
+ track.addCue(new VTTCue(2, 5, '1'));
+ track.addCue(new VTTCue(2, 5, '2'));
+ track.addCue(new VTTCue(2, 5, '3'));
+ assert_equals(concat_cuetext(track.cues), '123', 'initial order');
+
+ track.cues[2].endTime = 9;
+ assert_equals(concat_cuetext(track.cues), '312', '"3" moved first');
+
+ track.cues[1].endTime = 3;
+ assert_equals(concat_cuetext(track.cues), '321', '"1" moved last');
+}, document.title + ', equal start and end times with endTime mutations.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added-ref.html
new file mode 100644
index 0000000000..bd43c462dd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added-ref.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<title>Text track cue layout after controls are added (reference)</title>
+<style>
+::cue {
+ font-size: 50px;
+}
+
+/* Video width should be large enough to display all of the media controls. */
+video {
+ border: 1px solid gray;
+ width: 500px;
+}
+</style>
+<video controls onloadeddata="this.onloadeddata = null; takeScreenshot();">
+ <source src="/media/white.webm" type="video/webm">
+ <source src="/media/white.mp4" type="video/mp4">
+</video>
+<script>
+// Add a single cue at line -2, where it would be if there were controls visible
+// at the bottom. (This assumes that those controls are less than 50px high.)
+// cue at line -1.
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+var cue = new VTTCue(0, 1, "text");
+cue.line = -2;
+track.addCue(cue);
+track.mode = "showing";
+</script>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added.html
new file mode 100644
index 0000000000..23c27e418e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<link rel="match" href="track-cue-rendering-after-controls-added-ref.html">
+<title>Text track cue layout after controls are added</title>
+<style>
+::cue {
+ font-size: 50px;
+}
+</style>
+<!-- Width should be large enough to display all of the media controls. -->
+<video style="border:1px solid gray; width: 500px;">
+ <source src="/media/white.webm" type="video/webm">
+ <source src="/media/white.mp4" type="video/mp4">
+</video>
+<script>
+// Add a cue that will overlap with the video controls.
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+track.addCue(new VTTCue(0, 1, "text"));
+track.mode = "showing";
+
+video.onloadeddata = function() {
+ // Double nesting of requestAnimationFrame to
+ // make sure cue layout and paint happens.
+ window.requestAnimationFrame(function() {
+ window.requestAnimationFrame(function() {
+ video.controls = true;
+ // Wait for the relayout before screenshot.
+ window.requestAnimationFrame(function() {
+ takeScreenshot();
+ });
+ });
+ });
+};
+</script>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed-ref.html
new file mode 100644
index 0000000000..96afaef346
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed-ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<title>Text track cue layout after controls are removed (reference)</title>
+<style>
+::cue {
+ font-size: 50px;
+}
+
+video {
+ border: 1px solid gray;
+}
+</style>
+<video onloadeddata="this.onloadeddata = null; takeScreenshot();">
+ <source src="/media/white.webm" type="video/webm">
+ <source src="/media/white.mp4" type="video/mp4">
+</video>
+<script>
+// Add a single cue at line -2, where it would be if there were controls visible
+// at the bottom. (This assumes that those controls are less than 50px high.)
+// cue at line -1.
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+var cue = new VTTCue(0, 1, "text");
+cue.line = -2;
+track.addCue(cue);
+track.mode = "showing";
+</script>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed.html
new file mode 100644
index 0000000000..76019c9b41
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<link rel="match" href="track-cue-rendering-after-controls-removed-ref.html">
+<title>Text track cue layout after controls are removed</title>
+<style>
+::cue {
+ font-size: 50px;
+}
+</style>
+<video controls style="border:1px solid gray">
+ <source src="/media/white.webm" type="video/webm">
+ <source src="/media/white.mp4" type="video/mp4">
+</video>
+<script>
+// Add a cue that will overlap with the video controls.
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+track.addCue(new VTTCue(0, 1, "text"));
+track.mode = "showing";
+
+video.onloadeddata = function() {
+ // Double nesting of requestAnimationFrame to
+ // make sure cue layout and paint happens.
+ window.requestAnimationFrame(function() {
+ window.requestAnimationFrame(function() {
+ // Remove the controls. The cue should not move.
+ video.controls = false;
+ takeScreenshot();
+ });
+ });
+};
+</script>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html
new file mode 100644
index 0000000000..427189f6fc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Empty cues</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+ var video = document.createElement("video");
+ video.src = getVideoURI("/media/test");
+ video.addTextTrack("captions", "regular captions track", "en");
+ video.textTracks[0].addCue(new VTTCue(0, 4, ""));
+
+ video.onplaying = t.step_func_done();
+ video.play();
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit-ref.html
new file mode 100644
index 0000000000..8354041eb2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<style>
+.container {
+ position: relative;
+ display: inline-block;
+ width: 320px;
+ height: 240px;
+}
+.cue {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ overflow: hidden;
+}
+.cue > span {
+ font-family: sans-serif;
+ background: green;
+ color: green;
+ font-size: 120px;
+ padding: 2px;
+}
+</style>
+<div class="container">
+ <video autoplay onplaying="this.onplaying = null; this.pause(); takeScreenshot();">
+ <source src="/media/white.webm" type="video/webm">
+ <source src="/media/white.mp4" type="video/mp4">
+ </video>
+ <div class="cue"><span>PAS</span></div>
+</div>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit.html
new file mode 100644
index 0000000000..d3dcee1037
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<link rel="match" href="track-cue-rendering-line-doesnt-fit-ref.html">
+<script>
+function addCue(track, cueData) {
+ var cue = new VTTCue(0, 10, 'XXX');
+ for (var prop in cueData)
+ cue[prop] = cueData[prop];
+ track.addCue(cue);
+}
+</script>
+<style>
+video::cue {
+ font-size: 120px;
+ color: green;
+ background-color: green;
+}
+</style>
+<video autoplay onplaying="this.onplaying = null; this.pause(); takeScreenshot();">
+ <source src="/media/white.webm" type="video/webm">
+ <source src="/media/white.mp4" type="video/mp4">
+ <script>
+ var video = document.querySelector("video");
+ var track = video.addTextTrack('subtitles');
+ addCue(track, { line: 0, align: 'start', text: 'PAS' });
+ // This cue will not fit, and will not be displayed.
+ addCue(track, { line: 1, align: 'start', text: 'FAI' });
+ track.mode = 'showing';
+ </script>
+</video>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video-ref.html
new file mode 100644
index 0000000000..39461350b0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video-ref.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<style>
+.container {
+ transform: translate(1px, 0px);
+ position: relative;
+ display: inline-block;
+ width: 320px;
+ height: 240px;
+}
+.cue {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ overflow: hidden;
+ text-align: start;
+}
+.cue > span {
+ font-family: sans-serif;
+ background: green;
+ color: green;
+ font-size: 50px;
+ padding: 2px;
+}
+</style>
+<div class="container">
+ <video autoplay onplaying="this.onplaying = null; this.pause(); takeScreenshot();">
+ <source src="/media/white.webm" type="video/webm">
+ <source src="/media/white.mp4" type="video/mp4">
+ </video>
+ <div class="cue"><span>XXX</span></div>
+</div>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video.html
new file mode 100644
index 0000000000..69ca92e845
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<link rel="match" href="track-cue-rendering-transformed-video-ref.html">
+<style>
+video {
+ transform: translate(1px, 0px);
+}
+video::cue {
+ font-size: 50px;
+ color: green;
+ background-color: green;
+}
+</style>
+<video autoplay onplaying="this.onplaying = null; this.pause(); takeScreenshot();">
+ <source src="/media/white.webm" type="video/webm">
+ <source src="/media/white.mp4" type="video/mp4">
+ <script>
+ var video = document.querySelector('video');
+ var track = video.addTextTrack('subtitles');
+ var cue = new VTTCue(0, 10, 'XXX');
+ cue.align = 'start';
+ cue.line = 0;
+ track.addCue(cue);
+ track.mode = 'showing';
+ </script>
+</video>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-cuechange-dynamically-created-track-element.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-cuechange-dynamically-created-track-element.html
new file mode 100644
index 0000000000..f990bc8c72
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-cuechange-dynamically-created-track-element.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>'cuechange' event on dynamically created track element</title>
+<meta name="timeout" content="long">
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+<script>
+/**
+ * 'cuechange' event should be correctly dispatched on the dynamically created
+ * track element.
+ */
+promise_test(function(t) {
+ const video = document.querySelector("video");
+ const track = document.createElement("track");
+ track.src = "resources/cues-chrono-order.vtt";
+ track.track.mode = "hidden";
+ video.appendChild(track);
+
+ const cueChangedPromise = new Promise(r => track.oncuechange = r);
+ video.src = getVideoURI("/media/test");
+ // 'TimeMarchesOn' algorithm will be run after calling 'play()', from which
+ // the 'cuechange' event would be dispatched.
+ video.play();
+ return cueChangedPromise;
+});
+</script>
+</video>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-cuechange.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-cuechange.html
new file mode 100644
index 0000000000..2593401771
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-cuechange.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>TextTrack's cues are indexed and updated in order during video playback</title>
+<meta name="timeout" content="long">
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/cues-chrono-order.vtt" kind="captions" default>
+ <script>
+ // Use the cuechange event on TextTrack.
+ async_test(function(t) {
+ var video = document.querySelector("video");
+ var testTrack = document.querySelector("track");
+
+ video.src = getVideoURI("/media/test");
+ video.oncanplaythrough = t.step_func(attemptTests);
+
+ function attemptTests() {
+ assert_equals(testTrack.track.cues.length, 3);
+ testTrack.oncuechange = t.step_func(cueChangedFromTrackElement);
+ video.play();
+ }
+
+ var currentCueIndex;
+ var cueChangeCount = 0;
+ function cueChangedFromTrackElement() {
+ currentCueIndex = Math.floor(cueChangeCount / 2);
+ currentCue = event.target.track.cues[currentCueIndex];
+ if (cueChangeCount % 2 == 0) {
+ // Cue entered.
+ assert_equals(currentCue, testTrack.track.activeCues[0]);
+ assert_equals(currentCue.id, (currentCueIndex + 1).toString());
+ }
+
+ ++cueChangeCount;
+ if (cueChangeCount == testTrack.track.cues.length * 2)
+ t.done();
+ }
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-enter-exit.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-enter-exit.html
new file mode 100644
index 0000000000..2d49c21178
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-enter-exit.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<title>TextTrack's cues are indexed and updated in order during video playback</title>
+<meta name="timeout" content="long">
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/cues-chrono-order.vtt" kind="captions" default>
+ <script>
+ // Use the enter and exit events on TextTrackCue.
+ async_test(function(t) {
+ var video = document.querySelector("video");
+ var testTrack = document.querySelector("track");
+
+ video.src = getVideoURI("/media/test");
+
+ video.oncanplaythrough = t.step_func(attemptTests);
+
+ function attemptTests() {
+ assert_equals(testTrack.track.cues.length, 3);
+ for (var i = 0; i < testTrack.track.cues.length; i++) {
+ testTrack.track.cues[i].onenter = t.step_func(cueEntered);
+ testTrack.track.cues[i].onexit = t.step_func(cueExited);
+ }
+ video.play();
+ }
+
+ var cueCount = 0;
+ function cueEntered(event) {
+ var currentCue = event.target;
+
+ // This cue is the currently active cue.
+ assert_equals(currentCue, testTrack.track.activeCues[0]);
+ assert_equals(currentCue.id, (cueCount + 1).toString());
+ }
+
+ function cueExited() {
+ ++cueCount;
+ if (cueCount == testTrack.track.cues.length)
+ t.done();
+ }
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-enter-seeking.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-enter-seeking.html
new file mode 100644
index 0000000000..ae56e16205
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-enter-seeking.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>TextTrack's cue onenter handler called when seeked onto</title>
+<meta name="timeout" content="long">
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/cues-chrono-order.vtt" kind="captions" default>
+ <script>
+ // Check that the onenter event is called for the right cue when seeking on the video element.
+ // Based on the spec step 4 [1], after a seek happens, the missed cues should be empty,
+ // so any cues before the target time should not receive enter event.
+ // [1] https://html.spec.whatwg.org/multipage/media.html#time-marches-on
+ async_test(function(t) {
+ var video = document.querySelector("video");
+ var testTrack = document.querySelector("track");
+
+ video.src = getVideoURI("/media/test");
+
+ video.oncanplaythrough = t.step_func(attemptTests);
+
+ function attemptTests() {
+ assert_equals(testTrack.track.cues.length, 3);
+ const targetTime = 4.0000000004;
+
+ for (let cue of testTrack.track.cues) {
+ if (cue.endTime > targetTime) {
+ cue.onenter = t.step_func(_=>t.done());
+ } else {
+ cue.onenter = t.unreached_func("onenter called for wrong cue");
+ }
+ }
+
+ video.currentTime = targetTime;
+ }
+ });
+ </script>
+</video>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-missed.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-missed.html
new file mode 100644
index 0000000000..2acae212d2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-missed.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<title>Events are triggered for missed (skipped) cues during normal playback</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/missed-cues.vtt" default>
+ <script>
+ async_test(function(t) {
+ var video = document.querySelector("video");
+ var testTrack = document.querySelector("track");
+
+ video.src = getVideoURI("/media/test");
+
+ video.onended = t.step_func_done();
+
+ video.oncanplaythrough = t.step_func(function() {
+ video.oncanplaythrough = null;
+ video.currentTime = 5.00;
+ runTests();
+ });
+
+ testTrack.onload = t.step_func(runTests);
+
+ var cueCount;
+ var eventCount = 0;
+ function runTests() {
+ eventCount++;
+
+ if(eventCount != 2)
+ return;
+
+ assert_equals(testTrack.track.cues.length, 7);
+
+ for (cueCount = 2; cueCount < testTrack.track.cues.length; cueCount++) {
+ var cue = testTrack.track.cues[cueCount];
+
+ cue.onenter = t.step_func(cueEnteredOrExited);
+ cue.onexit = t.step_func(cueEnteredOrExited);
+ }
+
+ // Test events for missed cues, which are cues with ids
+ // from 3 to 7 in the file resources/missed-cues.vtt.
+ cueCount = 3;
+ video.play();
+ }
+
+ function cueEnteredOrExited(event) {
+ var currentCue = event.target;
+ assert_equals(testTrack.track.cues.getCueById(cueCount).text, currentCue.text);
+ assert_equals(currentCue.id, cueCount.toString());
+
+ if (event.type == "exit")
+ cueCount++;
+ }
+
+ });
+ </script>
+</video>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-pause-on-exit.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-pause-on-exit.html
new file mode 100644
index 0000000000..eaf7e2a1d4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-pause-on-exit.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Video is paused after cues having pause-on-exit flag are processed</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/simple-captions.vtt" default>
+ <script>
+ async_test(function(t) {
+ var video = document.querySelector("video");
+ var track = document.querySelector("track");
+ track.onload = t.step_func(function() {
+ assert_equals(track.track.cues.length, 4);
+ for (var i = 0; i < track.track.cues.length; ++i) {
+ var cue = track.track.cues[i];
+ if (i % 2 == 0) {
+ cue.pauseOnExit = true;
+ cue.onexit = t.step_func(function(event) {
+ assert_true(video.paused);
+
+ video.play();
+
+ if (event.target.id == 2)
+ t.done();
+ });
+ }
+ }
+ video.src = getVideoURI("/media/test");
+ video.currentTime = 4.00;
+ video.play();
+ assert_false(video.paused);
+ });
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-seeking.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-seeking.html
new file mode 100644
index 0000000000..99cd2d550e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-seeking.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>TextTrack's activeCues are indexed and updated during video playback</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/cues-overlapping.vtt" kind="subtitles" default>
+ <script>
+ async_test(function(t) {
+ var video = document.querySelector("video");
+ var track = document.querySelector("track");
+ track.onload = t.step_func(function() {
+ assert_equals(track.track.cues.length, 3);
+ video.src = getVideoURI("/media/test");
+ video.currentTime = 0.5;
+ });
+
+ var seekedCount = 0;
+ video.onseeked = t.step_func(function() {
+ ++seekedCount;
+
+ assert_equals(video.currentTime, seekedCount * 0.5);
+ assert_equals(track.track.activeCues.length, seekedCount - 1);
+ video.currentTime = (seekedCount + 1) * 0.5;
+
+ if (seekedCount == 4)
+ t.done();
+ });
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-sorted-before-dispatch.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-sorted-before-dispatch.html
new file mode 100644
index 0000000000..edc202f435
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-cues-sorted-before-dispatch.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<title>All events are triggered in chronological order</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/sorted-dispatch.vtt" default>
+ <script>
+ async_test(function(t) {
+ var video = document.querySelector("video");
+ video.src = getVideoURI("/media/test");
+ var track = document.querySelector("track");
+
+ track.onload = t.step_func(function() {
+ var cues = track.track.cues;
+ assert_equals(cues.length, 8);
+
+ for (var i = 0; i < cues.length; ++i) {
+ cues[i].onenter = t.step_func(cueEnteredOrExited);
+ cues[i].onexit = t.step_func(cueEnteredOrExited);
+ }
+
+ video.play();
+ });
+
+ var cueTimings = [];
+ function cueEnteredOrExited(event) {
+ var currentCue = event.target;
+
+ if (event.type == "exit")
+ cueTimings.push(currentCue.endTime);
+ else
+ cueTimings.push(currentCue.startTime);
+ }
+
+ video.onended = t.step_func_done(function() {
+ assert_equals(cueTimings.length, 14);
+ var time = 0;
+ for (var i = 0; i < cueTimings.length; ++i) {
+ assert_less_than_equal(time, cueTimings[i], "cueTimings[" + i + "]");
+ time = cueTimings[i];
+ }
+ });
+
+ video.currentTime = 5;
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-data-url.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-data-url.html
new file mode 100644
index 0000000000..26ff90d56d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-data-url.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<title>track element data: URL</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+[null, "anonymous", "use-credentials"].forEach(function(crossOriginValue) {
+ async_test(function() {
+ var video = document.createElement('video');
+ if (crossOriginValue !== null) {
+ video.setAttribute('crossorigin', crossOriginValue);
+ }
+ document.body.appendChild(video);
+ var t = document.createElement('track');
+ t.onload = this.step_func_done(function() {
+ assert_equals(t.track.cues.length, 1);
+ assert_equals(t.track.cues[0].startTime, 1);
+ assert_equals(t.track.cues[0].endTime, 2);
+ assert_equals(t.track.cues[0].id, 'x');
+ assert_equals(t.track.cues[0].text, 'test');
+ });
+ t.onerror = this.step_func(function() {
+ assert_unreached('got error event');
+ });
+ t.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\nx\n00:00:01.000 --> 00:00:02.000\ntest\n\n');
+ t.track.mode = 'showing';
+ video.appendChild(t);
+ }, document.title + ' ' + (crossOriginValue ? crossOriginValue : 'No CORS'));
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-default-attribute.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-default-attribute.html
new file mode 100644
index 0000000000..3e8c547fc3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-default-attribute.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>A track with the "default" attribute loads automatically</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track kind="captions" src="resources/default-styles.vtt">
+ <track kind="captions" src="resources/metadata-area.vtt">
+ <track kind="captions" src="resources/webvtt-file.vtt" id="default" default>
+ <script>
+ async_test(function(t) {
+ var timer = null;
+ var tracks = document.querySelectorAll("track");
+ for (var track of tracks) {
+ track.onload = t.step_func(function() {
+ assert_equals(event.target.readyState, HTMLTrackElement.LOADED);
+ assert_equals(event.target.id, "default");
+ assert_true(event.target.default);
+ // End the test after a brief pause so we allow other tracks to load if they will.
+ if (timer)
+ clearTimeout(timer);
+ timer = t.step_timeout(t.step_func_done(), 200);
+ });
+ }
+ });
+ </script>
+</video>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-delete-during-setup.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-delete-during-setup.html
new file mode 100644
index 0000000000..ce9f73335a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-delete-during-setup.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Track deletion during setup</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/metadata.vtt">
+</video>
+<script>
+async_test(function(t) {
+ var video = document.querySelector("video");
+ var track = document.querySelector("track");
+ t.step_timeout(function() {
+ video.parentNode.removeChild(video);
+ }, 61);
+
+ track.onload = t.step_func(function() {
+ var track2 = document.createElement("track");
+ video.appendChild(track2);
+ t.step_timeout(t.step_func_done(), 100);
+ });
+
+ assert_equals(track.readyState, HTMLTrackElement.NONE);
+ assert_equals(track.track.mode, "disabled");
+ track.track.mode = "hidden";
+
+ video.src = getVideoURI("/media/test");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-disabled-addcue.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-disabled-addcue.html
new file mode 100644
index 0000000000..038e6f6ba7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-disabled-addcue.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Adding cues to a disabled text track</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+ var cueDuration = 0.1;
+ var video = document.createElement("video");
+ var track = video.addTextTrack("subtitles");
+ track.mode = "disabled";
+
+ for (var i = 0; i < 10; ++i) {
+ var start = i * cueDuration;
+ var end = start + cueDuration;
+ track.addCue(new VTTCue(start, end, "Test Cue " + i));
+ }
+
+ // Waiting for 2 cue durations to elapse.
+ video.ontimeupdate = t.step_func(function(event) {
+ if (event.target.currentTime < (2 * cueDuration))
+ return;
+
+ // End test after at least 2 cueDurations to make sure the test
+ // would have gone through the period where the first 2 cues would
+ // have been rendered if the track was not disabled.
+ t.done();
+ });
+
+ video.src = getVideoURI("/media/test");
+ video.play();
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-disabled.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-disabled.html
new file mode 100644
index 0000000000..d517b9d12c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-disabled.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Disabling a track</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track kind="subtitles" src="resources/captions.vtt"/>
+</video>
+<script>
+async_test(function(t) {
+ var video = document.querySelector("video");
+ video.textTracks[0].mode = "disabled";
+
+ // Waiting for the duration of the first cue to elapse.
+ video.ontimeupdate = t.step_func(function (event) {
+ if (event.target.currentTime < 1)
+ return;
+
+ // End test after the duration of the first cue to make sure
+ // the test would have gone through the period where this cue
+ // would have been rendered if the track was not disabled.
+ t.done();
+ });
+
+ video.src = getVideoURI("/media/test");
+ video.play();
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-dom-change.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-dom-change.html
new file mode 100644
index 0000000000..ff447f33f2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-dom-change.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Simple DOM mutations with track element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+ var video = document.createElement("video");
+ var testTrack = document.createElement("track");
+
+ // Append the track element to the video element.
+ video.appendChild(testTrack);
+
+ // Set the mode of the text track to "showing".
+ testTrack.track.mode = "showing";
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-aborted-load.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-aborted-load.html
new file mode 100644
index 0000000000..234e087313
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-aborted-load.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>HTMLTrackElement 'src' attribute changed, load pending</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/media.html#start-the-track-processing-model">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video></video>
+<script>
+async_test(t => {
+ const track = document.createElement('track');
+ track.onload = t.unreached_func('first source should not load');
+ track.onerror = t.step_func_done();
+ track.src = 'resources/settings.vtt?pipe=trickle(d3600)';
+ track.track.mode = 'hidden';
+ document.querySelector('video').appendChild(track);
+ t.step_timeout(() => {
+ track.src = 'resources/entities.vtt';
+ }, 0);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change-error.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change-error.html
new file mode 100644
index 0000000000..dd97d0522d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change-error.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<title>HTMLTrackElement 'src' attribute mutations</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/settings.vtt" default>
+ <script>
+ async_test(function(t) {
+ var cues = null;
+ var testTrack = document.querySelector("track");
+ var stage = 0;
+ var timer = null;
+ function step_onLoad() {
+ switch (stage) {
+ case 0:
+ cues = testTrack.track.cues;
+ assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after first loading of the track");
+ assert_equals(cues.length, 4, "Number of cues after first loading of the track");
+ ++stage;
+ testTrack.src = "resources/non-existing-file.vtt"; // this should fail
+ break;
+ case 1:
+ case 3:
+ case 4:
+ assert_unreached("'error' event did not fire, stage = " + stage);
+ break;
+ case 2:
+ assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after loading of the second track");
+ assert_equals(cues.length, 4, "Number of cues after loading of the second track");
+ assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
+ ++stage;
+ testTrack.src = ""; // this should fail
+ assert_equals(cues.length, 0, "cues list is reset immediately after 'src' mutation with the new URL");
+ // This should raise onError event. If no, we'll know about this after some time.
+ timer = t.step_timeout(t.unreached_func("'error' event is not fired when an empty URL is set"), 100);
+ break;
+ default:
+ assert_unreached("unexpected stage number = " + stage);
+ break;
+ }
+ }
+
+ function step_onError() {
+ switch (stage) {
+ case 0:
+ case 2:
+ assert_unreached("'error' event fired, stage = " + stage);
+ break;
+ case 1:
+ assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+ assert_equals(cues.length, 0, "Number of cues after trying to load non-existing url");
+ assert_equals(testTrack.readyState, HTMLTrackElement.ERROR, "readyState after trying to load non-existing url");
+ ++stage;
+ testTrack.src = "resources/settings.vtt";
+ break;
+ case 3:
+ clearTimeout(timer);
+ assert_equals(testTrack.readyState, HTMLTrackElement.ERROR, "readyState after setting an empty URL");
+ assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+ assert_equals(cues.length, 0, "Number of cues with an empty URL set");
+ ++stage;
+ testTrack.src = "resources/settings.vtt";
+ // error should happen when we remove `src` during loading, so we have to wait a task because loading starts asynchronously.
+ t.step_timeout(() => {
+ testTrack.removeAttribute('src');
+ // This should raise onError event, so we'll wait for it for some time
+ timer = t.step_timeout(t.unreached_func("'error' event is not fired when an empty URL is set"), 100);
+ }, 0);
+ break;
+ case 4:
+ clearTimeout(timer);
+ assert_equals(testTrack.readyState, HTMLTrackElement.ERROR, "readyState after removing 'src' attr");
+ assert_equals(cues.length, 0, "Number of cues after removing 'src' attr");
+ t.done();
+ break;
+ default:
+ assert_unreached("unexpected stage number = " + stage);
+ break;
+ }
+ }
+
+ testTrack.onload = t.step_func(step_onLoad);
+ testTrack.onerror = t.step_func(step_onError);
+ });
+ </script>
+</video>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change.html
new file mode 100644
index 0000000000..f3c78668b4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<title>HTMLTrackElement 'src' attribute mutations</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/settings.vtt" default>
+ <script>
+ async_test(function(t) {
+ var cues = null;
+ var testTrack = document.querySelector("track");
+ var stage = 0;
+ function step_onLoad() {
+ switch (stage) {
+ case 0:
+ cues = testTrack.track.cues;
+ assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after first loading of the track");
+ assert_equals(cues.length, 4, "Number of cues after first loading of the track");
+ assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
+ ++stage;
+ testTrack.src = "resources/entities.vtt";
+ assert_equals(cues.length, 0, "cues list is reset immediately after 'src' mutation with the new URL");
+ break;
+ case 1:
+ assert_equals(testTrack.readyState, HTMLTrackElement.LOADED), "readyState after loading of the second track";
+ assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+ assert_equals(cues.length, 7, "Number of cues after loading of the second track");
+ assert_equals(cues[cues.length-1].text, 'This & is parsed to the same as &amp;.', "Last cue content check");
+ ++stage;
+ testTrack.src = "resources/settings.vtt";
+ break;
+ case 2:
+ assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after after loading of the first track again");
+ assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
+ assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+ assert_equals(cues.length, 4, "Number of cues after loading of the first track");
+ ++stage;
+ testTrack.src = "resources/settings.vtt";
+ // This should not raise onLoad or onError event, so we'll wait for it for some time
+ t.step_timeout(t.step_func_done(function() {
+ assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after changing 'src' to the same value");
+ assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+ assert_equals(cues.length, 4, "Number of cues after changing 'src' to the same value");
+ }, 100));
+ break;
+ case 3:
+ assert_unreached("'load' event should not fire, stage = " + stage);
+ break;
+ }
+ }
+
+ testTrack.onload = t.step_func(step_onLoad);
+ testTrack.onerror = t.unreached_func("'error' event should not fire");
+ });
+ </script>
+</video>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-helpers.js b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-helpers.js
new file mode 100644
index 0000000000..09c85dd7bc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-helpers.js
@@ -0,0 +1,83 @@
+function enableAllTextTracks(textTracks) {
+ for (var i = 0; i < textTracks.length; i++) {
+ var track = textTracks[i];
+ if (track.mode == "disabled")
+ track.mode = "hidden";
+ }
+}
+
+function assert_cues_equal(cues, expected) {
+ assert_equals(cues.length, expected.length);
+ for (var i = 0; i < cues.length; i++) {
+ assert_equals(cues[i].id, expected[i].id);
+ assert_equals(cues[i].startTime, expected[i].startTime);
+ assert_equals(cues[i].endTime, expected[i].endTime);
+ assert_equals(cues[i].text, expected[i].text);
+ }
+}
+
+function assert_cues_match(cues, expected) {
+ assert_equals(cues.length, expected.length);
+ for (var i = 0; i < cues.length; i++) {
+ var cue = cues[i];
+ var expectedItem = expected[i];
+ for (var property of Object.getOwnPropertyNames(expectedItem))
+ assert_equals(cue[property], expectedItem[property]);
+ }
+}
+
+function assert_cues_html_content(cues, expected) {
+ assert_equals(cues.length, expected.length);
+ for (var i = 0; i < cues.length; i++) {
+ var expectedItem = expected[i];
+ var property = Object.getOwnPropertyNames(expectedItem)[0];
+ var propertyValue = expectedItem[property];
+ assert_equals(propertyValue(cues[i]), expectedItem.expected);
+ }
+}
+
+function check_cues_from_track(src, func) {
+ async_test(function(t) {
+ var video = document.createElement("video");
+ var trackElement = document.createElement("track");
+ trackElement.src = src;
+ trackElement.default = true;
+ video.appendChild(trackElement);
+
+ trackElement.onload = t.step_func_done(function() {
+ func(trackElement.track);
+ });
+ }, "Check cues from " + src);
+}
+
+function assert_cue_fragment(cue, children) {
+ var fragment = createFragment(children);
+ assert_true(fragment.isEqualNode(cue.getCueAsHTML()));
+}
+
+function assert_cue_fragment_as_textcontent(cue, children) {
+ var fragment = createFragment(children);
+ assert_equals(cue.getCueAsHTML().textContent, fragment.textContent);
+}
+
+function createFragment(children) {
+ var fragment = document.createDocumentFragment();
+ cloneChildrenToFragment(fragment, children);
+ return fragment;
+}
+
+function cloneChildrenToFragment(root, children) {
+ for (var child of children) {
+ var childElement;
+ if (child.type == "text") {
+ childElement = document.createTextNode(child.value);
+ } else {
+ childElement = document.createElement(child.type);
+ var styles = child.style || {};
+ for (var attr of Object.getOwnPropertyNames(styles))
+ childElement[attr] = styles[attr];
+ cloneChildrenToFragment(childElement, child.value);
+ }
+ root.appendChild(childElement);
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-id.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-id.html
new file mode 100644
index 0000000000..f0223fda64
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-id.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>TextTrack "id" attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track id="LoremIpsum" src="resources/captions-fast.vtt" default>
+ <script>
+ test(function() {
+ var video = document.querySelector("video");
+ var track = document.querySelector("track");
+ var textTrack = track.track;
+
+ // Test default attribute value.
+ assert_equals(textTrack.id, "LoremIpsum");
+ assert_equals(video.textTracks[0].id, "LoremIpsum");
+
+ // Make sure we can look up tracks by id.
+ assert_equals(video.textTracks.getTrackById("LoremIpsum"), textTrack);
+
+ // Test that it's readonly.
+ textTrack.id = "newvalue";
+ assert_equals(textTrack.id, "LoremIpsum");
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-insert-after-load.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-insert-after-load.html
new file mode 100644
index 0000000000..28b4f82688
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-insert-after-load.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Inserting a track element immediately after video load</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+ var video = document.createElement('video');
+ video.src = getVideoURI('/media/test');
+ video.load();
+ video.appendChild(document.createElement('track'));
+ video.onloadedmetadata = t.step_func_done();
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-large-timestamp.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-large-timestamp.html
new file mode 100644
index 0000000000..bae1852cf8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-large-timestamp.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Very large timestamp is parsed correctly</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/large-timestamp.vtt" default>
+ <script>
+ async_test(function(t) {
+ var testTrack = document.querySelector("track");
+ testTrack.onload = t.step_func_done(function() {
+ assert_equals(testTrack.track.cues.length, 1);
+ var cue = testTrack.track.cues[0];
+ assert_equals(parseInt(cue.id), 1);
+ assert_equals(cue.startTime / 3600, 1234567);
+ assert_equals(cue.endTime / 3600, 1234567890);
+ });
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-load-error-readyState.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-load-error-readyState.html
new file mode 100644
index 0000000000..8e232bff53
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-load-error-readyState.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Error event on HTMLTrackElement and ERROR readyState on TextTrack</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="junk" default>
+ <script>
+ async_test(function(t) {
+ var track = document.querySelector("track");
+ track.onerror = t.step_func_done(function() {
+ assert_equals(track.readyState, HTMLTrackElement.ERROR);
+ });
+ });
+ </script>
+</video>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-element-readyState.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-element-readyState.html
new file mode 100644
index 0000000000..62a68f6543
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-element-readyState.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Load event on HTMLTrackElement and LOADED readyState on TextTrack when src is set on the element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/webvtt-file.vtt" default>
+ <script>
+ async_test(function(t) {
+ var track = document.querySelector("track");
+ track.onload = t.step_func_done(function() {
+ assert_equals(track.readyState, HTMLTrackElement.LOADED);
+ });
+ });
+ </script>
+</video>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-src-readyState.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-src-readyState.html
new file mode 100644
index 0000000000..e569eeb96f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-src-readyState.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Load event on HTMLTrackElement and LOADED readyState on TextTrack when src is set from JavaScript</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track>
+</video>
+<script>
+async_test(function(t) {
+ var track = document.querySelector("track");
+ assert_equals(track.readyState, HTMLTrackElement.NONE);
+
+ track.onload = t.step_func_done(function() {
+ assert_equals(track.readyState, HTMLTrackElement.LOADED);
+ });
+
+ track.src = "resources/webvtt-file.vtt";
+ track.track.mode = "hidden";
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode-disabled.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode-disabled.html
new file mode 100644
index 0000000000..6b46bf4e34
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode-disabled.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Cues are properly removed from the active cue list when their track changes mode to disabled</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/captions-gaps.vtt" kind="captions" default >
+ <script>
+ async_test(function(t) {
+ var video = document.querySelector("video");
+ var testTrack = document.querySelector("track");
+
+ video.src = getVideoURI("/media/counting");
+ video.oncanplaythrough = t.step_func(startTest);
+ video.onseeked = t.step_func_done(seeked);
+
+ function startTest() {
+ // Set the mode of the text track to "showing".
+ testTrack.track.mode = "showing";
+ // Seek to a time with a caption.
+ video.currentTime = 1.5;
+ }
+
+ function seeked() {
+ // Set the mode of the text track to "hidden", then to "showing" again.
+ testTrack.track.mode = "hidden";
+ testTrack.track.mode = "showing";
+
+ // Set the mode of the text track to "disabled".
+ testTrack.track.mode = "disabled";
+ }
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode-not-changed-by-new-track.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode-not-changed-by-new-track.html
new file mode 100644
index 0000000000..3ec47a39e2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode-not-changed-by-new-track.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<title>A track appended after the initial track configuration does not change other tracks</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track kind="metadata" src="resources/metadata.vtt">
+</video>
+<script>
+async_test(function(t) {
+ var video = document.querySelector('video');
+
+ var track1 = document.querySelectorAll('track')[0];
+ assert_equals(track1.readyState, HTMLTrackElement.NONE);
+ assert_equals(track1.track.mode, 'disabled');
+
+ video.src = getVideoURI('/media/test');
+ video.oncanplaythrough = t.step_func(canplaythrough);
+ track1.onload = t.step_func(metadataTrackLoaded);
+
+ function canplaythrough() {
+ // check initial metadata track state.
+ assert_equals(track1.readyState, HTMLTrackElement.NONE);
+ assert_equals(track1.track.mode, 'disabled');
+ assert_equals(track1.track.cues, null);
+ track1.track.mode = 'hidden';
+ }
+
+ function metadataTrackLoaded() {
+ // check metadata track state.
+ assert_equals(track1.readyState, HTMLTrackElement.LOADED);
+ assert_equals(track1.track.mode, 'hidden');
+ assert_equals(track1.track.cues.length, 12);
+ assert_equals(track1.track.cues[11].startTime, 22);
+
+ // Add a caption track, and explicitly enable it.
+ track2 = document.createElement('track');
+ track2.setAttribute('kind', 'captions');
+ track2.setAttribute('default', 'default');
+ track2.setAttribute('src', 'resources/webvtt-file.vtt');
+ track2.track.mode = 'showing';
+ track2.onload = t.step_func(captionsTrackLoaded);
+ video.appendChild(track2);
+ }
+
+ function captionsTrackLoaded() {
+ // Check that metadata track state has not changed.
+ assert_equals(track1.readyState, HTMLTrackElement.LOADED);
+ assert_equals(track1.track.mode, 'hidden');
+ // and that the caption track state is correct.
+ assert_equals(track2.readyState, HTMLTrackElement.LOADED);
+ assert_equals(track2.track.mode, 'showing');
+
+ video.textTracks.onaddtrack = t.step_func_done(trackAdded);
+ // add a subtitle track with video.addTextTrack().
+ track3 = video.addTextTrack('subtitles', 'Subtitle Track', 'en');
+ track3.mode = 'showing';
+ }
+
+ function trackAdded(event) {
+ // Check that metadata track state has not changed.
+ assert_equals(track1.readyState, HTMLTrackElement.LOADED);
+ assert_equals(track1.track.mode, 'hidden');
+ // and that the caption track state has not changed.
+ assert_equals(track2.readyState, HTMLTrackElement.LOADED);
+ assert_equals(track2.track.mode, 'showing');
+ // and that the subtitle track state is correct.
+ assert_equals(event.target, video.textTracks);
+ assert_true(event instanceof window.TrackEvent);
+ assert_equals(event.track, video.textTracks[video.textTracks.length - 1]);
+ assert_equals(track3.mode, 'showing');
+ }
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode-triggers-loading.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode-triggers-loading.html
new file mode 100644
index 0000000000..2e29d70469
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode-triggers-loading.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>A "metadata" track does not load automatically, but it does load when the mode is changed</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track kind="metadata" src="resources/metadata.vtt">
+</video>
+<script>
+async_test(function(t) {
+ var video = document.querySelector("video");
+
+ // Check initial metadata track state.
+ var track = document.querySelectorAll("track")[0];
+ assert_equals(track.readyState, HTMLTrackElement.NONE);
+ assert_equals(video.textTracks[0].mode, "disabled");
+
+ video.src = getVideoURI("/media/test");
+ video.oncanplaythrough = t.step_func(canplaythrough);
+ track.onload = t.step_func_done(trackLoaded);
+
+ function trackLoaded() {
+ assert_equals(track.readyState, HTMLTrackElement.LOADED);
+ assert_equals(track.track.mode, "hidden");
+ assert_equals(video.textTracks[0].cues.length, 12);
+ assert_equals(video.textTracks[0].cues[11].startTime, 22);
+ }
+
+ function canplaythrough() {
+ assert_equals(track.readyState, HTMLTrackElement.NONE);
+ assert_equals(video.textTracks[0].mode, "disabled");
+ assert_equals(video.textTracks[0].cues, null);
+ // Change metadata track mode so it loads.
+ video.textTracks[0].mode = "hidden";
+ }
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode.html
new file mode 100644
index 0000000000..206ac9968f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<title>TextTrack mode attribute</title>
+<meta name="timeout" content="long">
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/captions-fast.vtt" default>
+ <script>
+ async_test(function(t) {
+ var video = document.querySelector("video");
+ var track = document.querySelector("track");
+ if (track.readyState != HTMLTrackElement.LOADED) {
+ assert_not_equals(track.readyState, HTMLTrackElement.ERROR,
+ "track failed to load resource.");
+ track.onload = t.step_func(trackLoaded);
+ } else {
+ trackLoaded();
+ }
+
+ var cueCount = 0;
+ var textTrack;
+ function trackLoaded() {
+ textTrack = track.track;
+ // Test default attribute value.
+ assert_equals(textTrack.mode, "showing");
+ assert_equals(video.textTracks[0].mode, "showing");
+ // Set to bogus value, should return default.
+ var value = "bogus";
+ textTrack.mode = value;
+ assert_equals(textTrack.mode, "showing");
+ assert_equals(video.textTracks[0].mode, "showing");
+
+ // Set to numeric value (no longer supported), should return default.
+ textTrack.mode = 2;
+ assert_equals(textTrack.mode, "showing");
+ assert_equals(video.textTracks[0].mode, "showing");
+
+ // Set to known values.
+ setModeAndCheck("disabled");
+
+ video.src = getVideoURI("/media/test");
+ video.play();
+
+ // Wait for end of first cue (no events should fire while track is disabled).
+ video.ontimeupdate = () => {
+ if (video.currentTime > 0.4) {
+ testHiddenAndShowing();
+ video.ontimeupdate = null;
+ }
+ }
+ }
+
+ track.oncuechange = t.step_func(function(event) {
+ cueCount++;
+ // As the 'enter' and the 'exit' event would be fired for the second
+ // and the third cue, so there would be 4 times 'oncuechange' event.
+ if (cueCount == 4)
+ t.done();
+ });
+
+ function setModeAndCheck(value) {
+ textTrack.mode = value;
+ assert_equals(textTrack.mode, value);
+ assert_equals(video.textTracks[0].mode, value);
+ if (value == "disabled")
+ assert_equals(textTrack.cues, null);
+ }
+
+ function testHiddenAndShowing() {
+ setModeAndCheck("hidden");
+ setModeAndCheck("showing");
+ }
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-node-add-remove.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-node-add-remove.html
new file mode 100644
index 0000000000..2708879424
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-node-add-remove.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Add and remove track node</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+test(function() {
+ var video = document.createElement('video');
+ var tracka = document.createElement('track');
+ video.appendChild(tracka);
+ var trackb = document.createElement('track');
+ video.appendChild(trackb);
+
+ // Adding tracks outside the DOM tree.
+ assert_array_equals(video.textTracks, [tracka.track, trackb.track]);
+
+ // Inserting the parent video element into the document.
+ document.body.appendChild(video);
+ assert_array_equals(video.textTracks, [tracka.track, trackb.track]);
+
+ // Inserting and removing another track in the document.
+ var trackc = document.createElement('track');
+ video.appendChild(trackc);
+ assert_array_equals(video.textTracks, [tracka.track, trackb.track, trackc.track]);
+
+ trackb.parentNode.removeChild(trackb);
+ assert_array_equals(video.textTracks, [tracka.track, trackc.track]);
+
+ // Removing the video from the document.
+ document.body.removeChild(video);
+ assert_array_equals(video.textTracks, [tracka.track, trackc.track]);
+
+ tracka.parentNode.removeChild(tracka);
+ assert_array_equals(video.textTracks, [trackc.track]);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-active-cue.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-active-cue.html
new file mode 100644
index 0000000000..176e0065c5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-active-cue.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Removing an active cue</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video></video>
+<script>
+async_test(function(t) {
+ var video = document.querySelector("video");
+ video.src = getVideoURI("/media/test");
+
+ // Add a text track to the video element.
+ video.addTextTrack("captions", "regular captions track", "en");
+
+ // Add a cue to the track with enter event listener.
+ var cue = new VTTCue(0, 4, "Random");
+ cue.onenter = t.step_func_done(removeActiveCue);
+
+ var track = video.textTracks[0];
+ track.addCue(cue);
+
+ function removeActiveCue() {
+ assert_equals(track.activeCues.length, 1);
+
+ // Remove the cue while it is active.
+ track.removeCue(track.activeCues[0]);
+
+ // No crash. PASS.
+ }
+
+ // Play the video and remove cue when it becomes active.
+ video.play();
+ track.mode = "showing";
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-by-setting-innerHTML.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-by-setting-innerHTML.html
new file mode 100644
index 0000000000..95929bc83f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-by-setting-innerHTML.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Removing a track by setting video.innerHTML doesn't crash</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track default src="resources/captions-gaps.vtt">
+ <script>
+ // https://bugs.webkit.org/show_bug.cgi?id=100981
+ async_test(function(t) {
+ var firstSeek = true;
+ var video = document.querySelector('video');
+ video.onseeked = t.step_func(function() {
+ if (!firstSeek) {
+ t.done();
+ return;
+ }
+
+ // Remove the text track
+ video.innerHTML = '';
+
+ // Seek again to force a repaint.
+ video.currentTime = 7.9;
+ firstSeek = false;
+ });
+
+ video.currentTime = 0.5;
+ video.src = getVideoURI('/media/counting');
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-insert-ready-state.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-insert-ready-state.html
new file mode 100644
index 0000000000..91375f579e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-insert-ready-state.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>Attaching a media element again to the document, having a child track that failed loading doesn't block video from playing</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/no-webvtt.vtt" kind="captions" default>
+</video>
+<script>
+ async_test(function(t) {
+ var video = document.querySelector('video');
+ video.src = getVideoURI('/media/test');
+ video.oncanplaythrough = t.step_func(canplaythrough);
+
+ function canplaythrough() {
+ video.oncanplaythrough = null;
+ var track = document.querySelector('track');
+
+ // Track should have error as ready state.
+ assert_equals(track.readyState, HTMLTrackElement.ERROR);
+
+ // Remove the video element from body.
+ document.body.removeChild(video);
+
+ // Reset the video src attribute to re-trigger resource selection for tracks.
+ video.src = getVideoURI('/media/test');
+
+ // Append the video element back to the body.
+ document.body.appendChild(video);
+
+ assert_equals(track.readyState, HTMLTrackElement.ERROR);
+
+ video.onplaying = t.step_func_done();
+ video.play();
+ // The video should start playing.
+ }
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-quickly.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-quickly.html
new file mode 100644
index 0000000000..4be040c5f8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-quickly.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Removing a track element before it has been processed doesn't crash</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="video_container"></div>
+<script>
+var mediaFile = getVideoURI("/media/test");
+document.getElementById("video_container").innerHTML = "<video src='" + mediaFile + "' controls ><track kind='captions' src='resources/simple-captions.vtt' default ></video>";
+test(function() {
+// https://bugs.webkit.org/show_bug.cgi?id=85095
+// Test passes if it doesn't crash.
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-track-inband.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-track-inband.html
new file mode 100644
index 0000000000..7dcfe68318
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-track-inband.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <script src="/common/media.js"></script>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ async_test(function(test)
+ {
+ var video = document.createElement("video");
+
+ // Create an out-of-band text track by adding a track element.
+ var trackElement = document.createElement('track');
+
+ trackElement.addEventListener("error", test.step_func(function()
+ {
+ assert_unreached("'error' event on track element should not fire.")
+ }));
+
+ video.appendChild(trackElement);
+ trackElement.src = 'resources/webvtt-file.vtt';
+ trackElement.track.mode = 'hidden';
+
+ assert_equals(video.textTracks.length, 1);
+ var outOfBandTrack = video.textTracks[0];
+
+ // Load a media file with an inband text track.
+ var inbandTrack = null;
+ var url = "resources/vp8-vorbis-webvtt.webm"
+
+ var firstAddTrackHandler = test.step_func(function()
+ {
+ assert_equals(event.target, video.textTracks);
+ assert_equals(event instanceof window.TrackEvent, true);
+ if (event.track == outOfBandTrack) {
+ return;
+ }
+
+ assert_equals(inbandTrack, null);
+ assert_equals(video.textTracks.length, 2);
+ assert_equals(event.track, video.textTracks[1]);
+ inbandTrack = event.track;
+
+ video.textTracks.removeEventListener("addtrack", firstAddTrackHandler);
+
+ // Clear .src to force the inband track to get destroyed.
+ video.src = "";
+
+ // Verify that the inband track was removed.
+ assert_not_equals(inbandTrack, null);
+ assert_equals(video.textTracks.length, 1);
+ assert_equals(video.textTracks[0], outOfBandTrack);
+
+ // Load the URL again to trigger another 'addtrack' event to make sure
+ // no 'removetrack' event was queued.
+ video.src = url;
+ video.textTracks.addEventListener("addtrack", test.step_func(function()
+ {
+ assert_equals(video.textTracks.length, 2);
+ test.done();
+ }));
+ });
+ video.textTracks.addEventListener("addtrack", firstAddTrackHandler);
+
+ video.textTracks.addEventListener("removetrack", test.step_func(function()
+ {
+ assert_unreached("'removetrack' event should not fire.")
+ }));
+
+ video.src = url;
+ }, "Tests that the 'removetrack' event is NOT fired for inband TextTrack on a failed load.");
+
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-track.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-track.html
new file mode 100644
index 0000000000..d5695cd302
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-remove-track.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <script src="/common/media.js"></script>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ async_test(function(test)
+ {
+ var video = document.createElement("video");
+ var track;
+
+ function trackRemoved()
+ {
+ assert_equals(event.target, video.textTracks);
+ assert_equals(event instanceof window.TrackEvent, true);
+ assert_equals(event.track, track);
+ test.done();
+ }
+
+ var trackElement = document.createElement('track');
+ video.appendChild(trackElement);
+
+ trackElement.src = 'resources/webvtt-file.vtt';
+ trackElement.track.mode = 'hidden';
+
+ assert_equals(video.textTracks.length, 1);
+
+ track = video.textTracks[0];
+ video.removeChild(trackElement);
+ video.textTracks.addEventListener("removetrack", test.step_func(trackRemoved));
+ }, "Tests that the 'removetrack' event is fired when an out-of-band TextTrack is removed.");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-selection-metadata.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-selection-metadata.html
new file mode 100644
index 0000000000..c4d88a35f0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-selection-metadata.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Multiple 'metadata' tracks with 'default'</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track kind="metadata" src="resources/default-styles.vtt" id="t1">
+ <track kind="metadata" src="resources/class.vtt" default id="t2hidden">
+ <track kind="metadata" src="resources/metadata-area.vtt" id="t3">
+ <track kind="metadata" src="resources/webvtt-file.vtt" default id="t4hidden">
+</video>
+<script>
+async_test(function() {
+ var video = document.querySelector('video');
+ video.onloadstart = this.step_func_done(function() {
+ assert_equals(video.textTracks.length, 4);
+ for (var track of video.textTracks) {
+ assert_equals(track.kind, 'metadata');
+
+ var trackElement = document.getElementById(track.id);
+ if (track.id.indexOf('hidden') != -1) {
+ assert_true(trackElement.default);
+ assert_equals(track.mode, 'hidden');
+ } else {
+ assert_false(trackElement.default);
+ assert_equals(track.mode, 'disabled');
+ }
+ }
+ });
+
+ video.src = getVideoURI("/media/test");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-selection-task-order.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-selection-task-order.html
new file mode 100644
index 0000000000..522d067adf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-selection-task-order.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>HTMLTrackElement Text Track Selection Task Order</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+/**
+ * This test is used to ensure that we queue 'honor user preferences for automatic
+ * text track selection' as a macro task, not a micro task. In this test, we
+ * trigger a media event before queuing a text track selection task, and check
+ * the text track's mode to know whether the text track selection runs after the
+ * task for media event.
+ */
+async_test(function(t) {
+ let video = document.createElement("video");
+ video.play();
+ video.onplay = t.step_func(startedPlay);
+
+ // When we create a text track element, it queue a task to run automatic
+ // text track selection later.
+ let track = document.createElement("track");
+ track.default = true;
+ video.appendChild(track);
+ assert_equals(track.track.mode, "disabled", "Text track's mode is disabled by default.");
+
+ function startedPlay() {
+ assert_equals(track.track.mode, "disabled", "Text track selection hasn't started yet.");
+ track.onerror = t.step_func_done(trackError);
+ }
+
+ function trackError() {
+ assert_equals(track.track.mode, "showing", "Text track selection modified track's mode.");
+ t.done();
+ }
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-text-track-cue-list.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-text-track-cue-list.html
new file mode 100644
index 0000000000..73241ce0d4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-text-track-cue-list.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>TextTrackCueList functionality: length, operator[], and getCueById()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/settings.vtt" kind="captions" default>
+ <script>
+ async_test(function(t) {
+ var testTrack = document.querySelector("track");
+
+ testTrack.onload = t.step_func_done(function() {
+ var cues = testTrack.track.cues;
+
+ // Testing TextTrackCueList length.
+ assert_equals(cues.length, 4);
+
+ // Testing TextTrackCueList [] operator.
+ assert_equals(cues[0].id, "1");
+ assert_equals(cues[3].id, "4");
+ assert_equals(cues[4], undefined);
+
+ // Testing TextTrackCueList getCueById().
+ assert_equals(cues.getCueById("1").startTime, 0);
+ assert_equals(cues.getCueById("4").startTime, 121);
+ assert_equals(cues.getCueById("junk"), null);
+ });
+ });
+ </script>
+</video>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-texttracks.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-texttracks.html
new file mode 100644
index 0000000000..4d006fcefb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-texttracks.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<title>TextTracks in a TextTrackList are kept in the correct order</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track kind="captions" src="resources/webvtt-file.vtt">
+</video>
+<script>
+test(function() {
+ var video = document.querySelector("video");
+
+ // Add a track with video.addTextTrack().
+ video.addTextTrack("descriptions", "Descriptions Track", "en");
+
+ // Add a track element with DOM API.
+ var trackElement = document.createElement("track");
+ trackElement.setAttribute("kind", "chapters");
+ video.appendChild(trackElement);
+
+ // Verify track order.
+ assert_equals(video.textTracks.length, 3);
+ assert_equals(video.textTracks[0].kind, "captions");
+ assert_equals(video.textTracks[1].kind, "chapters");
+ assert_equals(video.textTracks[2].kind, "descriptions");
+
+ // Verify the default parameters of the text track object
+ // returned by addTextTrack().
+ assert_equals(video.textTracks[2].mode, "hidden");
+ assert_not_equals(video.textTracks[2].cues, null);
+ assert_equals(video.textTracks[2].cues.length, 0);
+
+ // Add another track element, it should insert
+ // before the addTextTrack() track.
+ trackElement = document.createElement("track");
+ trackElement.setAttribute("kind", "metadata");
+ video.appendChild(trackElement);
+
+ assert_equals(video.textTracks.length, 4);
+ assert_equals(video.textTracks[0].kind, "captions");
+ assert_equals(video.textTracks[1].kind, "chapters");
+ assert_equals(video.textTracks[2].kind, "metadata");
+ assert_equals(video.textTracks[3].kind, "descriptions");
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-positioning.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-positioning.html
new file mode 100644
index 0000000000..07ebfd622b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-positioning.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Cue text position and alignment from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/align-positioning.vtt">
+ <track src="resources/align-positioning-bad.vtt">
+</video>
+<script>
+async_test(function(t) {
+ var video = document.querySelector("video");
+
+ var trackElements = document.querySelectorAll("track");
+ assert_equals(trackElements.length, video.textTracks.length);
+ for (var i = 0; i < trackElements.length; i++)
+ trackElements[i].onload = t.step_func(trackLoaded);
+
+ enableAllTextTracks(video.textTracks);
+
+ var numberOfTracksLoaded = 0;
+ function trackLoaded() {
+ numberOfTracksLoaded++;
+ if (numberOfTracksLoaded != 2)
+ return;
+
+ testTrack(0);
+ testTrackError(1);
+ t.done();
+ }
+
+ function testTrack(index) {
+ var expected = [
+ { position : 10, align : "start" },
+ { position : 20, align : "center" },
+ { position : 80, align : "end" }
+ ];
+
+ assert_cues_match(video.textTracks[index].cues, expected);
+ }
+
+ function testTrackError(index) {
+ var expected = [
+ { position : 10, align : "center" },
+ { position : "auto", align : "center" },
+ { position : "auto", align : "center" }
+ ];
+
+ assert_cues_match(video.textTracks[index].cues, expected);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-text-line-position.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-text-line-position.html
new file mode 100644
index 0000000000..deb389916a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-text-line-position.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>Cue alignment, line and text position from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/align-text-line-position.vtt">
+ <track src="resources/align-text-line-position-bad.vtt">
+</video>
+<script>
+async_test(function(t) {
+ var video = document.querySelector("video");
+
+ var trackElements = document.querySelectorAll("track");
+ assert_equals(trackElements.length, video.textTracks.length);
+ for (var i = 0; i < trackElements.length; i++)
+ trackElements[i].onload = t.step_func(trackLoaded);
+
+ enableAllTextTracks(video.textTracks);
+
+ var numberOfTracksLoaded = 0;
+ function trackLoaded() {
+ numberOfTracksLoaded++;
+ if (numberOfTracksLoaded != 2)
+ return;
+
+ testTrack(0);
+ testTrackError(1);
+ t.done();
+ }
+
+ function testTrack(index) {
+ var expected = [
+ { align : "start", position : 10, line : 0, snapToLines : false },
+ { align : "start", position : "auto", line : 0, snapToLines : true },
+ { align : "center", position : 80, line : 80, snapToLines : false },
+ { align : "end", position : 30, line : 5, snapToLines : true },
+ { align : "center", position : 60, line : -3, snapToLines : true }
+ ];
+
+ assert_cues_match(video.textTracks[index].cues, expected);
+ }
+
+ function testTrackError(index) {
+ var expected = [
+ { align : "center", position : "auto", line : "auto", snapToLines : true },
+ { align : "end", position : 0, line : "auto", snapToLines : true },
+ { align : "center", position : 60, line : -3, snapToLines : true }
+ ];
+
+ assert_cues_match(video.textTracks[index].cues, expected);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-alignment.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-alignment.html
new file mode 100644
index 0000000000..e8f47e876a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-alignment.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Cue alignment from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/alignment.vtt", testTrack);
+check_cues_from_track("resources/alignment-ltr.vtt", testTrack);
+
+check_cues_from_track("resources/alignment-bad.vtt", function(track) {
+ var expected = [
+ { align: "center" },
+ { align: "center" },
+ { align: "center" },
+ { align: "center" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+
+function testTrack(track) {
+ var expected = [
+ { align: "start" },
+ { align: "center" },
+ { align: "end" },
+ { align: "center" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-blank-lines.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-blank-lines.html
new file mode 100644
index 0000000000..114aebc38c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-blank-lines.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Cues are affected neither by multiple newlines \n, \r, and \r\n nor by the absence of a seperating line</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/cues.vtt", function(track) {
+ var expected = [
+ { id: "1", startTime: 0, endTime: 30.5, text: "Bear is Coming!!!!!" },
+ { id: "2", startTime: 31, endTime: 60.5, text: "I said Bear is coming!!!!" },
+ { id: "3", startTime: 61, endTime: 361200.5, text: "I said Bear is coming now!!!!" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+
+check_cues_from_track("resources/cues-no-separation.vtt", function(track) {
+ var expected = [
+ { id: "1", startTime: 0, endTime: 30.5, text: "Bear is Coming!!!!!\n2" },
+ { id: "", startTime: 31, endTime: 60.5, text: "I said Bear is coming!!!!" },
+ { id: "", startTime: 61, endTime: 361200.5, text: "I said Bear is coming now!!!!" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-bom.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-bom.html
new file mode 100644
index 0000000000..c138f96af5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-bom.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Parser properly ignores a UTF-8 BOM character at the beginning of a file and all other cues are properly parsed</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/bom.vtt" default>
+ <script>
+ async_test(function(t) {
+ var track = document.querySelector("track");
+
+ track.onload = t.step_func_done(function() {
+ var expected = [
+ {
+ id : "1",
+ startTime : 0,
+ endTime : 30.5,
+ text : "Bear is Coming!!!!!"
+ },
+ {
+ id : "2",
+ startTime : 31,
+ endTime : 1200.5,
+ text : "I said Bear is coming!!!!"
+ }
+ ];
+
+ var cues = track.track.cues;
+ assert_equals(cues.length, 2);
+ assert_cues_equal(cues, expected);
+ });
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-class-markup.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-class-markup.html
new file mode 100644
index 0000000000..ecc5a57497
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-class-markup.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<title>Tests cues with class markup &lt;c&gt;.</title>
+<meta name="timeout" content="long">
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/class.vtt", function(track) {
+ assert_equals(track.cues.length, 3);
+
+ var children = [
+ { type: "span", style: { className: "black" },
+ value: [ { type: "text", value: "Bear is Coming!!!!!" } ] }
+ ];
+ assert_cue_fragment(track.cues[0], children);
+
+ children = [
+ { type: "span", style: { className: "green" },
+ value: [ { type: "text", value: "I said Bear is coming!!!!" } ] }
+ ];
+ assert_cue_fragment(track.cues[1], children);
+
+ children = [
+ { type: "text", value: "I said " },
+ { type: "span", style: { className: "red uppercase" },
+ value: [ { type: "text", value: "Bear is coming now" } ] },
+ { type: "text", value: "!!!!" }
+ ];
+ assert_cue_fragment(track.cues[2], children);
+});
+
+check_cues_from_track("resources/class-bad.vtt", function(track) {
+ assert_equals(track.cues.length, 3);
+
+ var children = [
+ { type: "span", value: [ { type: "text", value: "Bear is Coming!!!!!" } ] },
+ { type: "text", value: "\nThe space signified an annotation start." }
+ ];
+ assert_cue_fragment(track.cues[0], children);
+
+ children = [
+ { type: "span", style: { className: "red&large" },
+ value: [ { type: "text", value: "I said Bear is coming!!!!" } ] },
+ { type: "text", value: "\nProbably should only allow characters that CSS allows in class names." }
+ ];
+ assert_cue_fragment(track.cues[1], children);
+
+ children = [
+ { type: "text", value: "I said " },
+ { type: "span", style: { className: "9red upper+case" },
+ value: [ { type: "text", value: "Bear is coming now" } ] },
+ { type: "text", value: "!!!!\nProbably should only allow characters that CSS allows in class names." }
+ ];
+ assert_cue_fragment(track.cues[2], children);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-identifiers.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-identifiers.html
new file mode 100644
index 0000000000..02b0a15187
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-identifiers.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Any text other than "-->" is recognized as optional cue identifier</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/cue-id.vtt", function(track) {
+ var expected = [
+ { id: "random_id", startTime: 0, endTime: 30.5, text: "Bear is Coming!!!!!" },
+ { id: "another random identifier", startTime: 31, endTime: 60.5, text: "I said Bear is coming!!!!" },
+ { id: "identifier--too", startTime: 61, endTime: 120.5, text: "I said Bear is coming now!!!!" },
+ { id: "identifier--too", startTime: 121, endTime: 180.5, text: "Duplicate identifier" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+
+check_cues_from_track("resources/cue-id-error.vtt", function(track) {
+ var expected = [
+ { id: "", startTime: 0, endTime: 30.5, text: "Bear is Coming!!!!!" },
+ { id: "", startTime: 31, endTime: 60.5, text: "I said Bear is coming!!!!" },
+ { id: "", startTime: 61, endTime: 1200.5, text: "I said Bear is coming now!!!!" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-no-id.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-no-id.html
new file mode 100644
index 0000000000..b2f4b77083
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-no-id.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Empty cue identifiers, but having "-->" leads to discarded cue</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/cue-no-id.vtt", testTrack);
+check_cues_from_track("resources/cue-no-id-error.vtt", testTrack);
+
+function testTrack(track) {
+ var expected = [
+ { id: "", startTime: 0, endTime: 30.5, text: "Bear is Coming!!!!!" },
+ { id: "", startTime: 31, endTime: 60.5, text: "I said Bear is coming!!!!" },
+ { id: "", startTime: 61, endTime: 1200.5, text: "I said Bear is coming now!!!!" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+}
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-recovery.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-recovery.html
new file mode 100644
index 0000000000..6a104916b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-recovery.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>A cue is recovered when a line with a "-->" is encountered without blank line separator</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/cue-recovery-header.vtt", testTrack);
+check_cues_from_track("resources/cue-recovery-note.vtt", testTrack);
+check_cues_from_track("resources/cue-recovery-cuetext.vtt", testTrack);
+
+function testTrack(track) {
+ var expected = [
+ { startTime: 0, endTime: 1, text: "Valid cue 1" },
+ { startTime: 2, endTime: 3, text: "Valid cue 2" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+}
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size-align.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size-align.html
new file mode 100644
index 0000000000..a1243a95e7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size-align.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Cue size and alignment from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/cue-size-align.vtt", function(track) {
+ var expected = [
+ { size: 100, align: "start" },
+ { size: 10, align: "end" },
+ { size: 0, align: "center" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+
+check_cues_from_track("resources/cue-size-align-bad.vtt", function(track) {
+ var expected = [
+ { size: 100, align: "center" },
+ { size: 100, align: "end" },
+ { size: 100, align: "center" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size.html
new file mode 100644
index 0000000000..d8e03edce7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Cue size from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/cue-size.vtt", function(track) {
+ var expected = [
+ { size: 100 },
+ { size: 10 },
+ { size: 0 }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+
+check_cues_from_track("resources/cue-size-bad.vtt", function(track) {
+ var expected = [
+ { size: 100 },
+ { size: 100 },
+ { size: 100 }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-degenerate-cues.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-degenerate-cues.html
new file mode 100644
index 0000000000..8d2569993c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-degenerate-cues.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Degenerate cues without separating blank lines</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/degenerate-cues.vtt", function(track) {
+ var expected = [
+ { startTime: 0, endTime: 1, text: "" },
+ { startTime: 2, endTime: 3, text: "" },
+ { startTime: 4, endTime: 5, text: "" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-empty-cue.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-empty-cue.html
new file mode 100644
index 0000000000..e1f5570250
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-empty-cue.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Empty cues should not be discarded</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/empty-cue.vtt", function(track) {
+ assert_equals(track.cues.length, 3);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-entities.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-entities.html
new file mode 100644
index 0000000000..a5295795ef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-entities.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Entities in the cue text</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var getCueAsHTMLContent = function(cue) {
+ return cue.getCueAsHTML().textContent;
+};
+
+check_cues_from_track("resources/entities.vtt", function(track) {
+ var expected = [
+ { innerHTML: getCueAsHTMLContent,
+ expected: "This cue has an ampersand & character." },
+ { innerHTML: getCueAsHTMLContent,
+ expected: "This cue has a less than < character." },
+ { innerHTML: getCueAsHTMLContent,
+ expected: "This cue has a greater than > character." },
+ { innerHTML: getCueAsHTMLContent,
+ expected: "This cue has a Left-to-Right Mark \u200e." },
+ { innerHTML: getCueAsHTMLContent,
+ expected: "This cue has a Right-to-Left Mark \u200f." },
+ { innerHTML: getCueAsHTMLContent,
+ expected: "This cue has a non-breaking space \u00a0." },
+ { innerHTML: getCueAsHTMLContent,
+ expected: "This & is parsed to the same as &." }
+ ];
+
+ assert_cues_html_content(track.cues, expected);
+});
+
+check_cues_from_track("resources/entities-wrong.vtt", function(track) {
+ var expected = [
+ { innerHTML: getCueAsHTMLContent,
+ expected: "This cue has a less than ", },
+ { innerHTML: getCueAsHTMLContent,
+ expected: "This cue has a greater than > character.\nSince it's not related to a < character,\nit's just interpreted as text.", }
+ ];
+
+ assert_cues_html_content(track.cues, expected);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-header-comment.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-header-comment.html
new file mode 100644
index 0000000000..f9b35576f3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-header-comment.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>Optional comment area under the "WEBVTT" file header is properly ignored and also, default settings and styling are currently ignored (treated as faulty cues)</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/default-styles.vtt">
+ <track src="resources/metadata-area.vtt">
+</video>
+<script>
+async_test(function(t) {
+ var video = document.querySelector("video");
+
+ var trackElements = document.querySelectorAll("track");
+ for (var i = 0; i < video.textTracks.length; i++)
+ trackElements[i].onload = t.step_func(trackLoaded);
+
+ enableAllTextTracks(video.textTracks);
+
+ var numberOfTracksLoaded = 0;
+ function trackLoaded() {
+ numberOfTracksLoaded++;
+ if (numberOfTracksLoaded != 2)
+ return;
+
+ testTrack(0);
+ testTrack(1);
+ t.done();
+ }
+
+ function testTrack(index) {
+ var expected = [
+ {
+ id : "1",
+ startTime : 0,
+ endTime : 30.5,
+ text : "Bear is Coming!!!!!"
+ },
+ {
+ id : "2",
+ startTime : 31,
+ endTime : 1200.5,
+ text : "I said Bear is coming!!!!"
+ }
+ ];
+
+ assert_cues_equal(video.textTracks[index].cues, expected);
+ }
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-interspersed-non-cue.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-interspersed-non-cue.html
new file mode 100644
index 0000000000..2287cc2830
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-interspersed-non-cue.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>An empty line after an identifier line discards the current cue and restarts the cue loop</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/interspersed-non-cue.vtt", function(track) {
+ var expected = [
+ { text: "First" },
+ { text: "Second" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-line-position.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-line-position.html
new file mode 100644
index 0000000000..bea4acb917
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-line-position.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<title>Cue line position from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/line-position.vtt">
+ <track src="resources/line-position-bad.vtt">
+</video>
+<script>
+async_test(function(t) {
+ var video = document.querySelector("video");
+
+ var trackElements = document.querySelectorAll("track");
+ assert_equals(trackElements.length, video.textTracks.length);
+ for (var i = 0; i < trackElements.length; i++)
+ trackElements[i].onload = t.step_func(trackLoaded);
+
+ enableAllTextTracks(video.textTracks);
+
+ var numberOfTracksLoaded = 0;
+ function trackLoaded() {
+ numberOfTracksLoaded++;
+ if (numberOfTracksLoaded != 2)
+ return;
+
+ testTrack(0);
+ testTrackError(1);
+ t.done();
+ }
+
+ function testTrack(index) {
+ var expected = [
+ { line : 0, snapToLines : false },
+ { line : 0, snapToLines : true },
+ { line : 50, snapToLines : false },
+ { line : 5, snapToLines : true },
+ { line : 100, snapToLines : false },
+ { line : -1, snapToLines : true },
+ { line : 500, snapToLines : true }
+ ];
+
+ assert_cues_match(video.textTracks[index].cues, expected);
+ }
+
+ function testTrackError(index) {
+ var expected = [
+ { line : "auto", snapToLines : true },
+ { line : "auto", snapToLines : true },
+ { line : "auto", snapToLines : true },
+ { line : "auto", snapToLines : true },
+ { line : "auto", snapToLines : true }
+ ];
+
+ assert_cues_match(video.textTracks[index].cues, expected);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-magic-header.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-magic-header.html
new file mode 100644
index 0000000000..ff4637a8a5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-magic-header.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<title>Magic file header "WEBVTT" leads to the file properly recognized as a WebVTT file</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/webvtt-file.vtt">
+ <track src="resources/webvtt-rubbish.vtt">
+ <track src="resources/no-webvtt.vtt">
+</video>
+<script>
+async_test(function(t) {
+ var video = document.querySelector("video");
+
+ var trackElements = document.querySelectorAll("track");
+ trackElements[0].onload = t.step_func(trackLoaded);
+ trackElements[1].onload = t.step_func(trackLoaded);
+ trackElements[2].onerror = t.step_func(trackLoaded);
+
+ enableAllTextTracks(video.textTracks);
+
+ var numberOfTracksLoaded = 0;
+ function trackLoaded() {
+ numberOfTracksLoaded++;
+ if (numberOfTracksLoaded != 3)
+ return;
+
+ testTrack(0);
+ testTrack(1);
+ testTrackError(2);
+ t.done();
+ }
+
+ function testTrack(index) {
+ var expected = [
+ {
+ id : "1",
+ startTime : 0,
+ endTime : 30.5,
+ text : "Bear is Coming!!!!!"
+ },
+ {
+ id : "2",
+ startTime : 31,
+ endTime : 1200.5,
+ text : "I said Bear is coming!!!!"
+ }
+ ];
+
+ assert_cues_equal(video.textTracks[index].cues, expected);
+ }
+
+ function testTrackError(index) {
+ assert_cues_equal(video.textTracks[index].cues, []);
+ }
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-markup.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-markup.html
new file mode 100644
index 0000000000..ceb05dd450
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-markup.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<title>Cues with &lt;b&gt;, &lt;i&gt;, &lt;u&gt;, &lt;rt&gt; and &lt;ruby&gt; tags</title>
+<meta name="timeout" content="long">
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/markup.vtt", function(track) {
+ assert_equals(track.cues.length, 4);
+
+ var children = [
+ { type: "text", value: "The following bear is bold:\n" },
+ { type: "b", value: [ { type: "text", value: "Bear" } ] },
+ { type: "text", value: " is Coming!!!!!" }
+ ];
+ assert_cue_fragment(track.cues[0], children);
+
+ children = [
+ { type: "text", value: "The following bear is in italics and has a class of \"larger\":\n" },
+ { type: "i", value: [ { type: "text", value: "Bear" } ] },
+ { type: "text", value: " is Coming!!!!!" }
+ ];
+
+ var fragment = createFragment(children);
+ fragment.querySelector("i").className = "larger";
+ assert_true(fragment.isEqualNode(track.cues[1].getCueAsHTML()));
+
+ children = [
+ { type: "text", value: "The following bear is underlined even though the element has a blank:\nI said " },
+ { type: "u", value: [ { type: "text", value: "Bear" } ] },
+ { type: "text", value: " is coming!!!!" }
+ ];
+ assert_cue_fragment(track.cues[2], children);
+
+ children = [
+ { type: "text", value: "The following bear is ruby annotated:\nI said " },
+ {
+ type: "ruby",
+ value: [
+ { type: "text", value: "Bear" },
+ {
+ type: "rt",
+ value: [ { type: "text", value: "bear with me" } ]
+ }
+ ]
+ },
+ { type: "text", value: " is coming!!!!" }
+ ];
+ assert_cue_fragment(track.cues[3], children);
+});
+
+check_cues_from_track("resources/markup-bad.vtt", function(track) {
+ assert_equals(track.cues.length, 4);
+
+ var children = [
+ { type: "text", value: "The following bear starts bold but end is broken:\n" },
+ {
+ type: "b",
+ value:
+ [
+ { type: "text", value: "Bear" },
+ { type: "text", value: " is Coming!!!!!" }
+ ]
+ }
+ ];
+ assert_cue_fragment(track.cues[0], children);
+
+ children = [
+ { type: "text", value: "The following bear is not in italics but the markup is removed:\n" },
+ { type: "text", value: "Bear" },
+ { type: "text", value: " is Coming!!!!!" }
+ ];
+ assert_cue_fragment(track.cues[1], children);
+
+ children = [
+ { type: "text", value: "The following bear is not underlined and markup is removed:\nI said " },
+ { type: "text", value : "Bear" },
+ { type: "text", value : " is coming!!!!" }
+ ];
+ assert_cue_fragment(track.cues[2], children);
+
+ children = [
+ { type: "text", value: "The following bear is not ruby annotated and markup is removed:\nI said " },
+ { type: "text", value: "Bear" },
+ { type: "text", value: "bear with me" },
+ { type: "text", value: " is coming!!!!" }
+ ];
+ assert_cue_fragment(track.cues[3], children);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-newlines.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-newlines.html
new file mode 100644
index 0000000000..4da7e6b1b9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-newlines.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>A cue with no newline at eof is parsed properly</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/no-newline-at-eof.vtt" default>
+ <script>
+ async_test(function(t) {
+ var track = document.querySelector("track");
+
+ track.onload = t.step_func_done(function() {
+ var expected = [
+ {
+ id : "1",
+ startTime : 0,
+ endTime : 30.5,
+ text : "Bear is Coming!!!!!"
+ }
+ ];
+
+ assert_cues_equal(track.track.cues, expected);
+ });
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-no-timings.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-no-timings.html
new file mode 100644
index 0000000000..a39a2c37aa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-no-timings.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Cue without timings are ignored</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/no-timings.vtt" default>
+ <script>
+ async_test(function(t) {
+ var track = document.querySelector("track");
+
+ track.onload = t.step_func_done(function() {
+ assert_cues_equal(track.track.cues, []);
+ });
+ });
+ </script>
+</video> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines-ref.html
new file mode 100644
index 0000000000..137a9334f2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines-ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Reference test for track-webvtt-non-snap-to-lines.html</title>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/media.js"></script>
+<style>
+.container {
+ position: relative;
+ display: inline-block;
+}
+.cue {
+ position: absolute;
+ top: 30px;
+ left: 0px;
+ font-family: sans-serif;
+ background: green;
+ color: rgba(255, 255, 255, 1);
+ font-size: 7.5px;
+}
+</style>
+<div class="container">
+ <video autoplay onplaying="this.onplaying = null; this.pause(); takeScreenshot();">
+ <script>
+ document.currentScript.parentNode.src = getVideoURI("/media/test");
+ </script>
+ </video>
+ <span class="cue">Bear is Coming!!!!!</span>
+</div>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines.html
new file mode 100644
index 0000000000..ec350ff44d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Position is not adjusted for non snap-to-lines cues</title>
+<link rel="match" href="track-webvtt-non-snap-to-lines-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/media.js"></script>
+<style>
+::cue {
+ background: green;
+}
+</style>
+<video autoplay onplaying="this.onplaying = null; this.pause(); takeScreenshot();"></video>
+<script>
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+var cue = new VTTCue(0, 1, "Bear is Coming!!!!!");
+cue.snapToLines = false;
+cue.line = 20;
+cue.align = "left";
+track.addCue(cue);
+track.mode = "showing";
+video.src = getVideoURI("/media/test");
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-positioning.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-positioning.html
new file mode 100644
index 0000000000..d14a5768d3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-positioning.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Cue text position from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/positioning.vtt", testTrack);
+check_cues_from_track("resources/positioning-ltr.vtt", testTrack);
+
+check_cues_from_track("resources/positioning-bad.vtt", function(track) {
+ var expected = [
+ { position: "auto" },
+ { position: "auto" },
+ { position: "auto" },
+ { position: "auto" },
+ { position: "auto" },
+ { position: "auto" },
+ { position: "auto" },
+ { position: "auto" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+
+function testTrack(track) {
+ var expected = [
+ { position: 0 },
+ { position: 50 },
+ { position: "auto" },
+ { position: 100 }
+ ];
+
+ assert_cues_match(track.cues, expected);
+}
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-settings.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-settings.html
new file mode 100644
index 0000000000..9ad98ffa1a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-settings.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>WebVTT settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/settings.vtt", function(track) {
+ var expected = [
+ { line: 100, position: "auto", align: "start", vertical: "" },
+ { line: 15, position: 40, align: "center", vertical: "rl" },
+ { line: "auto", position: 10, align: "center", vertical: "" },
+ { line: 95, position: "auto", align: "end", vertical: "lr" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+
+check_cues_from_track("resources/settings-bad-separation.vtt", function(track) {
+ var expected = [
+ { line: 43, position: 10, align: "center", vertical: "" },
+ { line: "auto", position: 50, align: "end", vertical: "" },
+ { line: "auto", position: "auto", align: "center", vertical: "" },
+ { line: "auto", position: 90, align: "center", vertical: "lr" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timestamp.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timestamp.html
new file mode 100644
index 0000000000..e311f121f2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timestamp.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Cues with &lt;timestamps&gt; tags</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/timestamp.vtt", function(track) {
+ assert_equals(track.cues.length, 3);
+
+ // TODO(srirama.m): Timestamps are handled as ProcessingInstructions,
+ // but because ProcessingInstructions are used in XML and not HTML,
+ // they are ignored here. This should later be tested with oncuechange events.
+
+ var children = [ { type: "text", value: "This cue is painted on." } ];
+ assert_cue_fragment_as_textcontent(track.cues[0], children);
+
+ children = [ { type: "text", value: "I said Bear is coming!!!!" } ];
+ assert_cue_fragment_as_textcontent(track.cues[1], children);
+
+ children = [ { type: "text", value: "I said Bear is coming now!!!!" } ];
+ assert_cue_fragment_as_textcontent(track.cues[2], children);
+});
+
+check_cues_from_track("resources/timestamp-bad.vtt", function(track) {
+ assert_equals(track.cues.length, 3);
+
+ var children = [ { type: "text", value: "This cue is painted on.\nBut since the last two timestamps are out of order, they are ignored." } ];
+ assert_cue_fragment_as_textcontent(track.cues[0], children);
+
+ children = [ { type: "text", value: "I said Bear is coming!!!!\nAll of these timestamps are before the start of the cue, so get ignored." } ];
+ assert_cue_fragment_as_textcontent(track.cues[1], children);
+
+ children = [ { type: "text", value: "I said Bear is coming now!!!!\nAll of these timestamps are after the end of the cue, so get ignored." } ];
+ assert_cue_fragment_as_textcontent(track.cues[2], children);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-hour.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-hour.html
new file mode 100644
index 0000000000..c03e182c79
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-hour.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<title>Cue timings and various syntax errors in timings, with hours</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/timings-hour.vtt">
+ <track src="resources/timings-hour-error.vtt">
+</video>
+<script>
+async_test(function(t) {
+ var video = document.querySelector("video");
+
+ var trackElements = document.querySelectorAll("track");
+ for (var i = 0; i < video.textTracks.length; i++)
+ trackElements[i].onload = t.step_func(trackLoaded);
+
+ enableAllTextTracks(video.textTracks);
+
+ var numberOfTracksLoaded = 0;
+ function trackLoaded() {
+ numberOfTracksLoaded++;
+ if (numberOfTracksLoaded != 2)
+ return;
+
+ testTrack0();
+ testTrack1();
+ t.done();
+ }
+
+ function testTrack0() {
+ var expected = [
+ {
+ id : "1",
+ startTime : 0,
+ endTime : 30.5,
+ text : "Bear is Coming!!!!!"
+ },
+ {
+ id : "2",
+ startTime : 31,
+ endTime : 60.5,
+ text : "I said Bear is coming!!!!"
+ },
+ {
+ id : "3",
+ startTime : 61,
+ endTime : 361200.5,
+ text : "I said Bear is coming now!!!!"
+ }
+ ];
+
+ assert_cues_equal(video.textTracks[0].cues, expected);
+ }
+
+ function testTrack1() {
+ // Test that all the cues are ignored.
+ assert_cues_equal(video.textTracks[1].cues, []);
+ }
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-no-hours.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-no-hours.html
new file mode 100644
index 0000000000..e81ae03cc2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-no-hours.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<title>Cue timings and various syntax errors in timings, without hours</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/timings-no-hour.vtt">
+ <track src="resources/timings-no-hour-errors.vtt">
+</video>
+<script>
+async_test(function(t) {
+ var video = document.querySelector("video");
+
+ var trackElements = document.querySelectorAll("track");
+ for (var i = 0; i < video.textTracks.length; i++)
+ trackElements[i].onload = t.step_func(trackLoaded);
+
+ enableAllTextTracks(video.textTracks);
+
+ var numberOfTracksLoaded = 0;
+ function trackLoaded() {
+ numberOfTracksLoaded++;
+ if (numberOfTracksLoaded != 2)
+ return;
+
+ testTrack0();
+ testTrack1();
+ t.done();
+ }
+
+ function testTrack0() {
+ var expected = [
+ {
+ id : "1",
+ startTime : 0,
+ endTime : 30.5,
+ text : "Bear is Coming!!!!!"
+ },
+ {
+ id : "2",
+ startTime : 31,
+ endTime : 60.5,
+ text : "I said Bear is coming!!!!"
+ },
+ {
+ id : "3",
+ startTime : 61,
+ endTime : 120.5,
+ text : "I said Bear is coming now!!!!"
+ },
+ {
+ id : "4",
+ startTime : 121,
+ endTime : 180.5,
+ text : "tab separators"
+ }
+ ];
+
+ assert_cues_equal(video.textTracks[0].cues, expected);
+ }
+
+ function testTrack1() {
+ // Test that all the cues are ignored.
+ assert_cues_equal(video.textTracks[1].cues, []);
+ }
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-whitespace.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-whitespace.html
new file mode 100644
index 0000000000..db1346d23a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-whitespace.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>"Skip whitespace" step around cue-timings separator</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/timings-whitespace.vtt", function(track) {
+ var expected = [
+ { id: "1", startTime: 0.1, endTime: 1.5, text: "Single U+0020 SPACE left of cue-timings separator" },
+ { id: "2", startTime: 0.1, endTime: 1.5, text: "Single U+0020 SPACE right of cue-timings separator" },
+ { id: "3", startTime: 0.1, endTime: 1.5, text: "Single U+0009 TAB left of cue-timings separator" },
+ { id: "4", startTime: 0.1, endTime: 1.5, text: "Single U+0009 TAB right of cue-timings separator" },
+ { id: "5", startTime: 0.1, endTime: 1.5, text: "Single U+000C FORM FEED left of cue-timings separator" },
+ { id: "6", startTime: 0.1, endTime: 1.5, text: "Single U+000C FORM FEED right of cue-timings separator" },
+ { id: "7", startTime: 0.1, endTime: 1.5, text: "Several U+0020 SPACE left of cue-timings separator" },
+ { id: "8", startTime: 0.1, endTime: 1.5, text: "Several U+0020 SPACE right of cue-timings separator" },
+ { id: "9", startTime: 0.1, endTime: 1.5, text: "Several U+0009 TAB left of cue-timings separator" },
+ { id: "10", startTime: 0.1, endTime: 1.5, text: "Several U+0009 TAB right of cue-timings separator" },
+ { id: "11", startTime: 0.1, endTime: 1.5, text: "Several U+000C FORM FEED left of cue-timings separator" },
+ { id: "12", startTime: 0.1, endTime: 1.5, text: "Several U+000C FORM FEED right of cue-timings separator" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end-ref.html
new file mode 100644
index 0000000000..1c8f751c2c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>WebVTT two-cue layout after the first cue has ended (reference)</title>
+<script src="/common/reftest-wait.js"></script>
+<video style="border:1px solid gray">
+ <source src="/media/white.webm" type="video/webm">
+ <source src="/media/white.mp4" type="video/mp4">
+</video>
+<script>
+// Add a single cue at line -2, where it would be if there was a first
+// cue at line -1.
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+var cue = new VTTCue(0, 3, "cue 2");
+cue.line = -2;
+track.addCue(cue);
+track.mode = "showing";
+video.play();
+video.onplaying = function() {
+ video.onplaying=null;
+ video.pause();
+ takeScreenshot();
+};
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end.html
new file mode 100644
index 0000000000..df816ffe2b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>WebVTT two-cue layout after the first cue has ended</title>
+<link rel="match" href="track-webvtt-two-cue-layout-after-first-end-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<video style="border:1px solid gray">
+ <source src="/media/white.webm" type="video/webm">
+ <source src="/media/white.mp4" type="video/mp4">
+</video>
+<script>
+// Add two cues, where the first cue ends before the second.
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+let cue1 = new VTTCue(-1, 1, "cue 1");
+track.addCue(cue1);
+// As video's duration is 10s, it ensures that this cue would always be displayed.
+track.addCue(new VTTCue(0, 10, "cue 2"));
+track.mode = "showing";
+video.play();
+cue1.onexit = () => {
+ cue1.onexit = null;
+ video.pause();
+ takeScreenshot();
+};
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-unsupported-markup.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-unsupported-markup.html
new file mode 100644
index 0000000000..ed3107f89b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-unsupported-markup.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Unsupported markup is properly ignored</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var getCueAsHTMLContent = function(cue) {
+ return cue.getCueAsHTML().textContent;
+};
+
+check_cues_from_track("resources/unsupported-markup.vtt", function(track) {
+ var expected = [
+ {
+ innerHTML: getCueAsHTMLContent,
+ expected: "Bear is Coming!!!!!\nAnd what kind of a bear it is - just have look."
+ },
+ {
+ innerHTML: getCueAsHTMLContent,
+ expected: "\n I said Bear is coming!!!!\n I said Bear is still coming!!!!\n",
+ },
+ {
+ innerHTML: getCueAsHTMLContent,
+ expected: "\n I said Bear is coming now!!!!\n \n \n",
+ }
+ ];
+
+ assert_cues_html_content(track.cues, expected);
+
+ var expected_text = [
+ { text: "<h1>Bear is Coming!!!!!</h1>\n<p>And what kind of a bear it is - just have <a href=\"webpage.html\">look</a>.</p>" },
+ { text: "<ul>\n <li>I said Bear is coming!!!!</li>\n <li>I said Bear is still coming!!!!</li>\n</ul>" },
+ { text: "<ol>\n <li>I said Bear is coming now!!!!</li>\n <li><img src=\"bear.png\" alt=\"mighty bear\"></li>\n <li><video src=\"bear_ad.webm\" controls></video></li>\n</ol>" }
+ ];
+
+ assert_cues_match(track.cues, expected_text);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-utf8.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-utf8.html
new file mode 100644
index 0000000000..eb44c85ba8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-utf8.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>UTF-8 encoded characters are recognized properly and different encodings (iconv) are not recognized as a WebVTT file</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+ <track src="resources/utf8.vtt">
+ <track src="resources/iso2022jp3.vtt">
+</video>
+<script>
+async_test(function(t) {
+ var video = document.querySelector("video");
+
+ var trackElements = document.querySelectorAll("track");
+ for (var i = 0; i < video.textTracks.length; i++)
+ trackElements[i].onload = t.step_func(trackLoaded);
+
+ enableAllTextTracks(video.textTracks);
+
+ var numberOfTracksLoaded = 0;
+ function trackLoaded() {
+ numberOfTracksLoaded++;
+ if (numberOfTracksLoaded != 2)
+ return;
+
+ testTrack0();
+ testTrack1();
+ t.done();
+ }
+
+ function testTrack0() {
+ var expected = [
+ {
+ id : "1",
+ startTime : 0,
+ endTime : 30.5,
+ text : "景気判断"
+ },
+ {
+ id : "2",
+ startTime : 31,
+ endTime : 1200.5,
+ text : "電力不足"
+ }
+ ];
+
+ var cues = video.textTracks[0].cues;
+ assert_equals(cues.length, 2);
+ assert_cues_equal(cues, expected);
+ }
+
+ function testTrack1() {
+ assert_equals(video.textTracks[1].cues.length, 2);
+ }
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-valign.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-valign.html
new file mode 100644
index 0000000000..ace0760740
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-valign.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Cue vertical alignment (direction) from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/valign.vtt", testTrack);
+check_cues_from_track("resources/valign-ltr.vtt", testTrack);
+check_cues_from_track("resources/valign-bad.vtt", function(track) {
+ var expected = [
+ { vertical: "" },
+ { vertical: "" },
+ { vertical: "" }
+ ];
+
+ assert_cues_match(track.cues, expected);
+});
+
+function testTrack(track) {
+ var expected = [
+ { vertical: "rl", align: "center", position: "auto" },
+ { vertical: "lr", align: "center", position: "auto" },
+ { vertical: "rl", align: "start", position: 0 }
+ ];
+
+ assert_cues_match(track.cues, expected);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-voice.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-voice.html
new file mode 100644
index 0000000000..5df8b4057a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-voice.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>Cues with voice markup &lt;v&gt;</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/voice.vtt", function(track) {
+ assert_equals(track.cues.length, 3);
+
+ var children = [
+ { type: "span", style: { className: "blue", title: "Speaker" },
+ value: [ { type: "text", value: "Bear is Coming!!!!!" } ] },
+ { type: "text", value: "\nText span with a class and an annotation." }
+ ];
+ assert_cue_fragment(track.cues[0], children);
+
+ children = [
+ { type: "span", style: { title: "Doe Hunter" },
+ value: [ { type: "text", value: "I said Bear is coming!!!!" } ] }
+ ];
+ assert_cue_fragment(track.cues[1], children);
+
+ children = [
+ { type: "text", value: "I said " },
+ { type: "span", style: { className: "blue", title: "Speaker" },
+ value: [ { type: "text", value: "Bear is coming now" } ] },
+ { type: "text", value: "!!!!" }
+ ];
+ assert_cue_fragment(track.cues[2], children);
+});
+
+check_cues_from_track("resources/voice-bad.vtt", function(track) {
+ assert_equals(track.cues.length, 3);
+
+ var children = [
+ { type: "text", value: "Bear is Coming!!!!!" },
+ { type: "text", value: "\nThis is two annotations for an empty tag." }
+ ];
+ assert_cue_fragment(track.cues[0], children);
+
+ children = [
+ { type: "text", value: "I said Bear is coming!!!!" },
+ { type: "text", value: "\nThis does not parse as a voice tag." }
+ ];
+ assert_cue_fragment(track.cues[1], children);
+
+ children = [
+ { type: "text", value: "I said " },
+ { type: "text", value: "Bear is coming now" },
+ { type: "text", value: "!!!!\nThis does not parse as a voice tag." }
+ ];
+ assert_cue_fragment(track.cues[2], children);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/vtt-cue-float-precision.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/vtt-cue-float-precision.html
new file mode 100644
index 0000000000..9cb5824279
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/track/track-element/vtt-cue-float-precision.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<title>Float precision of VTTCue attributes line, position and size</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var cue = new VTTCue(0, 1, 'text');
+
+ // Assign a value which is exactly representable as double but not float.
+ var doubleValue = 1.000000000000004;
+ cue.line = doubleValue;
+ assert_equals(cue.line, doubleValue);
+ cue.position = doubleValue;
+ assert_equals(cue.position, doubleValue);
+ cue.size = doubleValue;
+ assert_equals(cue.size, doubleValue);
+
+ // Assign a value which is exactly representable as float but is non-integral.
+ var floatValue = 1.5;
+ cue.line = floatValue;
+ assert_equals(cue.line, floatValue);
+ cue.position = floatValue;
+ assert_equals(cue.position, floatValue);
+ cue.size = floatValue;
+ assert_equals(cue.size, floatValue);
+}, document.title+', stored as floats');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/user-interface/muted.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/user-interface/muted.html
new file mode 100644
index 0000000000..eb6d2ac688
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/user-interface/muted.html
@@ -0,0 +1,169 @@
+<!doctype html>
+<title>muted</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<style>video { display: none; }</style>
+<div id=log></div>
+
+<script>
+function test_setting(e, muted, hasAttribute) {
+ assert_equals(e.muted, muted);
+ assert_equals(e.hasAttribute('muted'), hasAttribute);
+
+ e.muted = !e.muted;
+ assert_equals(e.muted, !muted);
+ assert_equals(e.hasAttribute('muted'), hasAttribute);
+
+ e.muted = !e.muted;
+ assert_equals(e.muted, muted);
+ assert_equals(e.hasAttribute('muted'), hasAttribute);
+}
+</script>
+
+<!-- These tests are inside <audio>/<video> so that the steps for updating the
+ muted IDL attribute cannot be delayed until the end tag is parsed. -->
+
+<audio id=a1>
+<script>
+var a1 = document.getElementById('a1');
+
+test(function() {
+ assert_false(a1.muted);
+}, 'getting audio.muted (parser-created)');
+
+test(function() {
+ test_setting(a1, false, false);
+}, 'setting audio.muted (parser-created)');
+</script>
+</audio>
+
+<audio id=a2 muted>
+<script>
+var a2 = document.getElementById('a2');
+
+test(function() {
+ assert_true(a2.muted);
+}, 'getting audio.muted with muted="" (parser-created)');
+
+test(function() {
+ test_setting(a2, true, true);
+}, 'setting audio.muted with muted="" (parser-created)');
+</script>
+</audio>
+
+<video id=v1>
+<script>
+var v1 = document.getElementById('v1');
+
+test(function() {
+ assert_false(v1.muted);
+}, 'getting video.muted (parser-created)');
+
+test(function() {
+ test_setting(v1, false, false);
+}, 'setting video.muted (parser-created)');
+</script>
+</video>
+
+<video id=v2 muted>
+<script>
+var v2 = document.getElementById('v2');
+
+test(function() {
+ assert_true(v2.muted);
+}, 'getting video.muted with muted="" (parser-created)');
+
+test(function() {
+ test_setting(v2, true, true);
+}, 'setting video.muted with muted="" (parser-created)');
+</script>
+</video>
+
+<!-- Negative test to ensure that the load algorithm does not update the
+ muted IDL attribute to match the content attribute. -->
+
+<video id=v3 muted></video>
+<script>
+async_test(function(t) {
+ var v = document.getElementById('v3');
+ assert_true(v.muted);
+ v.muted = false;
+ v.src = 'data:,'; // invokes load()
+ v.addEventListener('error', t.step_func(function() {
+ assert_false(v.muted);
+ t.done();
+ }));
+}, 'getting video.muted with muted="" after load (parser-created)');
+</script>
+
+<script>
+['audio', 'video'].forEach(function(tagName) {
+ test(function() {
+ var m = document.createElement(tagName);
+ assert_false(m.muted);
+ }, 'getting ' + tagName + '.muted (script-created)');
+
+ test(function() {
+ var m = document.createElement(tagName);
+ test_setting(m, false, false);
+ }, 'setting ' + tagName + '.muted (script-created)');
+
+ test(function() {
+ var m = document.createElement(tagName);
+ m.setAttribute('muted', '');
+ assert_false(m.muted);
+ }, 'getting ' + tagName + '.muted with muted="" (script-created)');
+
+ test(function() {
+ var m = document.createElement(tagName);
+ m.setAttribute('muted', '');
+ test_setting(m, false, true);
+ }, 'setting ' + tagName + '.muted with muted="" (script-created)');
+
+ // Spec bug: https://www.w3.org/Bugs/Public/show_bug.cgi?id=25153
+ /*
+ test(function() {
+ var m = document.createElement(tagName);
+ m.setAttribute('muted', '');
+ m = m.cloneNode(false);
+ assert_true(m.hasAttribute('muted'));
+ assert_false(m.muted);
+ }, 'getting ' + tagName + '.muted with muted="" (cloneNode-created)');
+ */
+
+ test(function() {
+ var div = document.createElement('div');
+ div.innerHTML = '<' + tagName + ' muted>';
+ m = div.firstChild;
+ assert_true(m.hasAttribute('muted'));
+ assert_true(m.muted);
+ }, 'getting ' + tagName + '.muted with muted="" (innerHTML-created)');
+
+ test(function() {
+ var id = tagName;
+ assert_equals(document.getElementById(id), null);
+ document.write('<' + tagName + ' id=' + id + ' muted>');
+ m = document.getElementById(id);
+ assert_true(m.hasAttribute('muted'));
+ assert_true(m.muted);
+ }, 'getting ' + tagName + '.muted with muted="" (document.write-created)');
+
+ test(function() {
+ var m = document.createElement(tagName);
+ m.setAttribute('muted', '');
+
+ var c = m.cloneNode(true);
+ assert_true(c.muted);
+ }, 'cloning ' + tagName + ' propagates muted (script-created)');
+
+ test(function() {
+ var div = document.createElement('div');
+ div.innerHTML = '<' + tagName + ' muted>';
+ m = div.firstChild;
+
+ var c = m.cloneNode(true);
+ assert_true(c.muted);
+ }, 'cloning ' + tagName + ' propagates muted (innerHTML-created)');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_controls_present-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_controls_present-manual.html
new file mode 100644
index 0000000000..8e44951d7a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_controls_present-manual.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Video Test: video_controls_present.html</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-controls" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check if the controls attribute is present in the video element that expecting the user agent exposes a controller user interface" />
+ </head>
+ <body>
+ <p>Test passes if a controller user interface appears below and the text 'The user agent doesn't support media element.' does not appear anywhere on this page</p>
+ <video id="m" controls>The user agent doesn't support media element.</video>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_loop_base.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_loop_base.html
new file mode 100644
index 0000000000..9b5d69b31a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_loop_base.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Video Test: video_loop_base</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-loop" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check if video.loop is set to true that expecting the seeking event is fired more than once" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+ <video id="m" controls>The user agent doesn't support media element.</video>
+ <script type="text/javascript">
+ var media = document.getElementById("m");
+ var name = document.getElementsByName("assert")[0].content;
+ var t = async_test(name);
+ var looped = false;
+
+ function startTest() {
+ if (looped) {
+ t.step(function() {
+ assert_true(true, "looped");
+ });
+ t.done();
+ media.pause();
+ }
+
+ looped = true;
+ }
+
+ media.addEventListener("seeking", startTest, false);
+ media.loop = true;
+ media.src = getVideoURI("/media/2x2-green") + "?" + new Date() + Math.random();
+ media.play();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_muted_overriding_volume-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_muted_overriding_volume-manual.html
new file mode 100644
index 0000000000..6d770666cc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_muted_overriding_volume-manual.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Video Test: video_muted_overriding_volume</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-muted" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check if the muted attribute is present in the video element with volume is set to loudest that expecting the user hears no sound" />
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p>Test passes if the video is playing without sound output and the text 'The user agent doesn't support media element.' does not appear anywhere on this page</p>
+ <video id="m" controls muted>The user agent doesn't support media element.</video>
+ <script type="text/javascript">
+ var media = document.getElementById("m");
+ media.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+ media.volume = 1.0;
+ media.play();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_muted_present-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_muted_present-manual.html
new file mode 100644
index 0000000000..bc80827775
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_muted_present-manual.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Video Test: video_muted_present</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-muted" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check if the muted attribute is present in the video element that expecting the user hears no sound" />
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p>Test passes if the video is playing without sound output and the text 'The user agent doesn't support media element.' does not appear anywhere on this page</p>
+ <video id="m" controls muted>The user agent doesn't support media element.</video>
+ <script type="text/javascript">
+ var media = document.getElementById("m");
+ media.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+ media.play();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_volume_check.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_volume_check.html
new file mode 100644
index 0000000000..1a45358a76
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_volume_check.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Video Test: video_volume_check</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-volume" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check that video.volume returns the value of the muted content attribute" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+ <video id="m">The user agent doesn't support media element.</video>
+ <script type="text/javascript">
+ var media = document.getElementById("m");
+ var VOLUME = {
+ 'SILENT' : 0.0,
+ 'NORMAL' : 0.5,
+ 'LOUDEST' : 1.0,
+ 'LOWER' : -1.1,
+ 'UPPER' : 1.1,
+ };
+
+ test(function() {
+ assert_false(media.volume < VOLUME.SILENT || media.volume > VOLUME.LOUDEST, "media.volume outside the range 0.0 to 1.0 inclusive");
+ }, "Check if the intial value of the video.volume is in the range 0.0 to 1.0 inclusive");
+
+ function volume_setting(vol, name)
+ {
+ if (vol < VOLUME.SILENT || vol > VOLUME.LOUDEST) {
+ try {
+ media.volume = vol;
+ test(function() {
+ assert_true(false, "media.volume setting exception");
+ }, name);
+ } catch(e) {
+ test(function() {
+ // 1 should be e.IndexSizeError or e.INDEX_SIZE_ERR in previous spec
+ assert_equals(e.code, 1, "media.volume setting exception");
+ }, name);
+ }
+ } else {
+ media.volume = vol;
+ test(function() {
+ assert_equals(media.volume, vol, "media.volume new value");
+ }, name);
+ }
+ }
+
+ volume_setting(VOLUME.NORMAL, "Check if video.volume is able to set to new value in the range 0.0 to 1.0");
+ volume_setting(VOLUME.SILENT, "Check if media.volume is able to set to new value 0.0 as silent");
+ volume_setting(VOLUME.LOUDEST, "Check if media.volume is able to set to new value 1.0 as loudest");
+ volume_setting(VOLUME.LOWER, "Check if media.volume is set to new value less than 0.0 that expecting an IndexSizeError exception is to be thrown");
+ volume_setting(VOLUME.UPPER, "Check if video.volume is set to new value greater than 1.0 that expecting an IndexSizeError exception is to be thrown");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_volume_loudest-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_volume_loudest-manual.html
new file mode 100644
index 0000000000..7475781201
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_volume_loudest-manual.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Video Test: video_volume_loudest</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-volume" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check if the volume attribute is set to 1.0 as loudest in the video element that expecting the user hears sound loudly" />
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p>Test passes if the video is playing with sound heard and the text 'The user agent doesn't support media element.' does not appear anywhere on this page</p>
+ <video id="m" controls>The user agent doesn't support media element.</video>
+ <script type="text/javascript">
+ var media = document.getElementById("m");
+ media.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+ media.volume = 1.0;
+ media.play();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_volume_silent-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_volume_silent-manual.html
new file mode 100644
index 0000000000..1768dd4d4c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/video_volume_silent-manual.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Video Test: video_volume_silent</title>
+ <link rel="author" title="Intel" href="http://www.intel.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-volume" />
+ <meta name="flags" content="" />
+ <meta name="assert" content="Check if the volume attribute is set to 0.0 as silent in the video element that expecting the user hears no sound" />
+ <script src="/common/media.js"></script>
+ </head>
+ <body>
+ <p>Test passes if the video is playing without sound heard and the text 'The user agent doesn't support media element.' does not appear anywhere on this page</p>
+ <video id="m" controls volume=0.0>The user agent doesn't support media element.</video>
+ <script type="text/javascript">
+ var media = document.getElementById("m");
+ media.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+ media.volume = 0.0;
+ media.play();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/media-elements/volume_nonfinite.html b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/volume_nonfinite.html
new file mode 100644
index 0000000000..fce50c2e20
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/media-elements/volume_nonfinite.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Setting HTMLMediaElement.volume to non-finite numbers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+["audio", "video"].forEach(function(aElement) {
+ [NaN, Infinity, -Infinity].forEach(function(aValue) {
+ test(function() {
+ var el = document.createElement(aElement);
+ assert_throws_js(TypeError, function() {
+ el.volume = aValue;
+ });
+ }, "Setting " + aElement + ".volume to " + String(aValue) + " should throw a TypeError");
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/resources/common.js b/testing/web-platform/tests/html/semantics/embedded-content/resources/common.js
new file mode 100644
index 0000000000..06f18b3e04
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/resources/common.js
@@ -0,0 +1,45 @@
+// Helper to access the element, its associated loading promise, and also to
+// resolve the promise.
+class ElementLoadPromise {
+ constructor(element_id) {
+ this.element_id = element_id;
+ this.promise = new Promise((resolve, reject) => {
+ this.resolve = resolve
+ this.reject = reject
+ });
+ }
+ element() {
+ return document.getElementById(this.element_id);
+ }
+}
+
+// Returns if the image is complete and the lazily loaded image matches the expected image.
+function is_image_fully_loaded(image, expected_image) {
+ if (!image.complete || !expected_image.complete) {
+ return false;
+ }
+
+ if (image.width != expected_image.width ||
+ image.height != expected_image.height) {
+ return false;
+ }
+
+ let canvas = document.createElement('canvas');
+ canvas.width = image.width;
+ canvas.height = image.height;
+ let canvasContext = canvas.getContext("2d");
+ canvasContext.save();
+ canvasContext.drawImage(image, 0, 0);
+ let data = canvasContext.getImageData(0, 0, canvas.width, canvas.height).data;
+
+ canvasContext.restore();
+ canvasContext.drawImage(expected_image, 0, 0);
+ let expected_data = canvasContext.getImageData(0, 0, canvas.width, canvas.height).data;
+
+ for (var i = 0; i < data.length; i++) {
+ if (data[i] != expected_data[i]) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/resources/not-embeddable.html b/testing/web-platform/tests/html/semantics/embedded-content/resources/not-embeddable.html
new file mode 100644
index 0000000000..a941511642
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/resources/not-embeddable.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>I should not be embeddable because of X-Frame-Options</title>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/resources/not-embeddable.html.headers b/testing/web-platform/tests/html/semantics/embedded-content/resources/not-embeddable.html.headers
new file mode 100644
index 0000000000..fa717cc748
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/resources/not-embeddable.html.headers
@@ -0,0 +1 @@
+X-Frame-Options: deny
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/resources/should-load.html b/testing/web-platform/tests/html/semantics/embedded-content/resources/should-load.html
new file mode 100644
index 0000000000..a9a178ce51
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/resources/should-load.html
@@ -0,0 +1,3 @@
+<script>
+ parent.loadedCount++;
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/resources/should-not-load.html b/testing/web-platform/tests/html/semantics/embedded-content/resources/should-not-load.html
new file mode 100644
index 0000000000..6281b2da55
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/resources/should-not-load.html
@@ -0,0 +1,5 @@
+<script>
+ parent.nestingTest.step(function() {
+ parent.assert_unreached(window.frameElement.getAttribute("test-description"));
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-coords.html b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-coords.html
new file mode 100644
index 0000000000..9ec6f3e427
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-coords.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLAreaElement coords parsing</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style>
+ body { margin: 0 }
+</style>
+<img src=/images/threecolors.png usemap=#x id=img width=300 height=300>
+<map name=x><area id=area></map>
+<script src=support/hit-test.js></script>
+<script>
+tests = [
+ {desc: 'COMMA', shape: 'rect', coords: "2,2,10,10", hit: hitRect},
+ {desc: 'SEMICOLON', shape: 'rect', coords: "2;2;10;10", hit: hitRect},
+ {desc: 'SPACE', shape: 'rect', coords: "2 2 10 10", hit: hitRect},
+ {desc: 'TAB', shape: 'rect', coords: "2\t2\t10\t10", hit: hitRect},
+ {desc: 'FORM FEED', shape: 'rect', coords: "2\f2\f10\f10", hit: hitRect},
+ {desc: 'LINE FEED', shape: 'rect', coords: "2\n2\n10\n10", hit: hitRect},
+ {desc: 'CARRIGAGE RETURN', shape: 'rect', coords: "2\r2\r10\r10", hit: hitRect},
+ {desc: 'LINE TABULATION', shape: 'rect', coords: "2\u000b2\u000b10\u000b10", hit: hitNone},
+ {desc: 'LINE NEXT', shape: 'rect', coords: "2\u00852\u008510\u008510", hit: hitNone},
+ {desc: 'EN QUAD', shape: 'rect', coords: "2\u20002\u200010\u200010", hit: hitNone},
+ {desc: 'abc between numbers', shape: 'rect', coords: "2a2b20c20,2,10,10", hit: hitRect},
+ {desc: 'COLON between numbers', shape: 'rect', coords: "2:2:20:20,2,10,10", hit: hitRect},
+ {desc: 'U+0000 between numbers', shape: 'rect', coords: "2\u00002\u000020\u000020,2,10,10", hit: hitRect},
+ {desc: 'leading COMMA', shape: 'rect', coords: ",2,2,10,10", hit: hitRect},
+ {desc: 'leading SPACE', shape: 'rect', coords: " 2,2,10,10", hit: hitRect},
+ {desc: 'leading SEMICOLON', shape: 'rect', coords: ";2,2,10,10", hit: hitRect},
+ {desc: 'trailing COMMA', shape: 'rect', coords: "2,2,10,", hit: hitNone},
+ {desc: 'trailing SPACE', shape: 'rect', coords: "2,2,10 ", hit: hitNone},
+ {desc: 'trailing SEMICOLON', shape: 'rect', coords: "2,2,10;", hit: hitNone},
+ {desc: 'PERCENT', shape: 'rect', coords: "2%,2%,10%,10%", hit: hitRect},
+ {desc: 'CSS units', shape: 'rect', coords: "2in,2in,10cm,10cm", hit: hitRect},
+ {desc: 'float', shape: 'rect', coords: "1.4,1.4,10,10", hit: hitRect},
+ {desc: 'number starting with PERIOD', shape: 'rect', coords: ".4,.4,10,10", hit: [[area, 1, 1], [img, 0, 0]]},
+ {desc: 'sci-not', shape: 'rect', coords: "2,2,1e1,1e1", hit: hitRect},
+ {desc: 'leading/trailing garbage', shape: 'rect', coords: "='2,2,10,10' ", hit: hitRect},
+ {desc: 'non-ascii garbage', shape: 'rect', coords: "“2,2,10,10\"", hit: hitRect},
+ {desc: 'URL garbage with number', shape: 'rect', coords: "2,2,10ls/spain/holidays/regions/10/Canary+Islands/Canary+Islands.html", hit: hitNone},
+ {desc: 'consecutive COMMAs', shape: 'rect', coords: "2,,10,10", hit: hitNone},
+ {desc: 'consecutive SPACEs', shape: 'rect', coords: "2 10,10", hit: hitNone},
+ {desc: 'consecutive SEMICOLONs', shape: 'rect', coords: "2;;10,10", hit: hitNone},
+ {desc: 'several consecutive separators', shape: 'rect', coords: ",,2;,;2,;,10 \t\r\n10;;", hit: hitRect},
+ {desc: 'one too many numbers, trailing COMMA', shape: 'poly', coords: "100,100,120,100,100,120,300,", hit: hitPoly},
+];
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-download-click.html b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-download-click.html
new file mode 100644
index 0000000000..8100ada9d5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-download-click.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Clicking on an &lt;area> element with a download attribute must not throw an exception</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-area-element:activation-behaviour">
+<link rel="help" href="https://github.com/whatwg/html/issues/2116">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+"use strict";
+async_test(t => {
+ const frame = document.createElement("iframe");
+
+ frame.addEventListener("load", t.step_func(function () {
+ frame.contentWindow.addEventListener(
+ "beforeunload", t.unreached_func("Navigated instead of downloading"));
+ const string = "test";
+ const blob = new Blob([string], { type: "text/html" });
+
+ const link = frame.contentDocument.querySelector("#blob-url");
+ link.href = URL.createObjectURL(blob);
+
+ link.click();
+
+ t.step_timeout(() => t.done(), 1000);
+ }));
+ frame.src = "resources/area-download-click.html";
+ document.body.appendChild(frame);
+}, "Clicking on an <area> element with a download attribute must not throw an exception");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-processing.html b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-processing.html
new file mode 100644
index 0000000000..d1c3a83dd8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-processing.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLAreaElement processing</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style>
+ body { margin: 0 }
+</style>
+<img src=/images/threecolors.png usemap=#x id=img width=300 height=300>
+<map name=x><area id=area></map>
+<script src=support/hit-test.js></script>
+<script>
+var tests = [
+ {desc: 'too few numbers', shape: 'rect', coords: "2,2,10", hit: hitNone},
+ {desc: 'negative', shape: 'rect', coords: "-10,-10,10,10", hit: [[area, 1, 1], [img, 299, 299]]},
+ {desc: 'empty string', shape: 'rect', coords: "", hit: hitNone},
+ {desc: 'omitted coords', shape: 'rect', coords: null, hit: hitNone},
+ {desc: 'first > third', shape: 'rect', coords: "10,2,2,10", hit: hitRect},
+ {desc: 'second > fourth', shape: 'rect', coords: "2,10,10,2", hit: hitRect},
+ {desc: 'first > third, second > fourth', shape: 'rect', coords: "10,10,2,2", hit: hitRect},
+
+ {desc: 'negative', shape: 'default', coords: "-10,-10,-10,-10", hit: hitAll},
+
+ {desc: 'too few numbers', shape: 'circle', coords: "20,40", hit: hitNone},
+ {desc: 'negative radius', shape: 'circle', coords: "20,40,-10", hit: hitNone},
+ {desc: 'zero radius', shape: 'circle', coords: "20,40,0", hit: hitNone},
+
+ {desc: 'too few numbers', shape: 'poly', coords: "100,100,120,100,100", hit: hitNone},
+ {desc: 'one too many numbers', shape: 'poly', coords: "100,100,120,100,100,120,300", hit: hitPoly},
+ {desc: 'even-odd rule', shape: 'poly', coords: "100,100,200,100,100,200,150,50,200,200", hit: hitStar},
+];
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-shape.html b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-shape.html
new file mode 100644
index 0000000000..1ad0690f9e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-shape.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLAreaElement shape</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style>
+ body { margin: 0 }
+</style>
+<img src=/images/threecolors.png usemap=#x id=img width=300 height=300>
+<map name=x><area id=area></map>
+<script src=support/hit-test.js></script>
+<script>
+var tests = [
+ {desc: 'missing value default', shape: null, coords: "2,2,10,10", hit: hitRect},
+ {desc: 'missing value default', shape: null, coords: "20,40,10", hit: hitNone},
+ {desc: 'missing value default', shape: null, coords: null, hit: hitNone},
+ {desc: 'invalid value default', shape: 'foobar invalid', coords: "2,2,10,10", hit: hitRect},
+ {desc: 'invalid value default', shape: '', coords: "2,2,10,10", hit: hitRect},
+
+ {desc: 'empty string', shape: 'default', coords: "", hit: hitAll},
+ {desc: 'omitted coords', shape: 'DEFAULT', coords: null, hit: hitAll},
+
+ {desc: 'simple', shape: 'rect', coords: "2,2,10,10", hit: hitRect},
+ {desc: 'simple', shape: 'rectangle', coords: "2,2,10,10", hit: hitRect},
+
+ {desc: 'simple', shape: 'circle', coords: "20,40,10", hit: hitCircle},
+ {desc: 'simple', shape: 'circ', coords: "20,40,10", hit: hitCircle},
+ {desc: 'simple', shape: 'CIRCLE', coords: "20,40,10", hit: hitCircle},
+ {desc: 'simple', shape: 'CIRC', coords: "20,40,10", hit: hitCircle},
+ {desc: 'LATIN CAPITAL LETTER I WITH DOT ABOVE', shape: 'C\u0130RCLE', coords: "20,40,10", hit: hitNone},
+ {desc: 'LATIN SMALL LETTER DOTLESS I', shape: 'c\u0131rcle', coords: "20,40,10", hit: hitNone},
+
+ {desc: 'simple', shape: 'poly', coords: "100,100,120,100,100,120", hit: hitPoly},
+ {desc: 'simple', shape: 'polygon', coords: "100,100,120,100,100,120", hit: hitPoly},
+];
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-stringifier.html b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-stringifier.html
new file mode 100644
index 0000000000..3a661893d7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/area-stringifier.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>HTMLAreaElement stringifier</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://webidl.spec.whatwg.org/#es-stringifier">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/common/stringifiers.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ test_stringifier_attribute(document.createElement("area"), "href", false);
+ var area = document.createElement("area");
+ area.setAttribute("href", "foo");
+ test_stringifier_attribute(area, "href", false);
+})
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/resources/area-download-click.html b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/resources/area-download-click.html
new file mode 100644
index 0000000000..c0679f8233
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/resources/area-download-click.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<img src="/images/threecolors.png" usemap="#x" id="img" width="300" height="300">
+<map name="x">
+ <area id="blob-url" download="foo.html" coords="0,0,300,300">
+</map>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/support/hit-test.js b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/support/hit-test.js
new file mode 100644
index 0000000000..82a98f1c35
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-area-element/support/hit-test.js
@@ -0,0 +1,42 @@
+setup({explicit_done: true});
+
+var img = document.getElementById('img');
+var area = document.getElementById('area');
+
+var hitRect = [[area, 3, 3], [area, 9, 9], [img, 1, 3], [img, 3, 1], [img, 11, 9], [img, 9, 11], [img, 21, 41], [img, 101, 101]];
+var hitNone = [[img, 3, 3], [img, 9, 9], [img, 1, 3], [img, 3, 1], [img, 11, 9], [img, 9, 11], [img, 21, 41], [img, 101, 101]];
+var hitAll = [[area, 1, 1], [area, 1, 299], [area, 299, 1], [area, 299, 299], [area, 21, 41], [area, 101, 101]];
+var hitCircle = [[area, 11, 40], [area, 29, 40], [area, 20, 31], [area, 20, 49], [img, 12, 32], [img, 28, 48], [img, 101, 101]];
+var hitPoly = [[area, 101, 101], [area, 119, 101], [area, 101, 119], [img, 118, 118], [img, 3, 3], [img, 21, 41]];
+var hitStar = [[area, 101, 101], [area, 199, 101], [area, 150, 51], [img, 150, 125], [img, 3, 3], [img, 21, 41]];
+
+var tests;
+// The test file should have `tests` defined as follows:
+// tests = [
+// {desc: string, shape: string?, coords: string?, hit: [[element, x, y], ...]},
+// ...
+// ];
+
+onload = function() {
+ tests.forEach(function(t) {
+ test(function(t_obj) {
+ if (t.shape === null) {
+ area.removeAttribute('shape');
+ } else {
+ area.shape = t.shape;
+ }
+ if (area.coords === null) {
+ area.removeAttribute('coords');
+ } else {
+ area.coords = t.coords;
+ }
+ t.hit.forEach(function(arr) {
+ var expected = arr[0];
+ var x = arr[1];
+ var y = arr[2];
+ assert_equals(document.elementFromPoint(x, y), expected, 'elementFromPoint('+x+', '+y+')');
+ });
+ }, t.desc + ': ' + format_value(t.coords) + ' (' + t.shape + ')');
+ });
+ done();
+};
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio-appendChild-to-inactive-document-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio-appendChild-to-inactive-document-crash.html
new file mode 100644
index 0000000000..33d52ca899
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio-appendChild-to-inactive-document-crash.html
@@ -0,0 +1,6 @@
+<iframe id=i></iframe>
+<script>
+var doc = i.contentDocument.cloneNode();
+i.remove();
+doc.appendChild(document.createElement("audio"));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio-play-in-inactive-document-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio-play-in-inactive-document-crash.html
new file mode 100644
index 0000000000..ade40797b9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio-play-in-inactive-document-crash.html
@@ -0,0 +1,8 @@
+<audio id="a"></audio>
+<iframe id="i"></iframe>
+<script>
+var a = document.getElementById("a");
+i.contentDocument.documentElement.appendChild(a);
+i.remove();
+a.play();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_001.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_001.htm
new file mode 100644
index 0000000000..f455c68241
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_001.htm
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Media Elements: Content inside the 'audio' element is not shown to the user (image).</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#audio" />
+ <link rel="match" href="audio_content-ref.htm" />
+ <meta name="assert" content="Content inside the 'audio' element is not shown to the user (image)." />
+</head>
+<body>
+<p>Test passes if there is no red.</p>
+<div id='testcontent'>
+<audio><img src="../../../../images/fail.gif" /></audio>
+
+</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_002.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_002.htm
new file mode 100644
index 0000000000..23b3ea188a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_002.htm
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Media Elements: Content inside the 'audio' element is not shown to the user.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#audio" />
+ <link rel="match" href="audio_content-ref.htm" />
+ <meta name="assert" content="Content inside the 'audio' element is not shown to the user." />
+</head>
+<body>
+<p>Test passes if there is no red.</p>
+<div id='testcontent'>
+<audio><span style="color: red;">FAIL</span></audio>
+
+</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_constructor.html b/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_constructor.html
new file mode 100644
index 0000000000..c5b5b80ac1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_constructor.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Audio constructor</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var throwingObject = {
+ toString: function() { throw Error() },
+ valueOf: function() { throw Error() }
+ };
+ var tests = [
+ [function() { return new Audio() }, null, "No arguments"],
+ [function() { return new Audio("") }, "", "Empty string argument"],
+ [function() { return new Audio("src") }, "src", "Non-empty string argument"],
+ [function() { return new Audio(null) }, "null", "Null argument"],
+ [function() { return new Audio(undefined) }, null, "Undefined argument"],
+ [function() { return new Audio("", throwingObject) }, "", "Extra argument"],
+ ];
+ tests.forEach(function(t) {
+ var fn = t[0], expectedSrc = t[1], description = t[2];
+ test(function() {
+ var element = fn();
+ assert_equals(element.localName, "audio");
+ assert_equals(element.tagName, "AUDIO");
+ assert_equals(element.namespaceURI, "http://www.w3.org/1999/xhtml");
+ assert_equals(element.nodeType, Node.ELEMENT_NODE);
+ assert_equals(element.getAttribute("preload"), "auto");
+ assert_equals(element.getAttribute("src"), expectedSrc);
+ assert_equals(element.ownerDocument, document);
+ }, description);
+ });
+});
+
+test(function() {
+ var audio = new Audio();
+ assert_equals(Object.getPrototypeOf(audio), HTMLAudioElement.prototype);
+}, "Prototype of object created with named constructor");
+
+test(function() {
+ assert_throws_js(TypeError, function() {
+ Audio();
+ });
+}, "Calling Audio should throw");
+test(function() {
+ assert_throws_js(TypeError, function() {
+ HTMLAudioElement();
+ });
+}, "Calling HTMLAudioElement should throw");
+test(function() {
+ assert_throws_js(TypeError, function() {
+ new HTMLAudioElement();
+ });
+}, "Constructing HTMLAudioElement should throw");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_content-ref.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_content-ref.htm
new file mode 100644
index 0000000000..ef5964496d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-audio-element/audio_content-ref.htm
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Media Elements: Content inside the 'audio' element is not shown to the user.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+</head>
+<body>
+<p>Test passes if there is no red.</p>
+<div id='testcontent'>
+</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/2d-getcontext-options.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/2d-getcontext-options.html
new file mode 100644
index 0000000000..5d35d4108c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/2d-getcontext-options.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<title>Options conversion for getContext("2d")</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+ const expected = [
+ "alpha",
+ "colorSpace",
+ "colorSpace toString",
+ "desynchronized",
+ "willReadFrequently",
+ ];
+ var actual = [];
+ const options = {
+ get alpha() {
+ actual.push("alpha");
+ return true;
+ },
+ get willReadFrequently() {
+ actual.push("willReadFrequently");
+ return false;
+ },
+ get desynchronized() {
+ actual.push("desynchronized");
+ return false;
+ },
+ get colorSpace() {
+ actual.push("colorSpace");
+ return {
+ toString() {
+ actual.push("colorSpace toString");
+ return "srgb";
+ }
+ };
+ },
+ };
+
+ const canvas = document.createElement("canvas");
+ const context = canvas.getContext('2d', options);
+ assert_not_equals(context, null, "context");
+ assert_array_equals(actual, expected, "order of operations (creation)");
+ actual = [];
+ assert_equals(canvas.getContext('2d', options), context, "cached context");
+ assert_array_equals(actual, expected, "order of operations (caching)");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-001.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-001.html
new file mode 100644
index 0000000000..327c9f49d6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-001.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Canvas descendants focusability</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/canvas.html#being-used-as-relevant-canvas-fallback-content">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#focusable-area">
+<meta name="assert" content="Checks that elements being used as relevant canvas
+ fallback content can be focusable even if not rendered.">
+<div id="log"></div>
+<canvas>
+ <button data-focusable="true"></button>
+ <section data-focusable="false">
+ <div data-focusable="false"></div>
+ <span data-focusable="false"></span>
+ <a data-focusable="false"></a>
+ </section>
+ <section tabindex="-1" data-focusable="true">
+ <div tabindex="-1" data-focusable="true"></div>
+ <span tabindex="-1" data-focusable="true"></span>
+ <a href="#" data-focusable="true"></a>
+ </section>
+</canvas>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+for (let element of document.querySelectorAll("[data-focusable]")) {
+ let title = element.cloneNode(false).outerHTML.toLowerCase();
+ title = title.slice(0, title.lastIndexOf("<"));
+ test(function() {
+ assert_true(document.activeElement !== element, "Not initially focused");
+ element.focus();
+ if (JSON.parse(element.dataset.focusable)) {
+ assert_true(document.activeElement === element, "Should be focused");
+ } else {
+ assert_true(document.activeElement !== element, "Shouldn't be focused");
+ }
+ }, title);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-002.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-002.html
new file mode 100644
index 0000000000..aa607365d5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-002.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Canvas descendants focusability</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/canvas.html#being-used-as-relevant-canvas-fallback-content">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#focusable-area">
+<meta name="assert" content="Checks that descendants of a non-rendered canvas
+ aren't relevant canvas fallback content, so they aren't focusable.">
+<div id="log"></div>
+<canvas hidden>
+ <button data-focusable="false"></button>
+ <section tabindex="-1" data-focusable="false">
+ <div tabindex="-1" data-focusable="false"></div>
+ <span tabindex="-1" data-focusable="false"></span>
+ <a href="#" data-focusable="false"></a>
+ </section>
+</canvas>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup(() => {
+ const canvas = document.querySelector("canvas");
+ assert_equals(canvas.getClientRects().length, 0, "Canvas not rendered");
+});
+for (let element of document.querySelectorAll("[data-focusable]")) {
+ let title = element.cloneNode(false).outerHTML.toLowerCase();
+ title = title.slice(0, title.lastIndexOf("<"));
+ test(function() {
+ assert_equals(element.getClientRects().length, 0, "Not rendered");
+ assert_true(document.activeElement !== element, "Not initially focused");
+ element.focus();
+ if (JSON.parse(element.dataset.focusable)) {
+ assert_true(document.activeElement === element, "Should be focused");
+ } else {
+ assert_true(document.activeElement !== element, "Shouldn't be focused");
+ }
+ }, title);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-003.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-003.tentative.html
new file mode 100644
index 0000000000..cd42d1e6b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-003.tentative.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Canvas descendants focusability</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/canvas.html#being-used-as-relevant-canvas-fallback-content">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#focusable-area">
+<link rel="help" href="https://github.com/whatwg/html/issues/7534">
+<meta name="assert" content="Checks that elements being used as relevant canvas
+ fallback content can't be focusable if they are not rendered because of an
+ explicit 'display: none' style, but can if they are not rendered because of
+ a 'display: contents' style.">
+<div id="log"></div>
+<canvas>
+ <button hidden data-focusable="false"></button>
+ <section hidden tabindex="-1" data-focusable="false">
+ <div tabindex="-1" data-focusable="false"></div>
+ <span tabindex="-1" data-focusable="false"></span>
+ <a href="#" data-focusable="false"></a>
+ </section>
+ <button style="display: contents" data-focusable="true"></button>
+ <section style="display: contents" tabindex="-1" data-focusable="true">
+ <div style="display: contents" tabindex="-1" data-focusable="true"></div>
+ <span style="display: contents" tabindex="-1" data-focusable="true"></span>
+ <a style="display: contents" href="#" data-focusable="true"></a>
+ </section>
+</canvas>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup(() => {
+ const canvas = document.querySelector("canvas");
+ assert_greater_than(canvas.getClientRects().length, 0, "Canvas is rendered");
+});
+for (let element of document.querySelectorAll("[data-focusable]")) {
+ let title = element.cloneNode(false).outerHTML.toLowerCase();
+ title = title.slice(0, title.lastIndexOf("<"));
+ test(function() {
+ assert_equals(element.getClientRects().length, 0, "Not rendered");
+ assert_true(document.activeElement !== element, "Not initially focused");
+ element.focus();
+ if (JSON.parse(element.dataset.focusable)) {
+ assert_true(document.activeElement === element, "Should be focused");
+ } else {
+ assert_true(document.activeElement !== element, "Shouldn't be focused");
+ }
+ }, title);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-004.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-004.tentative.html
new file mode 100644
index 0000000000..5d8dfcd2f4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-004.tentative.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Canvas descendants focusability</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/canvas.html#being-used-as-relevant-canvas-fallback-content">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#focusable-area">
+<link rel="help" href="https://github.com/whatwg/html/issues/7534">
+<meta name="assert" content="Checks that elements being used as relevant canvas
+ fallback content can't be focusable if they are not in the flat tree.">
+<div id="log"></div>
+<canvas>
+ <section id="shadow-host">
+ <button data-focusable="false"></button>
+ <section tabindex="-1" data-focusable="false">
+ <div tabindex="-1" data-focusable="false"></div>
+ <span tabindex="-1" data-focusable="false"></span>
+ <a href="#" data-focusable="false"></a>
+ </section>
+ </section>
+</canvas>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup(() => {
+ const canvas = document.querySelector("canvas");
+ assert_greater_than(canvas.getClientRects().length, 0, "Canvas is rendered");
+ const shadowHost = document.getElementById("shadow-host");
+ const shadowRoot = shadowHost.attachShadow({ mode: "open" });
+ const slot = document.createElement("slot");
+ slot.name = "slot";
+ shadowRoot.appendChild(slot);
+});
+for (let element of document.querySelectorAll("[data-focusable]")) {
+ let title = element.cloneNode(false).outerHTML.toLowerCase();
+ title = title.slice(0, title.lastIndexOf("<"));
+ test(function() {
+ assert_equals(element.getClientRects().length, 0, "Not rendered");
+ assert_true(document.activeElement !== element, "Not initially focused");
+ element.focus();
+ if (JSON.parse(element.dataset.focusable)) {
+ assert_true(document.activeElement === element, "Should be focused");
+ } else {
+ assert_true(document.activeElement !== element, "Shouldn't be focused");
+ }
+ }, title);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-005.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-005.html
new file mode 100644
index 0000000000..f3bee6b06b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-005.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Canvas descendants focusability</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/canvas.html#being-used-as-relevant-canvas-fallback-content">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#focusable-area">
+<meta name="assert" content="Checks that descendants of a canvas that represents
+ fallback content are not focusable if not rendered, as usual.">
+<div id="log"></div>
+<!-- Use a sandboxed iframe to disable scripting and make the canvas
+ represent its fallback content instead of embedded content. -->
+<iframe sandbox="allow-same-origin" allow="focus-without-user-activation *"
+ srcdoc='
+ <button data-focusable="true" a></button>
+ <canvas>
+ <button data-focusable="true"></button>
+ <section tabindex="-1" data-focusable="true">
+ <div tabindex="-1" data-focusable="true"></div>
+ <span tabindex="-1" data-focusable="true"></span>
+ <a href="#" data-focusable="true"></a>
+ </section>
+ <button hidden data-focusable="false"></button>
+ <section tabindex="-1" hidden data-focusable="false">
+ <div tabindex="-1" data-focusable="false"></div>
+ <span tabindex="-1" data-focusable="false"></span>
+ <a href="#" data-focusable="false"></a>
+ </section>
+ </canvas>
+ '></iframe>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true });
+setup(async () => {
+ const iframe = document.querySelector("iframe");
+ await new Promise(resolve => {
+ const win = iframe.contentWindow;
+ if (win.location.href === "about:blank" ||
+ win.document.readyState !== "complete") {
+ iframe.addEventListener("load", resolve, {once: true});
+ } else {
+ resolve();
+ }
+ });
+ const doc = iframe.contentDocument;
+ for (let element of doc.querySelectorAll("[data-focusable]")) {
+ let title = element.cloneNode(false).outerHTML.toLowerCase();
+ title = title.slice(0, title.lastIndexOf("<"));
+ test(function() {
+ assert_true(doc.activeElement !== element, "Not initially focused");
+ element.focus();
+ if (JSON.parse(element.dataset.focusable)) {
+ assert_true(doc.activeElement === element, "Should be focused");
+ } else {
+ assert_true(doc.activeElement !== element, "Shouldn't be focused");
+ }
+ }, title);
+ }
+ done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.arguments.missing.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.arguments.missing.html
new file mode 100644
index 0000000000..eb4d69aed0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.arguments.missing.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: context.arguments.missing</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>context.arguments.missing</h1>
+<p class="desc"></p>
+
+<p class="notes">Defined in "Web IDL" (draft)
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("");
+_addTest(function(canvas, ctx) {
+
+assert_throws_js(TypeError, function() { canvas.getContext(); });
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.casesensitive.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.casesensitive.html
new file mode 100644
index 0000000000..8753185449
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.casesensitive.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: context.casesensitive</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>context.casesensitive</h1>
+<p class="desc">Context name "2D" is unrecognised; matching is case sensitive</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Context name \"2D\" is unrecognised; matching is case sensitive");
+_addTest(function(canvas, ctx) {
+
+_assertSame(canvas.getContext('2D'), null, "canvas.getContext('2D')", "null");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.emptystring.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.emptystring.html
new file mode 100644
index 0000000000..1f27225882
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.emptystring.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: context.emptystring</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>context.emptystring</h1>
+<p class="desc">getContext with empty string returns null</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("getContext with empty string returns null");
+_addTest(function(canvas, ctx) {
+
+_assertSame(canvas.getContext(""), null, "canvas.getContext(\"\")", "null");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.badname.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.badname.html
new file mode 100644
index 0000000000..55d503036b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.badname.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: context.unrecognised.badname</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>context.unrecognised.badname</h1>
+<p class="desc">getContext with unrecognised context name returns null</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("getContext with unrecognised context name returns null");
+_addTest(function(canvas, ctx) {
+
+_assertSame(canvas.getContext('This is not an implemented context in any real browser'), null, "canvas.getContext('This is not an implemented context in any real browser')", "null");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.badsuffix.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.badsuffix.html
new file mode 100644
index 0000000000..ea0a14aaed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.badsuffix.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: context.unrecognised.badsuffix</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>context.unrecognised.badsuffix</h1>
+<p class="desc">Context name "2d" plus a suffix is unrecognised</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Context name \"2d\" plus a suffix is unrecognised");
+_addTest(function(canvas, ctx) {
+
+_assertSame(canvas.getContext("2d#"), null, "canvas.getContext(\"2d#\")", "null");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.nullsuffix.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.nullsuffix.html
new file mode 100644
index 0000000000..ea8db36a89
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.nullsuffix.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: context.unrecognised.nullsuffix</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>context.unrecognised.nullsuffix</h1>
+<p class="desc">Context name "2d" plus a "\0" suffix is unrecognised</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Context name \"2d\" plus a \"\\0\" suffix is unrecognised");
+_addTest(function(canvas, ctx) {
+
+_assertSame(canvas.getContext("2d\0"), null, "canvas.getContext(\"2d\\0\")", "null");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.unicode.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.unicode.html
new file mode 100644
index 0000000000..727ea3584f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/context.unrecognised.unicode.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: context.unrecognised.unicode</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>context.unrecognised.unicode</h1>
+<p class="desc">Context name which kind of looks like "2d" is unrecognised</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Context name which kind of looks like \"2d\" is unrecognised");
+_addTest(function(canvas, ctx) {
+
+_assertSame(canvas.getContext("2\uFF44"), null, "canvas.getContext(\"2\\uFF44\")", "null"); // Fullwidth Latin Small Letter D
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/fallback.basic.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/fallback.basic.html
new file mode 100644
index 0000000000..5aaf49adf7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/fallback.basic.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: fallback.basic</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>fallback.basic</h1>
+<p class="desc">Fallback content is inserted into the DOM</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Fallback content is inserted into the DOM");
+_addTest(function(canvas, ctx) {
+
+_assertSame(canvas.childNodes.length, 1, "canvas.childNodes.length", "1");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/fallback.multiple.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/fallback.multiple.html
new file mode 100644
index 0000000000..9585b06a4a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/fallback.multiple.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: fallback.multiple</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>fallback.multiple</h1>
+<p class="desc">Fallback content with multiple elements</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL</p><p class="fallback">FAIL</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Fallback content with multiple elements");
+_addTest(function(canvas, ctx) {
+
+_assertSame(canvas.childNodes.length, 2, "canvas.childNodes.length", "2");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/fallback.nested.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/fallback.nested.html
new file mode 100644
index 0000000000..14b19cd104
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/fallback.nested.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: fallback.nested</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>fallback.nested</h1>
+<p class="desc">Fallback content containing another canvas (mostly testing parsers)</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><canvas><p class="fallback">FAIL (fallback content)</p></canvas><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Fallback content containing another canvas (mostly testing parsers)");
+_addTest(function(canvas, ctx) {
+
+_assertSame(canvas.childNodes.length, 2, "canvas.childNodes.length", "2");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/historical.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/historical.html
new file mode 100644
index 0000000000..33044ffb1b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/historical.html
@@ -0,0 +1,77 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Historical canvas features</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+var canvas, context, path2d;
+setup(function() {
+ canvas = document.createElement("canvas");
+ context = canvas.getContext('2d');
+ path2d = new Path2D();
+});
+function t(member, obj) {
+ var name = obj === canvas ? "Canvas" : String(obj).match(/\[object (\S+)\]/)[1];
+ test(function() {
+ assert_false(member in obj);
+ }, name + " support for " + member);
+}
+// added in https://github.com/whatwg/html/commit/0ecbf0e010df16d9c6d11eef6b2c58419158c4da
+// renamed in https://github.com/whatwg/html/commit/2542a12cb25ee93534cbed1f31b5e1bc05fcdd0e
+t("supportsContext", canvas);
+
+// removed in https://github.com/whatwg/html/commit/2cfb8e3f03d3166842d2ad0f661459d26e2a40eb
+t("probablySupportsContext", canvas);
+
+// removed in https://github.com/whatwg/html/commit/ef72f55da4acdf266174225c6ca8bf2a650d0219
+t("width", context);
+t("height", context);
+
+// removed in https://github.com/whatwg/html/commit/740634d0f30a3b76e9da166ac2fa8835fcc073ab
+t("setContext", canvas);
+t("transferControlToProxy", canvas);
+t("CanvasProxy", window);
+t("commit", canvas);
+test(function() {
+ assert_throws_js(TypeError, function() {
+ new CanvasRenderingContext2D();
+ }, 'no arguments');
+ assert_throws_js(TypeError, function() {
+ new CanvasRenderingContext2D(1, 1);
+ }, 'with arguments');
+}, "CanvasRenderingContext2D constructors");
+
+// removed in https://github.com/whatwg/html/commit/e1d04f49a38e2254a783c28987457a95a47d9511
+t("addPathByStrokingPath", path2d);
+t("addText", path2d);
+t("addPathByStrokingText", path2d);
+
+// renamed in https://github.com/whatwg/html/commit/fcb0756dd94d96df9b8355741d82fcd5ca0a6154
+test(function() {
+ var canvas = document.createElement('canvas');
+ var context = canvas.getContext('bitmaprenderer');
+ if (context) {
+ assert_false('transferImageBitmap' in context);
+ }
+}, 'ImageBitmapRenderingContext support for transferImageBitmap');
+
+// renamed in https://github.com/whatwg/html/commit/3aec2a7e04a3402201afd29c224b57fa54497517
+t('Path', window);
+
+// removed in https://github.com/whatwg/html/commit/d5759b0435091e4858c9bff90319cbe5b040eda2
+t('toDataURLHD', canvas);
+t('toBlobHD', canvas);
+t('createImageDataHD', context);
+t('getImageDataHD', context);
+t('putImageDataHD', context);
+test(function() {
+ if ('ImageData' in window) {
+ assert_false('resolution' in new ImageData(1, 1));
+ }
+}, 'ImageData support for resolution');
+
+// dropped/renamed in https://github.com/whatwg/html/commit/ff07c6d630fb986f6c4f64b2fb87387b4f89647d
+t('drawSystemFocusRing', context);
+t('drawCustomFocusRing', context);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/imagedata.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/imagedata.html
new file mode 100644
index 0000000000..e124f8ff6e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/imagedata.html
@@ -0,0 +1,58 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>ImageData Tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+ assert_throws_dom("IndexSizeError", function() {
+ new ImageData(0, 1);
+ });
+}, "ImageData(w, h), width cannot be 0");
+
+test(function() {
+ assert_throws_dom("IndexSizeError", function() {
+ new ImageData(1, 0);
+ });
+}, "ImageData(w, h), height cannot be 0");
+
+test(function() {
+ var imageData = new ImageData(2, 3);
+ assert_equals(imageData.width, 2);
+ assert_equals(imageData.height, 3);
+ assert_equals(imageData.data.length, 24);
+ assert_true(imageData.data instanceof Uint8ClampedArray);
+}, "ImageData(w, h), exposed attributes check");
+
+test(function() {
+ assert_throws_dom("InvalidStateError", function() {
+ new ImageData(new Uint8ClampedArray(3), 1);
+ });
+}, "ImageData(buffer, w), the buffer size must be a multiple of 4");
+
+test(function() {
+ assert_throws_dom("IndexSizeError", function() {
+ new ImageData(new Uint8ClampedArray(16), 3);
+ });
+}, "ImageData(buffer, w), buffer size must be a multiple of the image width");
+
+test(function() {
+ assert_throws_dom("IndexSizeError", function() {
+ new ImageData(new Uint8ClampedArray(16), 4, 3);
+ });
+}, "ImageData(buffer, w, h), buffer.length == 4 * w * h must be true");
+
+test(function() {
+ assert_throws_js(TypeError, function() {
+ new ImageData(new Int8Array(1), 1);
+ });
+}, "ImageData(buffer, w, opt h), Uint8ClampedArray argument type check");
+
+test(function() {
+ var imageData = new ImageData(new Uint8ClampedArray(24), 2);
+ assert_equals(imageData.width, 2);
+ assert_equals(imageData.height, 3);
+ assert_equals(imageData.data.length, 24);
+ assert_true(imageData.data instanceof Uint8ClampedArray);
+}, "ImageData(buffer, w, opt h), exposed attributes check");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.colour.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.colour.html
new file mode 100644
index 0000000000..166732a57b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.colour.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: initial.colour</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>initial.colour</h1>
+<p class="desc">Initial state is transparent black</p>
+
+<p class="notes">Output should be transparent black (not transparent anything-else), but manual
+verification can only confirm that it's transparent - it's not possible to make
+the actual blackness visible.
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="initial.colour.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("Initial state is transparent black");
+_addTest(function(canvas, ctx) {
+
+_assertPixel(canvas, 20,20, 0,0,0,0);
+
+
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.colour.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.colour.png
new file mode 100644
index 0000000000..eeedd0ff05
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.colour.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.clip.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.clip.html
new file mode 100644
index 0000000000..ebf52bfa76
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.clip.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: initial.reset.clip</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>initial.reset.clip</h1>
+<p class="desc">Resetting the canvas state resets the current clip region</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="/images/green-100x50.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("Resetting the canvas state resets the current clip region");
+_addTest(function(canvas, ctx) {
+
+canvas.width = 100;
+ctx.rect(0, 0, 1, 1);
+ctx.clip();
+canvas.width = 100;
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+_assertPixel(canvas, 20,20, 0,255,0,255);
+
+
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.different.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.different.html
new file mode 100644
index 0000000000..d55dd250c0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.different.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: initial.reset.different</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>initial.reset.different</h1>
+<p class="desc">Changing size resets canvas to transparent black</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="initial.reset.different.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("Changing size resets canvas to transparent black");
+_addTest(function(canvas, ctx) {
+
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 50, 50);
+_assertPixel(canvas, 20,20, 255,0,0,255);
+canvas.width = 50;
+_assertPixel(canvas, 20,20, 0,0,0,0);
+
+
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.different.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.different.png
new file mode 100644
index 0000000000..d83fdd55b1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.different.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.gradient.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.gradient.html
new file mode 100644
index 0000000000..31b56ec8e9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.gradient.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: initial.reset.gradient</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>initial.reset.gradient</h1>
+<p class="desc">Resetting the canvas state does not invalidate any existing gradients</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="/images/green-100x50.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("Resetting the canvas state does not invalidate any existing gradients");
+_addTest(function(canvas, ctx) {
+
+canvas.width = 50;
+var g = ctx.createLinearGradient(0, 0, 100, 0);
+g.addColorStop(0, '#0f0');
+g.addColorStop(1, '#0f0');
+canvas.width = 100;
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.fillStyle = g;
+ctx.fillRect(0, 0, 100, 50);
+_assertPixel(canvas, 50,25, 0,255,0,255);
+
+
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.path.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.path.html
new file mode 100644
index 0000000000..3525377d2d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.path.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: initial.reset.path</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>initial.reset.path</h1>
+<p class="desc">Resetting the canvas state resets the current path</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="initial.reset.path.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("Resetting the canvas state resets the current path");
+_addTest(function(canvas, ctx) {
+
+canvas.width = 100;
+ctx.rect(0, 0, 100, 50);
+canvas.width = 100;
+ctx.fillStyle = '#f00';
+ctx.fill();
+_assertPixel(canvas, 20,20, 0,0,0,0);
+
+
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.path.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.path.png
new file mode 100644
index 0000000000..eeedd0ff05
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.path.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.pattern.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.pattern.html
new file mode 100644
index 0000000000..28f8306d96
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.pattern.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: initial.reset.pattern</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>initial.reset.pattern</h1>
+<p class="desc">Resetting the canvas state does not invalidate any existing patterns</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="/images/green-100x50.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("Resetting the canvas state does not invalidate any existing patterns");
+_addTest(function(canvas, ctx) {
+
+canvas.width = 30;
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 30, 50);
+var p = ctx.createPattern(canvas, 'repeat-x');
+canvas.width = 100;
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.fillStyle = p;
+ctx.fillRect(0, 0, 100, 50);
+_assertPixel(canvas, 50,25, 0,255,0,255);
+
+
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.same.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.same.html
new file mode 100644
index 0000000000..1a0872ba2c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.same.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: initial.reset.same</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>initial.reset.same</h1>
+<p class="desc">Setting size (not changing the value) resets canvas to transparent black</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="initial.reset.same.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("Setting size (not changing the value) resets canvas to transparent black");
+_addTest(function(canvas, ctx) {
+
+canvas.width = 100;
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 50, 50);
+_assertPixel(canvas, 20,20, 255,0,0,255);
+canvas.width = 100;
+_assertPixel(canvas, 20,20, 0,0,0,0);
+
+
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.same.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.same.png
new file mode 100644
index 0000000000..eeedd0ff05
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.same.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.transform.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.transform.html
new file mode 100644
index 0000000000..36284ba498
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/initial.reset.transform.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: initial.reset.transform</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>initial.reset.transform</h1>
+<p class="desc">Resetting the canvas state resets the current transformation matrix</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="/images/green-100x50.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("Resetting the canvas state resets the current transformation matrix");
+_addTest(function(canvas, ctx) {
+
+canvas.width = 100;
+ctx.scale(0.1, 0.1);
+canvas.width = 100;
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+_assertPixel(canvas, 20,20, 0,255,0,255);
+
+
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.dataURI.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.dataURI.html
new file mode 100644
index 0000000000..93b560e82c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.dataURI.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.dataURI</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.dataURI</h1>
+<p class="desc">data: URIs do not count as different-origin, and do not taint the canvas</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="/images/green-100x50.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("data: URIs do not count as different-origin, and do not taint the canvas");
+_addTest(function(canvas, ctx) {
+
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+var data = canvas.toDataURL();
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+var img = new Image();
+deferTest();
+img.onload = t.step_func_done(function ()
+{
+ ctx.drawImage(img, 0, 0);
+ canvas.toDataURL(); // should be permitted
+ _assertPixel(canvas, 50,25, 0,255,0,255);
+});
+img.src = data;
+
+
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.canvas.cross.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.canvas.cross.html
new file mode 100644
index 0000000000..3a32cf2c16
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.canvas.cross.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.drawImage.canvas.cross</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.drawImage.canvas.cross</h1>
+<p class="desc">drawImage of unclean canvas makes the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("drawImage of unclean canvas makes the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+var canvas2 = document.createElement('canvas');
+canvas2.width = 100;
+canvas2.height = 50;
+var ctx2 = canvas2.getContext('2d');
+ctx2.drawImage(document.getElementById('yellow.png'), 0, 0);
+ctx.drawImage(canvas2, 0, 0);
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.canvas.redirect.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.canvas.redirect.html
new file mode 100644
index 0000000000..5545205837
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.canvas.redirect.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.drawImage.canvas.redirect</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.drawImage.canvas.redirect</h1>
+<p class="desc">drawImage of unclean canvas makes the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("drawImage of unclean canvas makes the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+var canvas2 = document.createElement('canvas');
+canvas2.width = 100;
+canvas2.height = 50;
+var ctx2 = canvas2.getContext('2d');
+ctx2.drawImage(document.getElementById('yellow.png'), 0, 0);
+ctx.drawImage(canvas2, 0, 0);
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginRedirectYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.image.cross.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.image.cross.html
new file mode 100644
index 0000000000..b58177edc5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.image.cross.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.drawImage.image.cross</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.drawImage.image.cross</h1>
+<p class="desc">drawImage of different-origin image makes the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("drawImage of different-origin image makes the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+ctx.drawImage(document.getElementById('yellow.png'), 0, 0);
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.image.redirect.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.image.redirect.html
new file mode 100644
index 0000000000..4661554f9b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.drawImage.image.redirect.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.drawImage.image.redirect</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.drawImage.image.redirect</h1>
+<p class="desc">drawImage of different-origin image makes the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("drawImage of different-origin image makes the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+ctx.drawImage(document.getElementById('yellow.png'), 0, 0);
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginRedirectYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.fillStyle.cross.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.fillStyle.cross.html
new file mode 100644
index 0000000000..35f0c70723
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.fillStyle.cross.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.canvas.fillStyle.cross</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.canvas.fillStyle.cross</h1>
+<p class="desc">Setting fillStyle to a pattern of an unclean canvas makes the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Setting fillStyle to a pattern of an unclean canvas makes the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+var canvas2 = document.createElement('canvas');
+canvas2.width = 100;
+canvas2.height = 50;
+var ctx2 = canvas2.getContext('2d');
+ctx2.drawImage(document.getElementById('yellow.png'), 0, 0);
+var p = ctx.createPattern(canvas2, 'repeat');
+ctx.fillStyle = p;
+ctx.fillStyle = 'red';
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.fillStyle.redirect.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.fillStyle.redirect.html
new file mode 100644
index 0000000000..d17be93233
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.fillStyle.redirect.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.canvas.fillStyle.redirect</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.canvas.fillStyle.redirect</h1>
+<p class="desc">Setting fillStyle to a pattern of an unclean canvas makes the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Setting fillStyle to a pattern of an unclean canvas makes the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+var canvas2 = document.createElement('canvas');
+canvas2.width = 100;
+canvas2.height = 50;
+var ctx2 = canvas2.getContext('2d');
+ctx2.drawImage(document.getElementById('yellow.png'), 0, 0);
+var p = ctx.createPattern(canvas2, 'repeat');
+ctx.fillStyle = p;
+ctx.fillStyle = 'red';
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginRedirectYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.strokeStyle.cross.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.strokeStyle.cross.html
new file mode 100644
index 0000000000..828becef8c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.strokeStyle.cross.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.canvas.strokeStyle.cross</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.canvas.strokeStyle.cross</h1>
+<p class="desc">Setting strokeStyle to a pattern of an unclean canvas makes the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Setting strokeStyle to a pattern of an unclean canvas makes the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+var canvas2 = document.createElement('canvas');
+canvas2.width = 100;
+canvas2.height = 50;
+var ctx2 = canvas2.getContext('2d');
+ctx2.drawImage(document.getElementById('yellow.png'), 0, 0);
+var p = ctx.createPattern(canvas2, 'repeat');
+ctx.strokeStyle = p;
+ctx.strokeStyle = 'red';
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.strokeStyle.redirect.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.strokeStyle.redirect.html
new file mode 100644
index 0000000000..c6e7a64c15
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.strokeStyle.redirect.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.canvas.strokeStyle.redirect</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.canvas.strokeStyle.redirect</h1>
+<p class="desc">Setting strokeStyle to a pattern of an unclean canvas makes the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Setting strokeStyle to a pattern of an unclean canvas makes the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+var canvas2 = document.createElement('canvas');
+canvas2.width = 100;
+canvas2.height = 50;
+var ctx2 = canvas2.getContext('2d');
+ctx2.drawImage(document.getElementById('yellow.png'), 0, 0);
+var p = ctx.createPattern(canvas2, 'repeat');
+ctx.strokeStyle = p;
+ctx.strokeStyle = 'red';
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginRedirectYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.timing.cross.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.timing.cross.html
new file mode 100644
index 0000000000..1ae1c4928b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.timing.cross.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.canvas.timing.cross</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.canvas.timing.cross</h1>
+<p class="desc">Pattern safety depends on whether the source was origin-clean, not on whether it still is clean</p>
+
+<p class="notes">Disagrees with spec on "is" vs "was"
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Pattern safety depends on whether the source was origin-clean, not on whether it still is clean");
+_addTest(function(canvas, ctx) {
+
+var canvas2 = document.createElement('canvas');
+canvas2.width = 100;
+canvas2.height = 50;
+var ctx2 = canvas2.getContext('2d');
+ctx2.fillStyle = '#0f0';
+ctx2.fillRect(0, 0, 100, 50);
+var p = ctx.createPattern(canvas2, 'repeat');
+ctx2.drawImage(document.getElementById('yellow.png'), 0, 0); // make canvas2 origin-unclean
+ctx.fillStyle = p;
+ctx.fillRect(0, 0, 100, 50);
+canvas.toDataURL();
+ctx.getImageData(0, 0, 1, 1);
+_assert(true, "true"); // okay if there was no exception
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.timing.redirect.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.timing.redirect.html
new file mode 100644
index 0000000000..f48366cd54
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.canvas.timing.redirect.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.canvas.timing.redirect</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.canvas.timing.redirect</h1>
+<p class="desc">Pattern safety depends on whether the source was origin-clean, not on whether it still is clean</p>
+
+<p class="notes">Disagrees with spec on "is" vs "was"
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Pattern safety depends on whether the source was origin-clean, not on whether it still is clean");
+_addTest(function(canvas, ctx) {
+
+var canvas2 = document.createElement('canvas');
+canvas2.width = 100;
+canvas2.height = 50;
+var ctx2 = canvas2.getContext('2d');
+ctx2.fillStyle = '#0f0';
+ctx2.fillRect(0, 0, 100, 50);
+var p = ctx.createPattern(canvas2, 'repeat');
+ctx2.drawImage(document.getElementById('yellow.png'), 0, 0); // make canvas2 origin-unclean
+ctx.fillStyle = p;
+ctx.fillRect(0, 0, 100, 50);
+canvas.toDataURL();
+ctx.getImageData(0, 0, 1, 1);
+_assert(true, "true"); // okay if there was no exception
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginRedirectYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.create.cross.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.create.cross.html
new file mode 100644
index 0000000000..e0d3d10556
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.create.cross.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.create.cross</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.create.cross</h1>
+<p class="desc">Creating an unclean pattern does not make the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Creating an unclean pattern does not make the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+var p = ctx.createPattern(document.getElementById('yellow.png'), 'repeat');
+canvas.toDataURL();
+ctx.getImageData(0, 0, 1, 1);
+_assert(true, "true"); // okay if there was no exception
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.create.redirect.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.create.redirect.html
new file mode 100644
index 0000000000..3fb7cf98b2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.create.redirect.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.create.redirect</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.create.redirect</h1>
+<p class="desc">Creating an unclean pattern does not make the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Creating an unclean pattern does not make the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+var p = ctx.createPattern(document.getElementById('yellow.png'), 'repeat');
+canvas.toDataURL();
+ctx.getImageData(0, 0, 1, 1);
+_assert(true, "true"); // okay if there was no exception
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginRedirectYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.cross.cross.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.cross.cross.html
new file mode 100644
index 0000000000..2dd14a87a9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.cross.cross.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.cross.cross</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.cross.cross</h1>
+<p class="desc">Using an unclean pattern makes the target canvas origin-unclean, not the pattern canvas</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Using an unclean pattern makes the target canvas origin-unclean, not the pattern canvas");
+_addTest(function(canvas, ctx) {
+
+var canvas2 = document.createElement('canvas');
+canvas2.width = 100;
+canvas2.height = 50;
+var ctx2 = canvas2.getContext('2d');
+var p = ctx2.createPattern(document.getElementById('yellow.png'), 'repeat');
+ctx.fillStyle = p;
+ctx.fillRect(0, 0, 100, 50);
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+canvas2.toDataURL();
+ctx2.getImageData(0, 0, 1, 1);
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.cross.redirect.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.cross.redirect.html
new file mode 100644
index 0000000000..8d69ea0afc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.cross.redirect.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.cross.redirect</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.cross.redirect</h1>
+<p class="desc">Using an unclean pattern makes the target canvas origin-unclean, not the pattern canvas</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Using an unclean pattern makes the target canvas origin-unclean, not the pattern canvas");
+_addTest(function(canvas, ctx) {
+
+var canvas2 = document.createElement('canvas');
+canvas2.width = 100;
+canvas2.height = 50;
+var ctx2 = canvas2.getContext('2d');
+var p = ctx2.createPattern(document.getElementById('yellow.png'), 'repeat');
+ctx.fillStyle = p;
+ctx.fillRect(0, 0, 100, 50);
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+canvas2.toDataURL();
+ctx2.getImageData(0, 0, 1, 1);
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginRedirectYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.fillStyle.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.fillStyle.sub.html
new file mode 100644
index 0000000000..a1053c04a4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.fillStyle.sub.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by tools/gentest.py. -->
+<title>Canvas test: security.pattern.canvas.fillStyle.cross</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/media.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<body>
+<p class="desc">Setting fillStyle to a pattern of an unclean canvas makes the canvas origin-unclean</p>
+
+<script>
+
+forEachCanvasSource(get_host_info().HTTP_REMOTE_ORIGIN,
+ get_host_info().HTTP_ORIGIN,
+ (name, factory) => {
+ promise_test(_ => {
+ return factory().then(source => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+ const pattern = ctx.createPattern(source, 'repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillStyle = 'red';
+ assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+ assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+ });
+ }, `${name}: Setting fillStyle to an origin-unclean pattern makes the canvas origin-unclean`);
+});
+
+forEachCanvasSource(get_host_info().HTTP_REMOTE_ORIGIN,
+ get_host_info().HTTP_ORIGIN,
+ (name, factory) => {
+ promise_test(_ => {
+ return factory().then(source => {
+ const pattern = new OffscreenCanvas(10, 10)
+ .getContext('2d')
+ .createPattern(source, 'repeat');
+
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+ ctx.fillStyle = pattern;
+ ctx.fillStyle = 'red';
+ assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+ assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+ });
+ }, `${name}: Setting fillStyle to an origin-unclean offscreen canvas pattern makes the canvas origin-unclean`);
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.fillStyle.cross.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.fillStyle.cross.html
new file mode 100644
index 0000000000..1d7dc1d84f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.fillStyle.cross.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.image.fillStyle.cross</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.image.fillStyle.cross</h1>
+<p class="desc">Setting fillStyle to a pattern of a different-origin image makes the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Setting fillStyle to a pattern of a different-origin image makes the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+var p = ctx.createPattern(document.getElementById('yellow.png'), 'repeat');
+ctx.fillStyle = p;
+ctx.fillStyle = 'red';
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.fillStyle.redirect.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.fillStyle.redirect.html
new file mode 100644
index 0000000000..3af917705b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.fillStyle.redirect.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.image.fillStyle.redirect</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.image.fillStyle.redirect</h1>
+<p class="desc">Setting fillStyle to a pattern of a different-origin image makes the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Setting fillStyle to a pattern of a different-origin image makes the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+var p = ctx.createPattern(document.getElementById('yellow.png'), 'repeat');
+ctx.fillStyle = p;
+ctx.fillStyle = 'red';
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginRedirectYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.strokeStyle.cross.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.strokeStyle.cross.html
new file mode 100644
index 0000000000..e35535af43
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.strokeStyle.cross.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.image.strokeStyle.cross</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.image.strokeStyle.cross</h1>
+<p class="desc">Setting strokeStyle to a pattern of a different-origin image makes the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Setting strokeStyle to a pattern of a different-origin image makes the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+var p = ctx.createPattern(document.getElementById('yellow.png'), 'repeat');
+ctx.strokeStyle = p;
+ctx.strokeStyle = 'red';
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.strokeStyle.redirect.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.strokeStyle.redirect.html
new file mode 100644
index 0000000000..09df15d24f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.pattern.image.strokeStyle.redirect.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.pattern.image.strokeStyle.redirect</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.pattern.image.strokeStyle.redirect</h1>
+<p class="desc">Setting strokeStyle to a pattern of a different-origin image makes the canvas origin-unclean</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Setting strokeStyle to a pattern of a different-origin image makes the canvas origin-unclean");
+_addTest(function(canvas, ctx) {
+
+var p = ctx.createPattern(document.getElementById('yellow.png'), 'repeat');
+ctx.strokeStyle = p;
+ctx.strokeStyle = 'red';
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+assert_throws_dom("SECURITY_ERR", function() { ctx.getImageData(0, 0, 1, 1); });
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginRedirectYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.reset.cross.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.reset.cross.html
new file mode 100644
index 0000000000..f823bbd8ac
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.reset.cross.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.reset.cross</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.reset.cross</h1>
+<p class="desc">Resetting the canvas state resets the origin-clean flag</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Resetting the canvas state resets the origin-clean flag");
+_addTest(function(canvas, ctx) {
+
+canvas.width = 50;
+ctx.drawImage(document.getElementById('yellow.png'), 0, 0);
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+canvas.width = 100;
+canvas.toDataURL();
+ctx.getImageData(0, 0, 1, 1);
+_assert(true, "true"); // okay if there was no exception
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.reset.redirect.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.reset.redirect.html
new file mode 100644
index 0000000000..af881c5fdc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/security.reset.redirect.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: security.reset.redirect</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>security.reset.redirect</h1>
+<p class="desc">Resetting the canvas state resets the origin-clean flag</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Resetting the canvas state resets the origin-clean flag");
+_addTest(function(canvas, ctx) {
+
+canvas.width = 50;
+ctx.drawImage(document.getElementById('yellow.png'), 0, 0);
+assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
+canvas.width = 100;
+canvas.toDataURL();
+ctx.getImageData(0, 0, 1, 1);
+_assert(true, "true"); // okay if there was no exception
+
+
+});
+</script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="data:text/javascript,addCrossOriginRedirectYellowImage()"></script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.default.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.default.html
new file mode 100644
index 0000000000..ecf35285a1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.default.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: size.attributes.default</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>size.attributes.default</h1>
+<p class="desc">Default width/height when attributes are missing</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" ><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="size.attributes.default.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("Default width/height when attributes are missing");
+_addTest(function(canvas, ctx) {
+
+_assertSame(canvas.width, 300, "canvas.width", "300");
+_assertSame(canvas.height, 150, "canvas.height", "150");
+_assert(!canvas.hasAttribute('width'), "!canvas.hasAttribute('width')");
+_assert(!canvas.hasAttribute('height'), "!canvas.hasAttribute('height')");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.default.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.default.png
new file mode 100644
index 0000000000..a72d047556
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.default.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.get.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.get.png
new file mode 100644
index 0000000000..47830c83ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.get.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.idl.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.idl.html
new file mode 100644
index 0000000000..1594a1c5e5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.idl.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: size.attributes.idl</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>size.attributes.idl</h1>
+<p class="desc">Getting/setting width/height IDL attributes</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Getting/setting width/height IDL attributes");
+_addTest(function(canvas, ctx) {
+
+canvas.width = "100";
+canvas.height = "100";
+_assertSame(canvas.width, 100, "canvas.width", "100");
+_assertSame(canvas.height, 100, "canvas.height", "100");
+
+canvas.width = "+1.5e2";
+canvas.height = "0x96";
+_assertSame(canvas.width, 150, "canvas.width", "150");
+_assertSame(canvas.height, 150, "canvas.height", "150");
+
+canvas.width = 200 - Math.pow(2, 32);
+canvas.height = 200 - Math.pow(2, 32);
+_assertSame(canvas.width, 200, "canvas.width", "200");
+_assertSame(canvas.height, 200, "canvas.height", "200");
+
+canvas.width = 301.999;
+canvas.height = 301.001;
+_assertSame(canvas.width, 301, "canvas.width", "301");
+_assertSame(canvas.height, 301, "canvas.height", "301");
+
+canvas.width = "400x";
+canvas.height = "foo";
+_assertSame(canvas.width, 0, "canvas.width", "0");
+_assertSame(canvas.height, 0, "canvas.height", "0");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.idl.set.zero.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.idl.set.zero.html
new file mode 100644
index 0000000000..c09d5cb278
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.idl.set.zero.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: size.attributes.idl.set.zero</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>size.attributes.idl.set.zero</h1>
+<p class="desc">Setting width/height IDL attributes to 0</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Setting width/height IDL attributes to 0");
+_addTest(function(canvas, ctx) {
+
+canvas.width = 0;
+canvas.height = 0;
+_assertSame(canvas.width, 0, "canvas.width", "0");
+_assertSame(canvas.height, 0, "canvas.height", "0");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setcontent.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setcontent.html
new file mode 100644
index 0000000000..a25c4b784a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setcontent.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: size.attributes.reflect.setcontent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>size.attributes.reflect.setcontent</h1>
+<p class="desc">Setting content attributes updates IDL and content attributes</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="size.attributes.reflect.setcontent.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("Setting content attributes updates IDL and content attributes");
+_addTest(function(canvas, ctx) {
+
+canvas.setAttribute('width', '120');
+canvas.setAttribute('height', '60');
+_assertSame(canvas.getAttribute('width'), '120', "canvas.getAttribute('width')", "'120'");
+_assertSame(canvas.getAttribute('height'), '60', "canvas.getAttribute('height')", "'60'");
+_assertSame(canvas.width, 120, "canvas.width", "120");
+_assertSame(canvas.height, 60, "canvas.height", "60");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setcontent.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setcontent.png
new file mode 100644
index 0000000000..47830c83ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setcontent.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setidl.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setidl.html
new file mode 100644
index 0000000000..e228276da7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setidl.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: size.attributes.reflect.setidl</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>size.attributes.reflect.setidl</h1>
+<p class="desc">Setting IDL attributes updates IDL and content attributes</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="size.attributes.reflect.setidl.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("Setting IDL attributes updates IDL and content attributes");
+_addTest(function(canvas, ctx) {
+
+canvas.width = 120;
+canvas.height = 60;
+_assertSame(canvas.getAttribute('width'), '120', "canvas.getAttribute('width')", "'120'");
+_assertSame(canvas.getAttribute('height'), '60', "canvas.getAttribute('height')", "'60'");
+_assertSame(canvas.width, 120, "canvas.width", "120");
+_assertSame(canvas.height, 60, "canvas.height", "60");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setidl.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setidl.png
new file mode 100644
index 0000000000..47830c83ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setidl.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setidlzero.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setidlzero.html
new file mode 100644
index 0000000000..65df3f9f94
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.reflect.setidlzero.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: size.attributes.reflect.setidlzero</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>size.attributes.reflect.setidlzero</h1>
+<p class="desc">Setting IDL attributes to 0 updates IDL and content attributes</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Setting IDL attributes to 0 updates IDL and content attributes");
+_addTest(function(canvas, ctx) {
+
+canvas.width = 0;
+canvas.height = 0;
+_assertSame(canvas.getAttribute('width'), '0', "canvas.getAttribute('width')", "'0'");
+_assertSame(canvas.getAttribute('height'), '0', "canvas.getAttribute('height')", "'0'");
+_assertSame(canvas.width, 0, "canvas.width", "0");
+_assertSame(canvas.height, 0, "canvas.height", "0");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.removed.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.removed.html
new file mode 100644
index 0000000000..c96cba7b17
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.removed.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: size.attributes.removed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>size.attributes.removed</h1>
+<p class="desc">Removing content attributes reverts to default size</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="120" height="60"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="size.attributes.removed.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("Removing content attributes reverts to default size");
+_addTest(function(canvas, ctx) {
+
+_assertSame(canvas.width, 120, "canvas.width", "120");
+canvas.removeAttribute('width');
+_assertSame(canvas.width, 300, "canvas.width", "300");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.removed.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.removed.png
new file mode 100644
index 0000000000..1ebf30d8aa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.removed.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.set.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.set.png
new file mode 100644
index 0000000000..47830c83ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.set.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.style.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.style.html
new file mode 100644
index 0000000000..aeb5c7ecb2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.style.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: size.attributes.style</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>size.attributes.style</h1>
+<p class="desc">Canvas size is independent of CSS resizing</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="50" height="30" style="width: 100px; height: 50px"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="size.attributes.style.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("Canvas size is independent of CSS resizing");
+_addTest(function(canvas, ctx) {
+
+_assertSame(canvas.width, 50, "canvas.width", "50");
+_assertSame(canvas.height, 30, "canvas.height", "30");
+
+
+});
+</script>
+
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.style.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.style.png
new file mode 100644
index 0000000000..eeedd0ff05
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/size.attributes.style.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob-cross-realm-callback-report-exception.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob-cross-realm-callback-report-exception.html
new file mode 100644
index 0000000000..393170baad
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob-cross-realm-callback-report-exception.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>toBlob() reports the exception from its callback in the callback's global object</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe srcdoc="<canvas></canvas>"></iframe>
+<iframe></iframe>
+<iframe></iframe>
+<script>
+setup({ allow_uncaught_exception: true });
+
+const onerrorCalls = [];
+window.onerror = () => { onerrorCalls.push("top"); };
+frames[0].onerror = () => { onerrorCalls.push("frame0"); };
+frames[1].onerror = () => { onerrorCalls.push("frame1"); };
+frames[2].onerror = () => { onerrorCalls.push("frame2"); };
+
+async_test(t => {
+ window.onload = t.step_func(() => {
+ const canvas = frames[0].document.querySelector("canvas");
+ canvas.toBlob(new frames[1].Function(`throw new parent.frames[2].Error("PASS");`));
+
+ t.step_timeout(() => {
+ assert_array_equals(onerrorCalls, ["frame1"]);
+ t.done();
+ }, 25);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob.jpeg.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob.jpeg.html
new file mode 100644
index 0000000000..1a95d4a6dd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob.jpeg.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Canvas test: toBlob.jpeg</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<canvas id="c"></canvas>
+<script>
+async_test(function() {
+ on_event(window, "load", this.step_func(function() {
+ var canvas = document.getElementById('c');
+ var ctx = canvas.getContext('2d');
+ canvas.toBlob(this.step_func_done(function(data) {
+ assert_equals(data.type, "image/jpeg");
+ }), 'image/jpeg');
+ }));
+}, "toBlob with image/jpeg returns a JPEG Blob");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob.null.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob.null.html
new file mode 100644
index 0000000000..11368a169c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob.null.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Canvas test: toBlob.null</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toBlob.null</h1>
+<p class="desc">toBlob with zero dimension returns a null Blob</p>
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="0"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+async_test(function() {
+ on_event(window, "load", this.step_func(function() {
+ var toBlobCalled = false;
+ c.toBlob(this.step_func(function(blob) {
+ toBlobCalled = true;
+ _assertSame(blob, null, "blob", "null");
+ c.width = 0;
+ c.height = 100;
+ c.toBlob(this.step_func_done(function(blob) {
+ _assertSame(blob, null, "blob", "null");
+ }), 'image/jpeg');
+ }), 'image/jpeg');
+ assert_false(toBlobCalled, "toBlob callback shouldn't be called synchronously");
+ }));
+}, "toBlob with zero dimension returns a null Blob");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob.png.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob.png.html
new file mode 100644
index 0000000000..1533bfdb6c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toBlob.png.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Canvas test: toBlob.png</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<canvas id="c"></canvas>
+<script>
+async_test(function() {
+ on_event(window, "load", this.step_func(function() {
+ var canvas = document.getElementById('c');
+ var ctx = canvas.getContext('2d');
+ canvas.toBlob(this.step_func_done(function(data) {
+ assert_equals(data.type, "image/png");
+ }), 'image/png');
+ }));
+}, "toBlob with image/png returns a PNG Blob");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.arguments.1.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.arguments.1.html
new file mode 100644
index 0000000000..8ed134c6b9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.arguments.1.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.arguments.1</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.arguments.1</h1>
+<p class="desc">toDataURL ignores extra arguments</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL ignores extra arguments");
+_addTest(function(canvas, ctx) {
+
+var data = canvas.toDataURL('image/png', 'another argument that should not raise an exception');
+assert_regexp_match(data, /^data:image\/png[;,]/);
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.arguments.2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.arguments.2.html
new file mode 100644
index 0000000000..5226c215f6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.arguments.2.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.arguments.2</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.arguments.2</h1>
+<p class="desc">toDataURL ignores extra arguments</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL ignores extra arguments");
+_addTest(function(canvas, ctx) {
+
+var data = canvas.toDataURL('image/png', 'another argument that should not raise an exception', 'and another');
+assert_regexp_match(data, /^data:image\/png[;,]/);
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.arguments.3.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.arguments.3.html
new file mode 100644
index 0000000000..23b3e33ed8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.arguments.3.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.arguments.3</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.arguments.3</h1>
+<p class="desc">toDataURL ignores extra arguments</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL ignores extra arguments");
+_addTest(function(canvas, ctx) {
+
+// More arguments that should not raise exceptions
+var data = canvas.toDataURL('image/png', null, null, null);
+assert_regexp_match(data, /^data:image\/png[;,]/);
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.bogustype.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.bogustype.html
new file mode 100644
index 0000000000..9b2414fc0b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.bogustype.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.bogustype</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.bogustype</h1>
+<p class="desc">toDataURL with a syntactically invalid type returns a PNG</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL with a syntactically invalid type returns a PNG");
+_addTest(function(canvas, ctx) {
+
+var data = canvas.toDataURL('bogus');
+assert_regexp_match(data, /^data:image\/png[;,]/);
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.default.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.default.html
new file mode 100644
index 0000000000..8bae384373
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.default.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.default</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.default</h1>
+<p class="desc">toDataURL with no arguments returns a PNG</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL with no arguments returns a PNG");
+_addTest(function(canvas, ctx) {
+
+var data = canvas.toDataURL();
+assert_regexp_match(data, /^data:image\/png[;,]/);
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.alpha.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.alpha.html
new file mode 100644
index 0000000000..daf278351d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.alpha.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.jpeg.alpha</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.jpeg.alpha</h1>
+<p class="desc">toDataURL with JPEG composites onto black</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="toDataURL.jpeg.alpha.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL with JPEG composites onto black");
+_addTest(function(canvas, ctx) {
+
+ctx.fillStyle = 'rgba(128, 255, 128, 0.5)';
+ctx.fillRect(0, 0, 100, 50);
+ctx.globalCompositeOperation = 'destination-over'; // should be ignored by toDataURL
+var data = canvas.toDataURL('image/jpeg');
+ctx.globalCompositeOperation = 'source-over';
+if (!data.match(/^data:image\/jpeg[;,]/)) {
+ _assert(true, "true");
+} else {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var img = new Image();
+ deferTest();
+ img.onload = t.step_func_done(function ()
+ {
+ ctx.drawImage(img, 0, 0);
+ _assertPixelApprox(canvas, 50,25, 63,127,63,255, 8);
+ });
+ img.src = data;
+}
+
+
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.alpha.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.alpha.png
new file mode 100644
index 0000000000..551871295c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.alpha.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.primarycolours.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.primarycolours.html
new file mode 100644
index 0000000000..750487bdea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.primarycolours.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.jpeg.primarycolours</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.jpeg.primarycolours</h1>
+<p class="desc">toDataURL with JPEG handles simple colours correctly</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="toDataURL.jpeg.primarycolours.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL with JPEG handles simple colours correctly");
+_addTest(function(canvas, ctx) {
+
+ctx.fillStyle = '#ff0';
+ctx.fillRect(0, 0, 25, 40);
+ctx.fillStyle = '#0ff';
+ctx.fillRect(25, 0, 50, 40);
+ctx.fillStyle = '#00f';
+ctx.fillRect(75, 0, 25, 40);
+ctx.fillStyle = '#fff';
+ctx.fillRect(0, 40, 100, 10);
+var data = canvas.toDataURL('image/jpeg'); // it is okay if this returns a PNG instead
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+var img = new Image();
+deferTest();
+img.onload = t.step_func_done(function ()
+{
+ ctx.drawImage(img, 0, 0);
+ _assertPixelApprox(canvas, 12,20, 255,255,0,255, 8);
+ _assertPixelApprox(canvas, 50,20, 0,255,255,255, 8);
+ _assertPixelApprox(canvas, 87,20, 0,0,255,255, 8);
+ _assertPixelApprox(canvas, 50,45, 255,255,255,255, 8);
+});
+img.src = data;
+
+
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.primarycolours.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.primarycolours.png
new file mode 100644
index 0000000000..cfd1369007
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.primarycolours.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.basic.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.basic.html
new file mode 100644
index 0000000000..dc5d814244
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.basic.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.jpeg.quality.basic</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.jpeg.quality.basic</h1>
+<p class="desc">toDataURL with JPEG uses the quality parameter</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="toDataURL.jpeg.quality.basic.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL with JPEG uses the quality parameter");
+_addTest(function(canvas, ctx) {
+
+ctx.fillStyle = '#00f';
+ctx.fillRect(0, 0, 100, 50);
+ctx.fillStyle = '#0ff';
+ctx.fillRect(0, 3, 100, 1);
+// Check for JPEG support first
+var data = canvas.toDataURL('image/jpeg');
+if (!data.match(/^data:image\/jpeg[;,]/)) {
+ _assert(true, "true");
+} else {
+ var data_hi = canvas.toDataURL('image/jpeg', 0.99);
+ var data_lo = canvas.toDataURL('image/jpeg', 0.01);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ deferTest();
+ var img_hi = new Image();
+ img_hi.onload = function ()
+ {
+ var img_lo = new Image();
+ img_lo.onload = t.step_func_done(function ()
+ {
+ ctx.drawImage(img_hi, 0, 0, 50, 50, 0, 0, 50, 50);
+ ctx.drawImage(img_lo, 0, 0, 50, 50, 50, 0, 50, 50);
+ _assert(data_hi.length > data_lo.length, "data_hi.length > data_lo.length");
+ _assertPixelApprox(canvas, 25,25, 0,0,255,255, 8);
+ _assertPixelApprox(canvas, 75,25, 0,0,255,255, 32);
+ });
+ img_lo.src = data_lo;
+ };
+ img_hi.src = data_hi;
+}
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.basic.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.basic.png
new file mode 100644
index 0000000000..2f8a0bc790
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.basic.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.notnumber.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.notnumber.html
new file mode 100644
index 0000000000..aa8066e67f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.notnumber.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.jpeg.quality.notnumber</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.jpeg.quality.notnumber</h1>
+<p class="desc">toDataURL with JPEG handles non-numeric quality parameters</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL with JPEG handles non-numeric quality parameters");
+_addTest(function(canvas, ctx) {
+
+ctx.fillStyle = '#00f';
+ctx.fillRect(0, 0, 100, 50);
+ctx.fillStyle = '#0ff';
+ctx.fillRect(0, 3, 100, 1);
+// Check for JPEG support first
+var data = canvas.toDataURL('image/jpeg');
+if (!data.match(/^data:image\/jpeg[;,]/)) {
+ _assert(true, "true");
+} else {
+ _assertSame(canvas.toDataURL('image/jpeg', 'bogus'), data, "canvas.toDataURL('image/jpeg', 'bogus')", "data");
+ _assertSame(canvas.toDataURL('image/jpeg', {}), data, "canvas.toDataURL('image/jpeg', {})", "data");
+ _assertSame(canvas.toDataURL('image/jpeg', null), data, "canvas.toDataURL('image/jpeg', null)", "data");
+ _assertSame(canvas.toDataURL('image/jpeg', undefined), data, "canvas.toDataURL('image/jpeg', undefined)", "data");
+ _assertSame(canvas.toDataURL('image/jpeg', true), data, "canvas.toDataURL('image/jpeg', true)", "data");
+ _assertSame(canvas.toDataURL('image/jpeg', '0.01'), data, "canvas.toDataURL('image/jpeg', '0.01')", "data");
+}
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.outsiderange.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.outsiderange.html
new file mode 100644
index 0000000000..9e40fb887b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpeg.quality.outsiderange.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.jpeg.quality.outsiderange</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.jpeg.quality.outsiderange</h1>
+<p class="desc">toDataURL with JPEG handles out-of-range quality parameters</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL with JPEG handles out-of-range quality parameters");
+_addTest(function(canvas, ctx) {
+
+ctx.fillStyle = '#00f';
+ctx.fillRect(0, 0, 100, 50);
+ctx.fillStyle = '#0ff';
+ctx.fillRect(0, 3, 100, 1);
+// Check for JPEG support first
+var data = canvas.toDataURL('image/jpeg');
+if (!data.match(/^data:image\/jpeg[;,]/)) {
+ _assert(true, "true");
+} else {
+ _assertSame(canvas.toDataURL('image/jpeg', 10), data, "canvas.toDataURL('image/jpeg', 10)", "data");
+ _assertSame(canvas.toDataURL('image/jpeg', -10), data, "canvas.toDataURL('image/jpeg', -10)", "data");
+ _assertSame(canvas.toDataURL('image/jpeg', 1.01), data, "canvas.toDataURL('image/jpeg', 1.01)", "data");
+ _assertSame(canvas.toDataURL('image/jpeg', -0.01), data, "canvas.toDataURL('image/jpeg', -0.01)", "data");
+
+ _assert(canvas.toDataURL('image/jpeg', 1).length >= canvas.toDataURL('image/jpeg', 0.9).length, "canvas.toDataURL('image/jpeg', 1).length >= canvas.toDataURL('image/jpeg', 0.9).length");
+ _assert(canvas.toDataURL('image/jpeg', 0).length <= canvas.toDataURL('image/jpeg', 0.1).length, "canvas.toDataURL('image/jpeg', 0).length <= canvas.toDataURL('image/jpeg', 0.1).length");
+}
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpg.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpg.html
new file mode 100644
index 0000000000..e59793db70
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.jpg.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.jpg</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.jpg</h1>
+<p class="desc">toDataURL with image/jpg is invalid type hence returns a PNG</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL with image/jpg is invalid type hence returns a PNG");
+_addTest(function(canvas, ctx) {
+
+var data = canvas.toDataURL('image/jpg');
+assert_regexp_match(data, /^data:image\/png[;,]/);
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.lowercase.ascii.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.lowercase.ascii.html
new file mode 100644
index 0000000000..858035cc64
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.lowercase.ascii.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.lowercase.ascii</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.lowercase.ascii</h1>
+<p class="desc">toDataURL type is case-insensitive</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL type is case-insensitive");
+_addTest(function(canvas, ctx) {
+
+var data = canvas.toDataURL('ImAgE/PnG');
+assert_regexp_match(data, /^data:image\/png[;,]/);
+
+// If JPEG is supported at all, it must be supported case-insensitively
+data = canvas.toDataURL('image/jpeg');
+if (data.match(/^data:image\/jpeg[;,]/)) {
+ data = canvas.toDataURL('ImAgE/JpEg');
+ assert_regexp_match(data, /^data:image\/jpeg[;,]/);
+}
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.lowercase.unicode.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.lowercase.unicode.html
new file mode 100644
index 0000000000..123f966ee2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.lowercase.unicode.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.lowercase.unicode</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.lowercase.unicode</h1>
+<p class="desc">toDataURL type is ASCII-case-insensitive</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL type is ASCII-case-insensitive");
+_addTest(function(canvas, ctx) {
+
+// Use LATIN CAPITAL LETTER I WITH DOT ABOVE (Unicode lowercase is "i")
+var data = canvas.toDataURL('\u0130mage/png');
+assert_regexp_match(data, /^data:image\/png[;,]/);
+
+var data = canvas.toDataURL('\u0130mage/jpeg');
+assert_regexp_match(data, /^data:image\/png[;,]/);
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.nocontext.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.nocontext.html
new file mode 100644
index 0000000000..704dc74fb9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.nocontext.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.nocontext</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.nocontext</h1>
+<p class="desc">toDataURL works before any context has been got</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL works before any context has been got");
+_addTest(function(canvas, ctx) {
+
+var no_context_data = canvas.toDataURL();
+var ctx = canvas.getContext('2d');
+ctx.rect(0, 0, 100, 50);
+ctx.fillStyle = "rgba(0, 0, 0, 0)";
+ctx.fill();
+var data = canvas.toDataURL();
+assert_equals(no_context_data, data);
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.complexcolours.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.complexcolours.html
new file mode 100644
index 0000000000..dadea7c5b0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.complexcolours.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.png.complexcolours</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.png.complexcolours</h1>
+<p class="desc">toDataURL with PNG handles non-primary and non-solid colours correctly</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="toDataURL.png.complexcolours.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL with PNG handles non-primary and non-solid colours correctly");
+_addTest(function(canvas, ctx) {
+
+// (These values are chosen to survive relatively alright through being premultiplied)
+ctx.fillStyle = 'rgba(1, 3, 254, 1)';
+ctx.fillRect(0, 0, 25, 25);
+ctx.fillStyle = 'rgba(8, 252, 248, 0.75)';
+ctx.fillRect(25, 0, 25, 25);
+ctx.fillStyle = 'rgba(6, 10, 250, 0.502)';
+ctx.fillRect(50, 0, 25, 25);
+ctx.fillStyle = 'rgba(12, 16, 244, 0.25)';
+ctx.fillRect(75, 0, 25, 25);
+var img = new Image();
+deferTest();
+img.onload = t.step_func_done(function ()
+{
+ ctx.drawImage(img, 0, 25);
+ // (The alpha values do not really survive float->int conversion, so just
+ // do approximate comparisons)
+ _assertPixel(canvas, 12,40, 1,3,254,255);
+ _assertPixelApprox(canvas, 37,40, 8,252,248,191, 2);
+ _assertPixelApprox(canvas, 62,40, 6,10,250,127, 4);
+ _assertPixelApprox(canvas, 87,40, 12,16,244,63, 8);
+});
+img.src = canvas.toDataURL();
+
+
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.complexcolours.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.complexcolours.png
new file mode 100644
index 0000000000..b5f9c118aa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.complexcolours.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.html
new file mode 100644
index 0000000000..26c92a45a7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.png</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.png</h1>
+<p class="desc">toDataURL with image/png returns a PNG</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL with image/png returns a PNG");
+_addTest(function(canvas, ctx) {
+
+var data = canvas.toDataURL('image/png');
+assert_regexp_match(data, /^data:image\/png[;,]/);
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.primarycolours.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.primarycolours.html
new file mode 100644
index 0000000000..a13850d54e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.primarycolours.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.png.primarycolours</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.png.primarycolours</h1>
+<p class="desc">toDataURL with PNG handles simple colours correctly</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<p class="output expectedtext">Expected output:<p><img src="toDataURL.png.primarycolours.png" class="output expected" id="expected" alt="">
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL with PNG handles simple colours correctly");
+_addTest(function(canvas, ctx) {
+
+ctx.fillStyle = '#ff0';
+ctx.fillRect(0, 0, 25, 40);
+ctx.fillStyle = '#0ff';
+ctx.fillRect(25, 0, 50, 40);
+ctx.fillStyle = '#00f';
+ctx.fillRect(75, 0, 25, 40);
+ctx.fillStyle = '#fff';
+ctx.fillRect(0, 40, 100, 10);
+var data = canvas.toDataURL();
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+var img = new Image();
+deferTest();
+img.onload = t.step_func_done(function ()
+{
+ ctx.drawImage(img, 0, 0);
+ _assertPixel(canvas, 12,20, 255,255,0,255);
+ _assertPixel(canvas, 50,20, 0,255,255,255);
+ _assertPixel(canvas, 87,20, 0,0,255,255);
+ _assertPixel(canvas, 50,45, 255,255,255,255);
+});
+img.src = data;
+
+
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.primarycolours.png b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.primarycolours.png
new file mode 100644
index 0000000000..cfd1369007
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.png.primarycolours.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.unrecognised.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.unrecognised.html
new file mode 100644
index 0000000000..835a898027
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.unrecognised.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.unrecognised</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.unrecognised</h1>
+<p class="desc">toDataURL with an unhandled type returns a PNG</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL with an unhandled type returns a PNG");
+_addTest(function(canvas, ctx) {
+
+var data = canvas.toDataURL('image/example');
+assert_regexp_match(data, /^data:image\/png[;,]/);
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.zeroheight.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.zeroheight.html
new file mode 100644
index 0000000000..9b09beb6d6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.zeroheight.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.zeroheight</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.zeroheight</h1>
+<p class="desc">toDataURL on zero-size canvas returns 'data:,'</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" height="0"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL on zero-size canvas returns 'data:,'");
+_addTest(function(canvas, ctx) {
+
+var data = canvas.toDataURL();
+_assertSame(data, 'data:,', "data", "'data:,'");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.zerosize.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.zerosize.html
new file mode 100644
index 0000000000..4468104268
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.zerosize.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.zerosize</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.zerosize</h1>
+<p class="desc">toDataURL on zero-size canvas returns 'data:,'</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="0" height="0"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL on zero-size canvas returns 'data:,'");
+_addTest(function(canvas, ctx) {
+
+var data = canvas.toDataURL();
+_assertSame(data, 'data:,', "data", "'data:,'");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.zerowidth.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.zerowidth.html
new file mode 100644
index 0000000000..9ab3524352
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/toDataURL.zerowidth.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: toDataURL.zerowidth</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>toDataURL.zerowidth</h1>
+<p class="desc">toDataURL on zero-size canvas returns 'data:,'</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="0"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("toDataURL on zero-size canvas returns 'data:,'");
+_addTest(function(canvas, ctx) {
+
+var data = canvas.toDataURL();
+_assertSame(data, 'data:,', "data", "'data:,'");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.delete.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.delete.html
new file mode 100644
index 0000000000..7fd54b30d5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.delete.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: type.delete</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>type.delete</h1>
+<p class="desc">window.HTMLCanvasElement interface object is [[Configurable]]</p>
+
+<p class="notes">Defined in "Web IDL" (draft)
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("window.HTMLCanvasElement interface object is [[Configurable]]");
+_addTest(function(canvas, ctx) {
+
+_assertSame(delete window.HTMLCanvasElement, true, "delete window.HTMLCanvasElement", "true");
+_assertSame(window.HTMLCanvasElement, undefined, "window.HTMLCanvasElement", "undefined");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.exists.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.exists.html
new file mode 100644
index 0000000000..26f59a1614
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.exists.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: type.exists</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>type.exists</h1>
+<p class="desc">HTMLCanvasElement is a property of window</p>
+
+<p class="notes">Defined in "Web IDL" (draft)
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("HTMLCanvasElement is a property of window");
+_addTest(function(canvas, ctx) {
+
+_assert(window.HTMLCanvasElement, "window.HTMLCanvasElement");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.extend.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.extend.html
new file mode 100644
index 0000000000..e17209f455
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.extend.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: type.extend</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>type.extend</h1>
+<p class="desc">HTMLCanvasElement methods can be added, and the new methods used by canvases</p>
+
+<p class="notes">Defined in "Web IDL" (draft)
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("HTMLCanvasElement methods can be added, and the new methods used by canvases");
+_addTest(function(canvas, ctx) {
+
+window.HTMLCanvasElement.prototype.getZero = function () { return 0; };
+_assertSame(canvas.getZero(), 0, "canvas.getZero()", "0");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.name.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.name.html
new file mode 100644
index 0000000000..fdf1d1d398
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.name.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: type.name</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>type.name</h1>
+<p class="desc">HTMLCanvasElement type and toString</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("HTMLCanvasElement type and toString");
+_addTest(function(canvas, ctx) {
+
+_assertSame(Object.prototype.toString.call(canvas), '[object HTMLCanvasElement]', "Object.prototype.toString.call(canvas)", "'[object HTMLCanvasElement]'");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.prototype.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.prototype.html
new file mode 100644
index 0000000000..f47f755388
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.prototype.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: type.prototype</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>type.prototype</h1>
+<p class="desc">window.HTMLCanvasElement has prototype, which is { ReadOnly, DontDelete }. prototype has getContext, which is not</p>
+
+<p class="notes">Defined in "Web IDL" (draft)
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("window.HTMLCanvasElement has prototype, which is { ReadOnly, DontDelete }. prototype has getContext, which is not");
+_addTest(function(canvas, ctx) {
+
+_assert(window.HTMLCanvasElement.prototype, "window.HTMLCanvasElement.prototype");
+_assert(window.HTMLCanvasElement.prototype.getContext, "window.HTMLCanvasElement.prototype.getContext");
+window.HTMLCanvasElement.prototype = null;
+_assert(window.HTMLCanvasElement.prototype, "window.HTMLCanvasElement.prototype");
+delete window.HTMLCanvasElement.prototype;
+_assert(window.HTMLCanvasElement.prototype, "window.HTMLCanvasElement.prototype");
+window.HTMLCanvasElement.prototype.getContext = 1;
+_assertSame(window.HTMLCanvasElement.prototype.getContext, 1, "window.HTMLCanvasElement.prototype.getContext", "1");
+delete window.HTMLCanvasElement.prototype.getContext;
+_assertSame(window.HTMLCanvasElement.prototype.getContext, undefined, "window.HTMLCanvasElement.prototype.getContext", "undefined");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.replace.html b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.replace.html
new file mode 100644
index 0000000000..e67fe7c4a2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-canvas-element/type.replace.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: type.replace</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>type.replace</h1>
+<p class="desc">HTMLCanvasElement methods can be replaced, and the replacement methods used by canvases</p>
+
+<p class="notes">Defined in "Web IDL" (draft)
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("HTMLCanvasElement methods can be replaced, and the replacement methods used by canvases");
+_addTest(function(canvas, ctx) {
+
+window.HTMLCanvasElement.prototype.getContext = function (name) { return 0; };
+_assertSame(canvas.getContext('2d'), 0, "canvas.getContext('2d')", "0");
+
+
+});
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/document-getters-return-null-for-cross-origin.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/document-getters-return-null-for-cross-origin.html
new file mode 100644
index 0000000000..89dc5d6d95
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/document-getters-return-null-for-cross-origin.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test that getSVGDocument() returns null for a cross-origin document.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<embed src='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect height="100" width="100"/></svg>'></embed>
+<script>
+const embed = document.querySelector('embed');
+var t = async_test('HTMLEmbedElement.getSVGDocument() for cross-origin document');
+window.addEventListener(
+ 'load', t.step_func_done(() => { assert_equals(embed.getSVGDocument(), null); }));
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-change-src.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-change-src.html
new file mode 100644
index 0000000000..4152d51b0c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-change-src.html
@@ -0,0 +1,26 @@
+<!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/iframe-embed-object.html#the-embed-element">
+<link rel="help" href="http://crbug.com/1035330">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const embed = document.createElement('embed');
+ let loadPromise = new Promise(resolve => embed.onload = resolve);
+ embed.src = '/media/white.mp4';
+ document.body.appendChild(embed);
+
+ await loadPromise;
+
+ loadPromise = new Promise(resolve => embed.onload = resolve);
+ embed.src = '/media/white.webm';
+
+ await loadPromise;
+}, 'Verifies that embed elements reload with new content when the src attribute is changed.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-dimension.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-dimension.html
new file mode 100644
index 0000000000..d65e39c750
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-dimension.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: dimension</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-embed-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<embed src="/images/blue.png" height="100" width="100" id="test">
+<script>
+ test(function () {
+ var height = getComputedStyle(document.getElementById("test"))["height"];
+ assert_equals(height, "100px", "The height of the embed element should be 100px.");
+ }, "Check the actual length of the embed element's height");
+
+ test(function () {
+ var width = getComputedStyle(document.getElementById("test"))["width"];
+ assert_equals(width, "100px", "The width of the embed element should be 100px.");
+ }, "Check the actual length of the embed element's width");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-document-under-content-visibility-focus.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-document-under-content-visibility-focus.html
new file mode 100644
index 0000000000..26a77918e0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-document-under-content-visibility-focus.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>HTML Test: The embed element represents a document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<meta name="assert" content="Ensure document finishes load when focus is attempted before the embed is finished loading">
+
+<style>
+.hidden { content-visibility: hidden; }
+</style>
+<body>
+ <script>
+ async_test(t => {
+ window.onload = () => t.done();
+ }, "ensure onload happens");
+ </script>
+ <div class=hidden>
+ <embed id=target src="embed-iframe.html">
+ </div>
+ <script>
+ // Ensure we process style in the hidden object, which normally delays
+ // load until the embed object is finished loading.
+ target.focus();
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-document-under-content-visibility-gbcr.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-document-under-content-visibility-gbcr.html
new file mode 100644
index 0000000000..074e63bf2b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-document-under-content-visibility-gbcr.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>HTML Test: The embed element represents a document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<meta name="assert" content="Ensure document finishes load when getBoundingClientRect is attempted before the embed is finished loading">
+
+<style>
+.hidden { content-visibility: hidden; }
+</style>
+<body>
+ <script>
+ async_test(t => {
+ window.onload = () => t.done();
+ }, "ensure onload happens");
+ </script>
+ <div class=hidden>
+ <embed id=target src="embed-iframe.html">
+ </div>
+ <script>
+ // Ensure we process style and layout in the hidden object.
+ target.getBoundingClientRect();
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-document.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-document.html
new file mode 100644
index 0000000000..3d44678cf1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-document.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>HTML Test: The embed element represents a document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<meta name="assert" content="Check if the embed element represents a document when a text/html resource source is used">
+<body>
+ <script type="application/javascript">
+ window.childLoaded = false;
+ async_test(function() {
+ addEventListener("load", this.step_func_done(function() {
+ assert_true(window.childLoaded);
+ }));
+ }, "Test document type embedding");
+ </script>
+ <embed src="embed-iframe.html">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-hidden-attribute-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-hidden-attribute-ref.html
new file mode 100644
index 0000000000..4b3d0feceb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-hidden-attribute-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>The hidden global presentation attribute within the embed element</title>
+
+This embed should be visible (green box):
+<embed src='../../../../images/green-256x256.png' type='image/png'></embed>
+
+These should not be visible (no red):
+
+<style>
+ embed {
+ display:block;
+ }
+</style>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-hidden-attribute.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-hidden-attribute.html
new file mode 100644
index 0000000000..18d3897df9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-hidden-attribute.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>The hidden global presentation attribute within the embed element</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=match href="embed-hidden-attribute-ref.html">
+
+This embed should be visible (green box):
+<embed src='../../../../images/green-256x256.png' type='image/png'></embed>
+
+These should not be visible (no red):
+<embed hidden src='../../../../images/fail.gif' type='image/png'></embed>
+<embed hidden="" src='../../../../images/fail.gif' type='image/png'></embed>
+<embed hidden="hidden" src='../../../../images/fail.gif' type='image/png'></embed>
+<embed hidden="true" src='../../../../images/fail.gif' type='image/png'></embed>
+<embed hidden="yes" src='../../../../images/fail.gif' type='image/png'></embed>
+
+<style>
+embed {
+ display:block;
+}
+</style>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-iframe.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-iframe.html
new file mode 100644
index 0000000000..f9b1bfdb57
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-iframe.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<body>
+ <script type="application/javascript">
+ parent.childLoaded = true;
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-ignored-in-media-element.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-ignored-in-media-element.html
new file mode 100644
index 0000000000..941b0c0b77
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-ignored-in-media-element.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>HTML Test: The embed element represents a document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<meta name="assert" content="Check if the embed element is ignored when used inside a media element">
+<script type="application/javascript">
+ var nestingTest = async_test("Test embed being ignored inside media element");
+ onload = nestingTest.step_func_done(function() {
+ assert_true(true, "We got to a load event without loading things we should not load");
+ });
+</script>
+<body>
+ <video>
+ <embed type="text/html" src="../resources/should-not-load.html"
+ test-description="<embed> in <video>">
+ </video>
+ <audio>
+ <embed type="text/html" src="../resources/should-not-load.html"
+ test-description="<embed> in <audio>">
+ </audio>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-in-object-fallback-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-in-object-fallback-2.html
new file mode 100644
index 0000000000..d904a7e1a1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-in-object-fallback-2.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title></title>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script>
+ var loadedCount = 0;
+ var nestingTest = async_test("Test <embed> nesting inside <object>");
+ onload = nestingTest.step_func_done(function() {
+ assert_equals(loadedCount, 12, "Should have loaded all should-load elements");
+ });
+ </script>
+ <style>
+ object, embed { display: none }
+ </style>
+ </head>
+ <body>
+ <object data="../resources/should-load.html" style="width: 100px; height: 100px">
+ <embed type="text/html" src="../resources/should-not-load.html"
+ test-description="<embed> inside <object>">
+ </object>
+ <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test">
+ <embed type="text/html" src="../resources/should-load.html" />
+ </object>
+ <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test">
+ <div></div>
+ <embed type="text/html" src="../resources/should-load.html" />
+ </object>
+ <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test">
+ <div>
+ <embed type="text/html" src="../resources/should-load.html" />
+ </div>
+ </object>
+ <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test">
+ <embed type="text/html" src="../resources/should-load.html" />
+ <embed type="text/html" src="../resources/should-load.html" />
+ <object data="../resources/should-load.html">
+ <embed type="text/html" src="../resources/should-not-load.html"
+ test-description="<embed> inside loaded <object> inside non-loaded <object>">
+ </object>
+ <object data="data:application/x-does-not-exist,test">
+ <embed type="text/html" src="../resources/should-load.html" />
+ </object>
+ </object>
+ <div>
+ <object data="../resources/should-load.html" style="width: 100px; height: 100px"></object>
+ <embed type="text/html" src="../resources/should-load.html" />
+ </div>
+ <div>
+ <embed type="text/html" src="../resources/should-load.html" />
+ <object data="../resources/should-load.html" style="width: 100px; height: 100px"></object>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-in-object-fallback-subdocument.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-in-object-fallback-subdocument.html
new file mode 100644
index 0000000000..a2c2a93992
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-in-object-fallback-subdocument.html
@@ -0,0 +1,4 @@
+<script>
+ var varName = location.search.substr(1);
+ parent[varName] = true;
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-in-object-fallback.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-in-object-fallback.html
new file mode 100644
index 0000000000..52fa01b91a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-in-object-fallback.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Ensure that embed elements inside object elements load when the objects
+ fall back but not otherwise</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ var child1Loaded = false;
+ var child2Loaded = false;
+ var child3Loaded = false;
+ var parent3Loaded = false;
+</script>
+<object>
+ <embed src="embed-in-object-fallback-subdocument.html?child1Loaded">
+</object>
+<object>
+ <embed id="two" src="embed-in-object-fallback-subdocument.html?child2Loaded">
+ <!-- Something that forces the embed to be in the tree before the <object>
+ is done parsing -->
+ <script>
+ test(function() {
+ assert_equals(document.getElementById("two").localName,
+ "embed");
+ }, "We have the right embed element");
+ </script>
+</object>
+<object data="embed-in-object-fallback-subdocument.html?parent3Loaded">
+ <embed src="embed-in-object-fallback-subdocument.html?child3Loaded">
+</object>
+<script>
+ var t = async_test("Check that the right things loaded");
+ onload = t.step_func_done(function() {
+ assert_true(child1Loaded, "child 1 should load");
+ assert_true(child2Loaded, "child 2 should load");
+ assert_false(child3Loaded, "child 3 should not load");
+ assert_true(parent3Loaded, "parent 3 should load");
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-named-attribute-detached-context-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-named-attribute-detached-context-crash.html
new file mode 100644
index 0000000000..7445b49f0f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-named-attribute-detached-context-crash.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<iframe id="i"></iframe>
+<script>
+let e = i.contentDocument.createElement("embed");
+i.contentDocument.body.appendChild(e);
+i.remove();
+
+// These should not crash.
+e.tryToGetAnAttribute;
+e.tryToSetAnAttribute = "foo";
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-network-error.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-network-error.sub.html
new file mode 100644
index 0000000000..bae995c101
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-network-error.sub.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Network errors with embed elements</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+async_test(t => {
+ const embed = document.createElement("embed");
+ embed.src = "//{{hosts[][nonexistent]}}/";
+ embed.onload = () => t.done();
+ embed.onerror = t.unreached_func("error event must not fire");
+ document.body.append(embed);
+}, "new embed: nonexistent host");
+
+async_test(t => {
+ const embed = document.createElement("embed");
+ embed.src = "../resources/not-embeddable.html";
+ embed.onload = () => t.done();
+ embed.onerror = t.unreached_func("error event must not fire");
+ document.body.append(embed);
+}, "new embed: X-Frame-Options prevents embedding");
+
+async_test(t => {
+ const embed = document.createElement("embed");
+ embed.src = "/common/blank.html";
+ embed.name = "existingEmbed1";
+ embed.onload = t.step_func(() => {
+ embed.onload = () => t.done();
+ embed.onerror = t.unreached_func("error event must not fire 2");
+
+ frames.existingEmbed1.location.href = "//{{hosts[][nonexistent]}}/";
+ });
+ embed.onerror = t.unreached_func("error event must not fire 1");
+ document.body.append(embed);
+}, "navigating an existing embed: nonexistent host");
+
+async_test(t => {
+ const embed = document.createElement("embed");
+ embed.src = "/common/blank.html";
+ embed.name = "existingEmbed2";
+ embed.onload = t.step_func(() => {
+ embed.onload = () => t.done();
+ embed.onerror = t.unreached_func("error event must not fire 2");
+
+ frames.existingEmbed2.location.href = "../resources/not-embeddable.html";
+ });
+ embed.onerror = t.unreached_func("error event must not fire 1");
+ document.body.append(embed);
+}, "navigating an existing embed: X-Frame-Options prevents embedding");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-01.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-01.html
new file mode 100644
index 0000000000..e66bd4a906
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-01.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: The embed element without src and type attributes represents nothing</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-embed-element">
+<link rel="match" href="embed-represent-nothing-ref.html">
+<meta name="assert" content="Check if the embed element without src and type attributes represents nothing">
+<style>
+ embed {
+ background-color: red;
+ height: 100px;
+ width: 100px;
+ }
+</style>
+<body>
+ <p>Test passes if there is <strong>no red</strong>.</p>
+ <embed>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-02.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-02.html
new file mode 100644
index 0000000000..65cd672387
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-02.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: The embed element represents nothing when its type and src attributs are removed</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-embed-element">
+<link rel="match" href="embed-represent-nothing-ref.html">
+<meta name="assert" content="Check if the embed element represents nothing when its src and type attributes are removed">
+<style>
+ embed {
+ background-color: red;
+ height: 100px;
+ width: 100px;
+ }
+</style>
+<body>
+ <p>Test passes if there is <strong>no red</strong>.</p>
+ <embed id="embed" src="/images/red-16x16.png" type="image/png">
+ <script>
+ document.getElementById("embed").removeAttribute("src");
+ document.getElementById("embed").removeAttribute("type");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-03.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-03.html
new file mode 100644
index 0000000000..a16f3794ae
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-03.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: The embed element represents nothing when it has a media ancestor</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-embed-element">
+<link rel="match" href="embed-represent-nothing-ref.html">
+<meta name="assert" content="Check if the embed element represents nothing when it has a media ancestor">
+<style>
+ embed {
+ background-color: red;
+ height: 100px;
+ width: 100px;
+ }
+</style>
+<body>
+ <p>Test passes if there is <strong>no red</strong>.</p>
+ <video>
+ <embed src="/images/red-16x16.png">
+ </video>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-04.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-04.html
new file mode 100644
index 0000000000..7cc1b668a6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-04.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: The embed element represents nothing when it has an object ancestor</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-embed-element">
+<link rel="match" href="embed-represent-nothing-ref.html">
+<meta name="assert" content="Check if the embed element represents nothing when it has a object ancestor that is not showing its fallback content">
+<style>
+ embed {
+ background-color: red;
+ height: 100px;
+ width: 100px;
+ }
+</style>
+<body>
+ <p>Test passes if there is <strong>no red</strong>.</p>
+ <object type="application/x-shockwave-flash">
+ <embed src="/images/red-16x16.png">
+ </object>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-ref.html
new file mode 100644
index 0000000000..91d680debf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Embed Reftest Reference</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<body>
+ <p>Test passes if there is <strong>no red</strong>.</p>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-svg-navigation-resets-size.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-svg-navigation-resets-size.html
new file mode 100644
index 0000000000..237c9c3646
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/embed-svg-navigation-resets-size.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Intrinsic sizing of SVG embedded via &lt;embed&gt; should disappear on navigation</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<embed id="element" name="embed" src='data:image/svg+xml,
+ <svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ viewBox="0 0 1000 1000"
+ width="400"
+ height="400"
+ font-size="100">
+ </svg>'></embed>
+<script>
+promise_test(async () => {
+ await new Promise(resolve => {
+ onload = resolve;
+ });
+
+ assert_equals(element.scrollWidth, 400, "The width should be determined by the embedded SVG");
+ assert_equals(element.scrollHeight, 400, "The height should be determined by the embedded SVG");
+
+ await new Promise(resolve => {
+ element.onload = resolve;
+ element.src = "about:blank";
+ });
+
+ assert_equals(element.scrollWidth, 300, "The width should be the default");
+ assert_equals(element.scrollHeight, 150, "The height should be the default");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/historical.html b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/historical.html
new file mode 100644
index 0000000000..9df7280bb6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-embed-element/historical.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>Historical embed element features should not be supported</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<embed id=embed>
+<script>
+test(function() {
+ var elm = document.getElementById('embed');
+ assert_equals(typeof elm, 'object', 'typeof');
+ assert_throws_js(TypeError, function() {
+ elm();
+ });
+}, 'embed legacycaller should not be supported');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-frame-element/document-getters-return-null-for-cross-origin.html b/testing/web-platform/tests/html/semantics/embedded-content/the-frame-element/document-getters-return-null-for-cross-origin.html
new file mode 100644
index 0000000000..2628e91009
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-frame-element/document-getters-return-null-for-cross-origin.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test that contentDocument returns null for a cross-origin document.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test('HTMLFrameElement.contentDocument for cross-origin document');
+window.addEventListener(
+ 'load', t.step_func_done(() => { assert_equals(document.querySelector('frame').contentDocument, null); }));
+</script>
+<frameset>
+<frame src='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect height="100" width="100"/></svg>'></frame>
+</frameset>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/change_child.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/change_child.html
new file mode 100644
index 0000000000..738ceee529
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/change_child.html
@@ -0,0 +1,14 @@
+<body>
+ Child.
+ <iframe id="grandchild" src="change_grandchild.html"></iframe>
+</body>
+<script>
+ var timer = window.setInterval(poll, 100);
+ function poll() {
+ if (document.body.getAttribute("data-contains-grandchild")) {
+ var grandchild = document.getElementById("grandchild");
+ window.frameElement.parentNode.appendChild(grandchild);
+ window.clearTimeout(timer);
+ }
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/change_grandchild.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/change_grandchild.html
new file mode 100644
index 0000000000..885622c2b4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/change_grandchild.html
@@ -0,0 +1,4 @@
+<body>Grandchild.</body>
+<script>
+ window.frameElement.parentNode.setAttribute("data-contains-grandchild", true);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/change_parentage.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/change_parentage.html
new file mode 100644
index 0000000000..1d62ccc480
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/change_parentage.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Change the frame heriarchy</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+ <iframe src="change_child.html"></iframe>
+</body>
+<script>
+ async_test(function(t) {
+ var timer = window.setInterval(t.step_func(poll), 100);
+ function poll() {
+ // We wait for the grandchild's script to set the custom attribtue.
+ // Note that if this test passes, the grandchild's script must have been run twice,
+ // once to trigger the move from the child to here, and once to pass this test.
+ if (document.body.getAttribute("data-contains-grandchild")) {
+ window.clearTimeout(timer);
+ t.done();
+ }
+ }
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/content_document_changes_only_after_load_matures.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/content_document_changes_only_after_load_matures.html
new file mode 100644
index 0000000000..b657f26158
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/content_document_changes_only_after_load_matures.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Iframe's contentDocument should only change after its pending load has matured.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body></body>
+<script>
+async_test(function(t) {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ var checkedDuringParse = false;
+ iframe.onload = t.step_func_done(function() {
+ testContentDocument();
+ assert_true(checkedDuringParse);
+ });
+
+ let url = "support/iframe-that-checks-contentDocument.html";
+ window.testContentDocument = t.step_func(function() {
+ assert_true(iframe.contentDocument.location.toString().includes(url));
+ checkedDuringParse = true;
+ });
+
+ assert_equals(iframe.contentDocument.location.toString(), "about:blank");
+ iframe.src = url + "?pipe=trickle(d2)";
+ // The location of the contentDocument should not change until the new document has matured.
+ assert_equals(iframe.contentDocument.location.toString(), "about:blank");
+}, "contentDocument should only change after a load matures.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross-origin-to-whom-part-2.window.js b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross-origin-to-whom-part-2.window.js
new file mode 100644
index 0000000000..c88158d267
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross-origin-to-whom-part-2.window.js
@@ -0,0 +1,59 @@
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ frame.src = "support/document-with-embedded-svg.html";
+ const elements = {
+ "embed": ["getSVGDocument"],
+ "frame": ["contentDocument"],
+ "iframe": ["getSVGDocument", "contentDocument"],
+ "object": ["getSVGDocument", "contentDocument"]
+ };
+ function element_to_document(element, api) {
+ return api === "getSVGDocument" ? element[api]() : element[api];
+ }
+ function assert_apis(instance, assertNull = false) {
+ const name = instance.localName;
+ let priorPossibleDocument = null;
+ elements[name].forEach(api => {
+ const assertReason = `${name}.${api}`;
+ const possibleDocument = element_to_document(instance, api);
+ if (assertNull) {
+ assert_equals(possibleDocument, null, assertReason);
+ return;
+ } else {
+ assert_not_equals(possibleDocument, null, assertReason);
+
+ // This needs standardizing still
+ // assert_class_string(possibleDocument, "XMLDocument");
+ }
+
+ // Ensure getSVGDocument() and contentDocument if both available return the same
+ if (priorPossibleDocument === null) {
+ priorPossibleDocument = possibleDocument;
+ } else {
+ assert_equals(priorPossibleDocument, possibleDocument);
+ }
+ });
+ }
+ frame.onload = t.step_func_done(() => {
+ const instances = Object.keys(elements).map(element => frame.contentDocument.querySelector(element));
+ // Everything is same origin and same origin-domain, no sweat
+ instances.forEach(instance => assert_apis(instance));
+ // Make the SVG cross origin-domain (its container and the current settings object are not
+ // affected)
+ instances.forEach(instance => {
+ const svgDocument = element_to_document(instance, elements[instance.localName][0]);
+ svgDocument.domain = svgDocument.domain;
+ });
+ instances.forEach(instance => assert_apis(instance, true));
+ const svgContainer = frame.contentDocument;
+ // Make the current settings object same origin-domain with the SVG and cross origin-domain with
+ // SVG's container (SVG's container is not affected)
+ document.domain = document.domain;
+ assert_equals(frame.contentDocument, null);
+ instances.forEach(instance => assert_apis(instance, true));
+ // Make everything same origin-domain once more
+ svgContainer.domain = svgContainer.domain;
+ instances.forEach(instance => assert_apis(instance));
+ });
+ document.body.appendChild(frame);
+}, "Test embed/frame/iframe/object nested document APIs for same origin-domain and cross origin-domain embedder document");
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross-origin-to-whom.window.js b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross-origin-to-whom.window.js
new file mode 100644
index 0000000000..a1e592d8f8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross-origin-to-whom.window.js
@@ -0,0 +1,37 @@
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ frame.src = "support/document-with-embedded-svg.html";
+ const elements = {
+ "embed": ["getSVGDocument"],
+ "frame": ["contentDocument"],
+ "iframe": ["getSVGDocument", "contentDocument"],
+ "object": ["getSVGDocument", "contentDocument"]
+ };
+ function assert_apis(instance) {
+ const name = instance.localName;
+ let priorPossibleDocument = null;
+ elements[name].forEach(api => {
+ const possibleDocument = api == "getSVGDocument" ? instance[api]() : instance[api];
+ assert_not_equals(possibleDocument, null, `${name}.${api}`);
+ // This needs standardizing still
+ // assert_class_string(possibleDocument, "XMLDocument");
+
+ // Ensure getSVGDocument() and contentDocument if both available return the same
+ if (priorPossibleDocument === null) {
+ priorPossibleDocument = possibleDocument;
+ } else {
+ assert_equals(priorPossibleDocument, possibleDocument);
+ }
+ });
+ }
+ frame.onload = t.step_func_done(() => {
+ const instances = Object.keys(elements).map(element => frame.contentDocument.querySelector(element));
+ // Everything is same origin and same origin-domain, no sweat
+ instances.forEach(instance => assert_apis(instance));
+ // Make the current settings object cross origin-domain (SVG and its container are not affected)
+ document.domain = document.domain;
+ assert_equals(frame.contentDocument, null);
+ instances.forEach(instance => assert_apis(instance));
+ });
+ document.body.appendChild(frame);
+}, "Test embed/frame/iframe/object nested document APIs for same origin-domain and cross origin-domain current settings object");
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross_origin_child.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross_origin_child.html
new file mode 100644
index 0000000000..8b44fe805f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross_origin_child.html
@@ -0,0 +1,12 @@
+<script src="iframe_harness.js"></script>
+<body>
+ <iframe src="cross_origin_grandchild.html"></iframe>
+</body>
+<script>
+ send_test_results({
+ "id": '79a52de8-4222-427e-92db-caec28e75f8e',
+ "parent": window.parent !== window,
+ "grandparent": window.parent.parent === window.parent,
+ "top": window.top === window.parent,
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross_origin_grandchild.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross_origin_grandchild.html
new file mode 100644
index 0000000000..1eff64af10
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross_origin_grandchild.html
@@ -0,0 +1,11 @@
+<script src="iframe_harness.js"></script>
+<body>
+</body>
+<script>
+ send_test_results({
+ "id": '6c8da65d-2c5e-44ef-bb0b-b8b9849aab19',
+ "parent": window.parent !== window,
+ "grandparent": window.parent.parent !== window.parent,
+ "top": window.top === window.parent.parent,
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross_origin_parentage.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross_origin_parentage.sub.html
new file mode 100644
index 0000000000..128892d7ed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/cross_origin_parentage.sub.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Check the frame heriarchy</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="iframe_harness.js"></script>
+<body>
+ <iframe src="http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/embedded-content/the-iframe-element/cross_origin_child.html"></iframe>
+</body>
+<script>
+ get_test_results('bffa23ee-b45a-4e9a-9405-87ab437d5cfa');
+ get_test_results('79a52de8-4222-427e-92db-caec28e75f8e');
+ get_test_results('6c8da65d-2c5e-44ef-bb0b-b8b9849aab19');
+ send_test_results({
+ "id": 'bffa23ee-b45a-4e9a-9405-87ab437d5cfa',
+ "parent": window.parent === window,
+ "top": window.top === window,
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/document-getters-return-null-for-cross-origin.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/document-getters-return-null-for-cross-origin.html
new file mode 100644
index 0000000000..e3dc0b0e4e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/document-getters-return-null-for-cross-origin.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test that contentDocument/getSVGDocument() return null for a cross-origin document.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<iframe src='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect height="100" width="100"/></svg>'></iframe>
+<script>
+const iframe = document.querySelector('iframe');
+var t1 = async_test('HTMLIFrameElement.contentDocument for cross-origin document');
+window.addEventListener(
+ 'load', t1.step_func_done(() => { assert_equals(iframe.contentDocument, null); }));
+var t2 = async_test('HTMLIFrameElement.getSVGDocument() for cross-origin document');
+window.addEventListener(
+ 'load', t2.step_func_done(() => { assert_equals(iframe.getSVGDocument(), null); }));
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/historical.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/historical.html
new file mode 100644
index 0000000000..0bc8b857b4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/historical.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>Historical iframe element features should not be supported</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+function t(property) {
+ test(function() {
+ assert_false(property in document.createElement('iframe'));
+ }, 'iframe.' + property + ' should not be supported');
+}
+
+// added in https://github.com/whatwg/html/commit/f6490f17f577fa3478791b29ad8c2b586418001f
+// removed in https://github.com/whatwg/html/commit/1490eba4dba5ab476f0981443a86c01acae01311
+t('seamless');
+
+// Added by https://github.com/whatwg/html/pull/2133
+// Removed by https://github.com/whatwg/html/pull/5915
+t('allowPaymentRequest');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/hittest-detached-iframe-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/hittest-detached-iframe-crash.html
new file mode 100644
index 0000000000..70fe8c1126
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/hittest-detached-iframe-crash.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script>
+function test() {
+ document.body.offsetHeight;
+ document.body.attachShadow({mode: 'closed'});
+ iframe.contentDocument.caretRangeFromPoint(0, 0);
+}
+</script>
+<iframe id="iframe" onload="test()"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-allow.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-allow.html
new file mode 100644
index 0000000000..9e67314923
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-allow.html
@@ -0,0 +1,66 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Check processing of allow attribute in nested browsing context</title>
+<link rel="author" title="Google" href="https://www.google.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-allow">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/browsing-the-web.html#initialise-the-document-object">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#fullscreen-enabled-flag">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+<script>
+ // This returns a data URL (cross-origin with the containing document) which
+ // advances a counter, and reports the counter value together with the
+ // document's fullscreenEnabled state, every time it receives a postMessage.
+ // Fullscreen itself is not important for this test, but the flag is a useful
+ // indicator of whether a policy-controlled-feature is allowed or denied.
+ function getSourceForCrossOriginPage(initial_count) {
+ var page_contents = "<html><body><script>var count="+initial_count+";window.addEventListener('message',function(){parent.postMessage({'count':count++,'fullscreenEnabled':document.fullscreenEnabled},'*');});</scr"+"ipt></body></html>";
+ return "data:text/html;base64,"+btoa(page_contents);
+ }
+
+ async_test(function(t) {
+ var iframe = document.createElement("iframe");
+ iframe.src = getSourceForCrossOriginPage(0);
+
+ iframe.addEventListener('load', function() {
+ // Request the fullscreenEnabled state whenever the frame loads
+ iframe.contentWindow.postMessage(true,"*");
+ });
+
+ window.addEventListener('message', this.step_func(function(msg) {
+ if (msg.data.count == 0) {
+ assert_false(msg.data.fullscreenEnabled, "Document inside cross-origin iframe without allow attribute should not have feature enabled");
+ iframe.setAttribute("allow", "fullscreen");
+ iframe.contentWindow.postMessage(true,"*"); // Request state again
+ } else if (msg.data.count == 1) {
+ assert_false(msg.data.fullscreenEnabled, "Feature should be denied when correct allow attribute is added, before reload");
+ iframe.src = getSourceForCrossOriginPage(2); // Reload the frame
+ } else if (msg.data.count == 2) {
+ assert_true(msg.data.fullscreenEnabled, "Feature should be allowed when correct allow attribute is added, after reload");
+ iframe.removeAttribute("allow");
+ iframe.contentWindow.postMessage(true,"*"); // Request state again
+ } else if (msg.data.count == 3) {
+ assert_true(msg.data.fullscreenEnabled, "Feature should be allowed when allow attribute is removed, before reload");
+ iframe.src = getSourceForCrossOriginPage(4); // Reload the frame
+ } else if (msg.data.count == 4) {
+ assert_false(msg.data.fullscreenEnabled, "Feature should be denied when allow attribute is removed, after reload");
+ iframe.setAttribute("allow", "payment"); // Set allow to an unrelated feature
+ iframe.src = getSourceForCrossOriginPage(5); // Reload the frame
+ } else if (msg.data.count == 5) {
+ assert_false(msg.data.fullscreenEnabled, "Feature should be denied with incorrect allow attribute");
+ iframe.setAttribute("allow", "payment;fullscreen"); // Include fullscreen again
+ iframe.src = getSourceForCrossOriginPage(6); // Reload the frame
+ } else if (msg.data.count == 6) {
+ assert_true(msg.data.fullscreenEnabled, "Feature should be allowed with complex allow attribute");
+ t.done();
+ } else {
+ assert_unreached();
+ }
+ }));
+
+ document.body.appendChild(iframe);
+ }, "iframe-cross-origin-allow");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-allowfullscreen.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-allowfullscreen.html
new file mode 100644
index 0000000000..9fc285bf3e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-allowfullscreen.html
@@ -0,0 +1,95 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Check how allowfullscreen affects fullscreen enabled flag</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/browsing-the-web.html#initialise-the-document-object">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#fullscreen-enabled-flag">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+<script>
+ // This returns a data URL (cross-origin with the containing document) which
+ // advances a counter, and reports the counter value together with the
+ // document's fullscreenEnabled state, every time it receives a postMessage.
+ function getSourceForCrossOriginPage(initial_count) {
+ var page_contents = "<html><body><script>var count="+initial_count+";window.addEventListener('message',function(){parent.postMessage({'count':count++,'fullscreenEnabled':document.fullscreenEnabled},'*');});</scr"+"ipt></body></html>";
+ return "data:text/html;base64,"+btoa(page_contents);
+ }
+
+ async_test(function(t) {
+ var iframe = document.createElement("iframe");
+ iframe.src = "support/blank.htm";
+ var eventWatcher = new EventWatcher(t, iframe, "load");
+ document.body.appendChild(iframe);
+ t.add_cleanup(function() {
+ document.body.removeChild(iframe);
+ });
+
+ assert_true(document.fullscreenEnabled, "Top level document has fullscreen enabled flag set");
+ eventWatcher.wait_for("load").then(t.step_func_done(function() {
+ assert_true(iframe.contentDocument.fullscreenEnabled, "Document inside same-origin iframe without allowfullscreen attribute should still have fullscreen enabled flag set");
+ }));
+ }, "iframe-same-origin-allowfullscreen");
+
+ async_test(function(t) {
+ var iframe = document.createElement("iframe");
+ iframe.src = getSourceForCrossOriginPage(0);
+
+ iframe.addEventListener('load', function() {
+ // Request the fullscreenEnabled state whenever the frame loads
+ iframe.contentWindow.postMessage(true,"*");
+ });
+
+ window.addEventListener('message', this.step_func(function(msg) {
+ if (msg.data.count == 0) {
+ assert_false(msg.data.fullscreenEnabled, "Document inside cross-origin iframe without allowfullscreen attribute should not have fullscreen enabled flag set");
+ iframe.setAttribute("allowfullscreen", "");
+ iframe.contentWindow.postMessage(true,"*"); // Request state again
+ } else if (msg.data.count == 1) {
+ assert_false(msg.data.fullscreenEnabled, "Fullscreen should be denied when allowfullscreen attribute is added, before reload");
+ iframe.src = getSourceForCrossOriginPage(2); // Reload the frame
+ } else if (msg.data.count == 2) {
+ assert_true(msg.data.fullscreenEnabled, "Fullscreen should be allowed when allowfullscreen attribute is added, after reload");
+ iframe.removeAttribute("allowfullscreen");
+ iframe.contentWindow.postMessage(true,"*"); // Request state again
+ } else if (msg.data.count == 3) {
+ assert_true(msg.data.fullscreenEnabled, "Fullscreen should be allowed when allowfullscreen attribute is removed, before reload");
+ iframe.src = getSourceForCrossOriginPage(4); // Reload the frame
+ } else if (msg.data.count == 4) {
+ assert_false(msg.data.fullscreenEnabled, "Fullscreen should be denied when allowfullscreen attribute is removed, after reload");
+ t.done();
+ }
+ }));
+
+ document.body.appendChild(iframe);
+ t.add_cleanup(function() {
+ document.body.removeChild(iframe);
+ });
+ }, "iframe-cross-origin-allowfullscreen");
+
+ /* Fullscreen enabled flag with about:blank */
+
+ function test_allowfullscreen_noload(setup_iframe, check) {
+ var iframe = document.createElement("iframe");
+ setup_iframe(iframe);
+ document.body.appendChild(iframe);
+ check(iframe.contentDocument);
+ document.body.removeChild(iframe);
+ }
+
+ test(function() {
+ test_allowfullscreen_noload(function() {}, function(doc) {
+ assert_true(doc.fullscreenEnabled, "Fullscreen should still be enabled in same-origin document without allowfullscreen attribute");
+ });
+ }, "iframe-noload-noallowfullscreen");
+
+ test(function() {
+ test_allowfullscreen_noload(function(iframe) {
+ iframe.setAttribute("allowfullscreen", true);
+ }, function(doc) {
+ assert_true(doc.fullscreenEnabled, "Fullscreen should be enabled with allowfullscreen attribute");
+ });
+ }, "iframe-noload-allowfullscreen");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-append-to-child-document.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-append-to-child-document.html
new file mode 100644
index 0000000000..ac8bd5e053
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-append-to-child-document.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Append iframe element to its own child document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id=x></iframe>
+<script>
+test(function() {
+ var iframe = document.getElementById('x');
+ var childWindow = iframe.contentWindow;
+ assert_equals(childWindow.parent, window);
+ childWindow.document.body.appendChild(iframe);
+ assert_equals(childWindow.parent, null);
+ assert_equals(iframe.contentWindow, null);
+ assert_equals(childWindow.document.body.firstChild, iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-display-none-with-object.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-display-none-with-object.html
new file mode 100644
index 0000000000..ee73953217
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-display-none-with-object.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test that iframe with object triggers load event in owner document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+ async_test(t => {
+ window.onload = t.step_func_done();
+ const obj = document.createElement("iframe");
+ obj.style.display = "none";
+ obj.src = "support/iframe-with-object.html";
+ document.body.appendChild(obj);
+ }, "Load event triggered on window");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-document-move-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-document-move-crash.html
new file mode 100644
index 0000000000..43da8bf50c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-document-move-crash.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+This test passes if it does not crash.
+<script>
+ var doc1 = document.documentElement;
+ let iframe1 = document.createElement("iframe");
+ doc1.appendChild(iframe1);
+ separateDoc = document.implementation.createDocument("", null);
+ iframe1.addEventListener("DOMFocusOut", function () { separateDoc.adoptNode(iframe1); });
+ iframe1.focus();
+ iframe1 = document.createElement("iframe");
+ doc1.appendChild(iframe1);
+ iframe1.focus();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-first-load-canceled-second-load-blank.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-first-load-canceled-second-load-blank.html
new file mode 100644
index 0000000000..bed3b6477f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-first-load-canceled-second-load-blank.html
@@ -0,0 +1,38 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+// Check about:blank navigation is asynchronous, even if the initial navigation
+// is canceled.
+promise_test(async test => {
+ // Wait for document.body to exist, before appending the <iframe>.
+ await new Promise(resolve => window.onload = resolve);
+
+ // The initial navigation in this new iframe will result in a 204 No Content.
+ // As a result the navigation will be canceled. The <iframe> will stay on the
+ // initial empty document.
+ const iframe = document.createElement("iframe");
+ iframe.src = "/common/blank.html?pipe=status(204)"
+ document.body.appendChild(iframe);
+
+ // The navigation in the iframe will be canceled. There are no good ways to
+ // detect when it will happen. Anyway, any additional navigation must be
+ // asynchronous. To test what happens when there are no pending navigation,
+ // waiting one second should be enough, most of the time.
+ await new Promise(resolve => test.step_timeout(resolve, 1000));
+
+ let load_event_fired = false;
+ const load_event_promise = new Promise(resolve => {
+ iframe.onload = () => {
+ load_event_fired = true;
+ resolve();
+ };
+ });
+ iframe.src = "about:blank";
+ assert_equals(load_event_fired, false,
+ "about:blank must not commit synchronously");
+ await load_event_promise;
+}, "about:blank navigation is asynchronous, even if the initial one is " +
+ "canceled.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-initially-empty-is-updated-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-initially-empty-is-updated-ref.html
new file mode 100644
index 0000000000..0484c397cd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-initially-empty-is-updated-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <iframe src="resources/hello-world.html"></iframe>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-initially-empty-is-updated.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-initially-empty-is-updated.html
new file mode 100644
index 0000000000..818ec0362e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-initially-empty-is-updated.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Iframe that doesn't load can be updated and rendered.</title>
+<meta charset="utf-8">
+<link rel="match" href="iframe-initially-empty-is-updated-ref.html"/>
+<html>
+ <body>
+ <iframe src="resources/empty.html"></iframe>
+ <script>
+ window[0].document.body.appendChild(document.createElement('div'))
+ .appendChild(document.createTextNode('Hello world!'));
+ window[0].document.body.firstChild.style = 'color: green';
+ window.stop();
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ });
+ });
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-load-event.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-load-event.html
new file mode 100644
index 0000000000..5ffe53c308
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-load-event.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test some sanity behavior around iframe load/error events</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+<script>
+async_test(function(t) {
+ var obj = document.createElement("iframe");
+ obj.onload = t.step_func_done(function(e){
+ assert_not_equals(obj.contentWindow, null, "The iframe element should represent a nested browsing context.")
+ assert_equals(Object.getPrototypeOf(e).constructor, Event, "The load event should use the Event interface.");
+ assert_true(e.isTrusted, "The load event should be a trusted event.");
+ assert_false(e.cancelable, "The load event should not be a cancelable event.");
+ assert_false(e.bubbles, "The load event should not be a bubble event.");
+ assert_equals(e.target, obj, "The load event target should be the corresponding object element.");
+ });
+
+ obj.onerror = t.unreached_func("The error event should not be fired.");
+
+ var url = URL.createObjectURL(new Blob([""], { type: "text/html" }));
+
+ obj.src = url;
+ document.body.appendChild(obj);
+}, "load event of blob URL");
+
+async_test(function(t) {
+ var obj = document.createElement("iframe");
+ obj.onload = t.step_func_done(function(e){
+ assert_not_equals(obj.contentWindow, null, "The object element should represent a nested browsing context.")
+ assert_equals(Object.getPrototypeOf(e).constructor, Event, "The load event should use the Event interface.");
+ assert_true(e.isTrusted, "The load event should be a trusted event.");
+ assert_false(e.cancelable, "The load event should not be a cancelable event.");
+ assert_false(e.bubbles, "The load event should not be a bubble event.");
+ assert_equals(e.target, obj, "The load event target should be the corresponding object element.");
+ });
+
+ obj.onerror = t.unreached_func("The error event should not be fired.");
+
+ document.body.appendChild(obj);
+}, "load event of initial about:blank");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-eager.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-eager.html
new file mode 100644
index 0000000000..8acd99d23d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-eager.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<head>
+ <title>Iframes with loading='eager' load immediately regardless of their
+ position with respect to the viewport.</title>
+ <link rel="author" title="Scott Little" href="mailto:sclittle@chromium.org">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t = async_test("Test that iframes with loading='eager' load " +
+ "immediately regardless of their position with " +
+ "respect to the viewport.");
+
+ let has_in_viewport_loaded = false;
+ const in_viewport_iframe_onload = t.step_func(() => {
+ assert_false(has_in_viewport_loaded,
+ "The in_viewport element should load only once.");
+ has_in_viewport_loaded = true;
+ });
+
+ let has_below_viewport_loaded = false;
+ const below_viewport_iframe_onload = t.step_func(() => {
+ assert_false(has_below_viewport_loaded,
+ "The below_viewport element should load only once.");
+ has_below_viewport_loaded = true;
+ });
+
+ window.addEventListener("load", t.step_func_done(() => {
+ assert_true(has_in_viewport_loaded,
+ "The in_viewport element should have loaded before " +
+ "window.load().");
+ assert_true(has_below_viewport_loaded,
+ "The below_viewport element should have loaded before " +
+ "window.load().");
+ }));
+</script>
+
+<body>
+ <iframe id="in_viewport" src="resources/subframe.html?in-viewport"
+ loading="eager" width="200px" height="100px"
+ onload="in_viewport_iframe_onload();"></iframe>
+ <div style="height:1000vh;"></div>
+
+ <!-- The below_viewport element loads very slowly in order to ensure that the
+ window load event is blocked on it. -->
+ <iframe id="below_viewport"
+ src="resources/subframe.html?below-viewport&pipe=trickle(d1)"
+ loading="eager" width="200px" height="100px"
+ onload="below_viewport_iframe_onload();"></iframe>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-base-url-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-base-url-2.html
new file mode 100644
index 0000000000..8782f3d315
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-base-url-2.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<head>
+ <title>Deferred loading=lazy iframes load relative to the document's base URL
+ at parse-time</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="author" title="Raj T" href="mailto:rajendrant@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<script>
+ const below_viewport_iframe = new ElementLoadPromise("below-viewport");
+
+ let has_window_loaded = false;
+
+ async_test(t => {
+ // Change the document's base URL to a bogus one, and scroll the
+ // below-viewport iframe into view. When it loads, it should load relative
+ // to the old base URL computed at parse-time.
+ window.addEventListener("load", t.step_func(() => {
+ window.history.pushState(2, document.title,
+ '/invalid-url-where-no-subresources-exist/')
+ has_window_loaded = true;
+ below_viewport_iframe.element().scrollIntoView();
+ }));
+
+ below_viewport_iframe.promise.then(t.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "Below-viewport loading=lazy iframes do not block the " +
+ "window load event");
+ assert_true(below_viewport_iframe.element()
+ .contentDocument.body.innerHTML.includes("<p>Subframe</p>"));
+ }));
+
+ }, "When a loading=lazy iframe is loaded, it loads relative to the " +
+ "document's base URL computed at parse-time.");
+</script>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <script>
+ // Change the document's base URL so that the iframe request parses relative
+ // to it when it sets up the request at parse-time.
+ window.history.pushState(1, document.title, 'resources/')
+ </script>
+ <iframe id="below-viewport" src="subframe.html" loading="lazy" width="200px"
+ height="100px" onload="below_viewport_iframe.resolve()"</iframe>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-base-url-3.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-base-url-3.html
new file mode 100644
index 0000000000..611221d801
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-base-url-3.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<head>
+ <title>Deferred iframes with loading='lazy' changed to eager later
+ use the document's base URL computed at parse-time</title>
+ <link rel="author" title="Oliver Medhurst" href="mailto:omedhurst@mozilla.com">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+ <base href='/html/semantics/embedded-content/the-iframe-element/resources/'>
+</head>
+
+<script>
+ const below_viewport_iframe = new ElementLoadPromise("below-viewport");
+
+ let has_window_loaded = false;
+
+ async_test(t => {
+ // Change the base URL and scroll down to load the deferred iframe.
+ window.addEventListener("load", t.step_func(() => {
+ const base = document.querySelector('base');
+ base.href = '/invalid-url-where-no-subresources-exist/';
+ has_window_loaded = true;
+ below_viewport_iframe.element().loading = 'eager';
+ }));
+
+ below_viewport_iframe.promise.then(
+ t.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "Below-viewport loading=lazy iframes do not block the " +
+ "window load event");
+ assert_true(below_viewport_iframe.element().contentDocument.body.
+ innerHTML.includes("<p>Subframe</p>"),
+ "The loading=lazy iframe's content is accessible upon loading");
+ }));
+
+ }, "When a loading=lazy iframe is changed to eager later before loading, it loads relative to the " +
+ "document's base URL computed at parse-time.");
+</script>
+
+<body>
+ <div style="height:1000vh"></div>
+ <iframe id="below-viewport" src="subframe.html" loading="lazy"
+ width="200px" height="100px" onload="below_viewport_iframe.resolve()">
+ </iframe>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-base-url.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-base-url.html
new file mode 100644
index 0000000000..ec9f73400d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-base-url.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<head>
+ <title>Deferred iframes with loading='lazy' use the original
+ base URL specified at parse-time</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+ <base href='/html/semantics/embedded-content/the-iframe-element/resources/'>
+</head>
+
+<script>
+ const below_viewport_iframe = new ElementLoadPromise("below-viewport");
+
+ let has_window_loaded = false;
+
+ async_test(t => {
+ // Change the base URL and scroll down to load the deferred iframe.
+ window.addEventListener("load", t.step_func(() => {
+ const base = document.querySelector('base');
+ base.href = '/invalid-url-where-no-subresources-exist/';
+ has_window_loaded = true;
+ below_viewport_iframe.element().scrollIntoView();
+ }));
+
+ below_viewport_iframe.promise.then(
+ t.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "Below-viewport loading=lazy iframes do not block the " +
+ "window load event");
+ assert_true(below_viewport_iframe.element().contentDocument.body.
+ innerHTML.includes("<p>Subframe</p>"),
+ "The loading=lazy iframe's content is accessible upon loading");
+ }));
+
+ }, "When a loading=lazy iframe is loaded, it loads relative to the " +
+ "document's base URL computed at parse-time.");
+</script>
+
+<body>
+ <div style="height:1000vh"></div>
+ <iframe id="below-viewport" src="subframe.html" loading="lazy"
+ width="200px" height="100px" onload="below_viewport_iframe.resolve()">
+ </iframe>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-script-disabled-iframe.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-script-disabled-iframe.html
new file mode 100644
index 0000000000..4f191cd784
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-script-disabled-iframe.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<head>
+<title>Iframes with loading='lazy' in script disabled iframe are not handled
+ as 'lazy'</title>
+<link rel="help" href="https://github.com/scott-little/lazyload">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<div style="height:1000vh;"></div>
+<iframe id="iframe" sandbox="allow-same-origin"
+ src="resources/iframe-loading-lazy-in-viewport.html">
+</iframe>
+<script>
+promise_test(async t => {
+ await new Promise(resolve => iframe.addEventListener("load", resolve));
+
+ const inner_iframe = iframe.contentDocument.querySelector("iframe");
+
+ assert_equals(inner_iframe.contentDocument.body.textContent.trim(), 'Subframe',
+ "lazy-load iframe shouldn't be honored in script disabled iframe");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-far.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-far.html
new file mode 100644
index 0000000000..eeb05b7b98
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-far.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 10000vh;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id="scroller">
+ <div id="spacer"></div>
+ <iframe
+ id="target"
+ src="resources/subframe.html"
+ loading="lazy"
+ onload="iframe_onload();"
+ ></iframe>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded iframes do not load when far from viewport."
+ );
+
+ function iframe_onload() {
+ t.unreached_func(
+ "Lazy-loading iframe far from viewport should not load."
+ )();
+ }
+
+ t.step_timeout(() => {
+ t.done();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-horizontal-far.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-horizontal-far.html
new file mode 100644
index 0000000000..f058b46fbd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-horizontal-far.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ display: flex;
+ }
+
+ #spacer {
+ width: 10000vw;
+ height: 130px;
+ flex-shrink: 0;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ flex-shrink: 0;
+ }
+</style>
+
+<div id="scroller">
+ <div id="spacer"></div>
+ <iframe
+ id="target"
+ src="resources/subframe.html"
+ loading="lazy"
+ onload="iframe_onload();"
+ ></iframe>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded iframes do not load when far from viewport."
+ );
+
+ function img_onload() {
+ t.unreached_func(
+ "Lazy-loading iframe far from viewport should not load."
+ )();
+ }
+
+ t.step_timeout(() => {
+ t.done();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-horizontal.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-horizontal.html
new file mode 100644
index 0000000000..80ba0829a9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-horizontal.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ display: flex;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ flex-shrink: 0;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ flex-shrink: 0;
+ }
+</style>
+
+<div id="scroller">
+ <div id="spacer"></div>
+ <iframe
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="iframe_onload();"
+ ></iframe>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded iframes load when near viewport."
+ );
+
+ function iframe_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for iframe to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-2.html
new file mode 100644
index 0000000000..d080831175
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-2.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id=scroller2>
+ <div id="spacer"></div>
+ <div id="scroller">
+ <iframe>
+ id="target"
+ src="resources/subframe.html"
+ loading="lazy"
+ onload="iframe_onload();"
+ ></iframe>
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded iframes load when near viewport."
+ );
+
+ function iframe_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for iframe to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-3.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-3.html
new file mode 100644
index 0000000000..aa02bb4151
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-3.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ #scroller3 {
+ width: 120px;
+ height: 120px;
+ overflow: scroll;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id=scroller3>
+ <div id=scroller2>
+ <div id="scroller">
+ <div id="spacer"></div>
+ <iframe
+ id="target"
+ src="resources/subframe.html"
+ loading="lazy"
+ onload="iframe_onload();"
+ ></iframe>
+ </div>
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded iframes load when near viewport."
+ );
+
+ function iframe_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for iframe to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-4.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-4.html
new file mode 100644
index 0000000000..e15d891fb5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-4.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ .spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id=scroller2>
+ <div class="spacer"></div>
+ <div id="scroller">
+ <div class="spacer"></div>
+ <iframe
+ id="target"
+ src="resources/subframe.html"
+ loading="lazy"
+ onload="iframe_onload();"
+ ></iframe>
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded iframes load when near viewport."
+ );
+
+ function iframe_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for iframe to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-5.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-5.html
new file mode 100644
index 0000000000..b36265024d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested-5.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ display: flex;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ .spacer {
+ width: 130px;
+ height: 130px;
+ flex-shrink: 0;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ flex-shrink: 0;
+ }
+</style>
+
+<div id=scroller2>
+ <div class="spacer"></div>
+ <div id="scroller">
+ <div class="spacer"></div>
+ <iframe
+ id="target"
+ src="resources/subframe.html"
+ loading="lazy"
+ onload="iframe_onload();"
+ ></iframe>
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function iframe_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for iframe to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested.html
new file mode 100644
index 0000000000..50b5e7ee0a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller-nested.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id=scroller2>
+ <div id="scroller">
+ <div id="spacer"></div>
+ <iframe
+ id="target"
+ src="resources/subframe.html"
+ loading="lazy"
+ onload="iframe_onload();"
+ ></iframe>
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded iframes load when near viewport."
+ );
+
+ function iframe_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for iframe to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller.html
new file mode 100644
index 0000000000..631710e740
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-scroller.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #spacer {
+ width: 100px;
+ height: 100px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id="scroller">
+ <div id="spacer"></div>
+ <iframe
+ id="target"
+ src="resources/subframe.html"
+ loading="lazy"
+ onload="iframe_onload();"
+ ></iframe>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded iframes load when near viewport."
+ );
+
+ function iframe_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for iframe to load."
+ )();
+ }, 2000);
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-viewport-001.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-viewport-001.html
new file mode 100644
index 0000000000..408e5309e5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-viewport-001.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <meta charset="utf-8">
+ <link rel="match" href="iframe-loading-lazy-in-viewport-ref.html">
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1860041">
+</head>
+<body>
+ <iframe loading="lazy" src="data:text/html,PASS" onload="document.documentElement.className = ''"></iframe>
+ <script>
+ document.querySelector("iframe").getBoundingClientRect();
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-viewport-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-viewport-ref.html
new file mode 100644
index 0000000000..067bfa1920
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-in-viewport-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<iframe src="data:text/html,PASS"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-load-event.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-load-event.html
new file mode 100644
index 0000000000..bf98bf7585
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-load-event.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<head>
+ <title>In-viewport loading=lazy iframes do not block the window load event</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<body>
+ <!-- This image blocks the window load event for 1 second -->
+ <img src="/common/square.png?pipe=trickle(d1)">
+
+ <!-- These iframes must load because they intersect the viewport, but they must
+ not block the window load event, because they are loading=lazy -->
+ <iframe id="visible" loading="lazy"
+ src="resources/subframe.html?visible&pipe=trickle(d3)"></iframe>
+ <iframe id="visibility_hidden" style="visibility:hidden;" loading="lazy"
+ src="resources/subframe.html?visibility_hidden&pipe=trickle(d3)"></iframe>
+</body>
+
+<script>
+ const visible_iframe = document.querySelector('#visible');
+ const hidden_iframe = document.querySelector('#visibility_hidden');
+
+ const visible_iframe_t =
+ async_test('In-viewport loading=lazy iframe does not block the load event');
+
+ const hidden_iframe_t =
+ async_test('In-viewport loading=lazy visibility:hidden iframe does not ' +
+ 'block the load event');
+
+ let has_window_loaded = false;
+ window.addEventListener("load", () => {
+ has_window_loaded = true;
+ });
+
+ visible_iframe.onload = visible_iframe_t.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "The visible iframe should not block the load event");
+ });
+
+ hidden_iframe.onload = hidden_iframe_t.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "The hidden iframe should not block the load event");
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-multiple-queued-navigations.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-multiple-queued-navigations.html
new file mode 100644
index 0000000000..0569881ea5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-multiple-queued-navigations.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<head>
+ <title>Multiple queued lazy load navigations do not crash the page</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t = async_test('Multiple queued lazy load navigations do not crash ' +
+ 'the page');
+
+ let has_below_viewport_loaded = false;
+
+ window.addEventListener("load", t.step_func(() => {
+ assert_false(has_below_viewport_loaded,
+ "The below_viewport element should not have loaded before " +
+ "window.load().");
+
+ // Queue another lazy load navigation on the iframe. This should not result
+ // in multiple internal intersection observers being created for the iframe
+ // element, but instead should result in only one intersection observer
+ // associated with the iframe element, and the resulting navigation should
+ // be for the latest `src` attribute mutation.
+ const target = document.querySelector('#below_viewport');
+ target.src = 'resources/subframe.html?new-src';
+ target.scrollIntoView();
+ }));
+
+ const below_viewport_iframe_onload = t.step_func_done(() => {
+ const target = document.querySelector('#below_viewport');
+ // We check both of these to ensure that the `src` attribute and actual
+ // navigated resource do not get out-of-sync when navigating to lazy loaded
+ // resources.
+ assert_true(
+ target.src.includes('new-src'),
+ "The iframe's src should be updated to reflect the latest `src` " +
+ "mutation");
+ assert_true(
+ target.contentDocument.location.href.includes('new-src'),
+ 'The iframe should be navigated to the resource provided by the latest ' +
+ '`src` mutation');
+ });
+</script>
+
+<body>
+ <div style="height:3000vh;"></div>
+ <iframe id="below_viewport" src="resources/subframe.html?old-src"
+ loading="lazy" width="200px" height="100px"
+ onload="below_viewport_iframe_onload();"></iframe>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-multiple-times.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-multiple-times.html
new file mode 100644
index 0000000000..89e41e61bf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-multiple-times.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<head>
+ <title>Iframes with loading='lazy' can be lazy loaded multiple times</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <!-- This is used to represent the top of the viewport, so we can scroll the
+ below-viewport iframe out-of-view later in the test -->
+ <div id="top_div"></div>
+ <div style="height:1000vh;"></div>
+ <iframe id="below_viewport" loading="lazy" src="resources/unload-reporter.html?first"></iframe>
+
+<script>
+ const t = async_test();
+ const iframe = document.querySelector('#below_viewport');
+ const top_div = document.querySelector('#top_div');
+
+ let has_window_load_fired = false;
+ let iframe_being_unloaded = false;
+
+ // This should be triggered first.
+ window.addEventListener('load', t.step_func(() => {
+ has_window_load_fired = true;
+ // Scroll the loading=lazy below-viewport iframe into view, so that it loads.
+ iframe.scrollIntoView();
+ }));
+
+ window.addEventListener('message', t.step_func(msg => {
+ if (msg.data === 'unloading')
+ iframe_being_unloaded = true;
+ }));
+
+ iframe.onload = t.step_func(() => {
+ assert_true(has_window_load_fired,
+ "The loading=lazy below-viewport iframe should not block the " +
+ "window load event");
+ changeIframeSourceAndScrollToTop();
+ });
+
+ function changeIframeSourceAndScrollToTop() {
+ top_div.scrollIntoView();
+
+ // Lazily load a "different" iframe.
+ iframe.src = 'resources/unload-reporter.html?second';
+ iframe.onload =
+ t.unreached_func("The loading=lazy below-viewport iframe should lazily " +
+ "load its second resource, and not load it eagerly " +
+ "when the `src` attribute is changed");
+
+ // In 1s, scroll the iframe *back* into view, and record that it loads
+ // successfully.
+ t.step_timeout(() => {
+ assert_false(iframe_being_unloaded,
+ "The iframe's old resource is not eagerly unloaded");
+
+ iframe.onload = t.step_func_done(() => {
+ assert_true(iframe_being_unloaded,
+ "The iframe's old resources was unloaded correctly");
+ });
+
+ iframe.scrollIntoView();
+ }, 1000);
+ }
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-referrerpolicy-change.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-referrerpolicy-change.sub.html
new file mode 100644
index 0000000000..68734d5708
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-referrerpolicy-change.sub.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<head>
+ <title>Deferred loading=lazy iframes are fetched with the parse-time
+ `referrerpolicy` attribute</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="author" title="Raj T" href="mailto:rajendrant@chromium.org">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<script>
+ const below_viewport_iframe = new ElementLoadPromise("below_viewport_iframe");
+
+ async_test(function(t) {
+ // Change the referrer-policy and scroll down to load the deferred elements.
+ window.addEventListener("load", t.step_func(() => {
+ below_viewport_iframe.element().referrerPolicy = "no-referrer";
+ below_viewport_iframe.element().scrollIntoView();
+ }));
+
+ below_viewport_iframe.promise.then(
+ t.step_func_done(function() {
+ // The `Referer` header should be the full URL, as specified by the
+ // iframe's `referrerpolicy` attribute at parse-time, and not the origin
+ // (as specified in meta referrer tag) or null (as overridden
+ // referrerpolicy=no-referrer after load deferral).
+ assert_true(below_viewport_iframe.element().contentDocument.body.innerHTML
+ .includes("Referer: http://{{location[host]}}{{location[path]}}"),
+ "The iframe's `Referer` should be the referrer's full URL");
+ }));
+ }, "Test that when a deferred iframe is fetched, it uses the " +
+ "`referrerpolicy` specified at parse-time");
+</script>
+
+<body>
+ <meta name="referrer" content="origin">
+ <div style="height:1000vh;"></div>
+ <iframe id="below_viewport_iframe" src="/xhr/resources/echo-headers.py"
+ loading="lazy" width="200px" height="100px"
+ referrerpolicy="unsafe-url"
+ onload="below_viewport_iframe.resolve();">
+ </iframe>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-to-eager.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-to-eager.html
new file mode 100644
index 0000000000..371601a8c3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-to-eager.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<head>
+ <title>Below-viewport iframes with loading='lazy' load when set to
+ loading='eager' or the `loading` attribute is removed</title>
+ <link rel="author" title="Dom Farolino" href="mailto:domfarolino@gmail.com">
+ <link rel="help" href="https://github.com/whatwg/html/pull/5579">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t = async_test("Below-viewport iframes with loading='lazy' load when " +
+ "set to loading='eager' or the `loading` attribute is " +
+ "removed");
+
+ const iframe_1_onload = t.unreached_func("#iframe_1 should not load before " +
+ "the window load event");
+ const iframe_2_onload = t.unreached_func("#iframe_2 should not load before " +
+ "the window load event");
+
+ window.addEventListener("load", t.step_func(() => {
+ const iframe_1 = document.querySelector('#iframe_1');
+ const iframe_2 = document.querySelector('#iframe_2');
+
+ const iframe_1_promise = new Promise((resolve, reject) => {
+ iframe_1.onerror = reject;
+ iframe_1.onload = resolve;
+ });
+
+ const iframe_2_promise = new Promise((resolve, reject) => {
+ iframe_2.onerror = reject;
+ iframe_2.onload = resolve;
+ });
+
+ Promise.all([iframe_1_promise, iframe_2_promise])
+ .then(t.step_func_done())
+ .catch(t.unreached_func("The iframes should load successfully"));
+
+ // Kick off the requests.
+ iframe_1.loading = 'eager';
+ iframe_2.removeAttribute('loading'); // unset the attribute, putting it in
+ // the default (eager) state.
+ }));
+
+</script>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <iframe id="iframe_1"
+ src="resources/subframe.html?1"
+ loading="lazy" onload="iframe_1_onload();"></iframe>
+ <iframe id="iframe_2"
+ src="resources/subframe.html?2"
+ loading="lazy" onload="iframe_2_onload();"></iframe>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy.html
new file mode 100644
index 0000000000..336703ebc4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<head>
+ <title>Iframes with loading='lazy' load when in the viewport</title>
+ <link rel="author" title="Scott Little" href="mailto:sclittle@chromium.org">
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="help" href="https://github.com/whatwg/html/pull/5579">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t_in_viewport =
+ async_test('In-viewport iframes load eagerly');
+ const t_below_viewport =
+ async_test('Below-viewport iframes load lazily');
+ const t_below_viewport_srcdoc =
+ async_test('Below-viewport srcdoc iframes load lazily');
+ const t_below_viewport_data_url =
+ async_test('Below-viewport data: url iframes load lazily');
+ const t_below_viewport_blob_url =
+ async_test('Below-viewport blob url iframes load lazily');
+
+
+ document.addEventListener('DOMContentLoaded', e => {
+ const blob_url_iframe = document.querySelector('#below_viewport_blob_url');
+ const blob = new Blob(['<p>Blob subframe</p>'], {type: 'text/html'});
+ const url = URL.createObjectURL(blob);
+ blob_url_iframe.src = url;
+ });
+
+ let has_window_loaded = false;
+
+ const in_viewport_iframe_onload = t_in_viewport.step_func_done(() => {
+ assert_false(has_window_loaded,
+ "The in_viewport iframe should not block the load event");
+ });
+
+ window.addEventListener("load", () => {
+ has_window_loaded = true;
+ document.getElementById("below_viewport_srcdoc").scrollIntoView();
+ });
+
+ const below_viewport_iframe_onload = t_below_viewport.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "The window load event should have fired before " +
+ "the below-viewport iframe loads");
+ });
+
+ // Onload handlers for below-viewport srcdoc iframe.
+ // Must make this accessible to the srcdoc iframe's body.
+ window.below_viewport_srcdoc_iframe_subresource_onload = t_below_viewport_srcdoc.step_func(() => {
+ assert_true(has_window_loaded,
+ "The window load event should have fired before " +
+ "the below-viewport srcdoc iframe's subresource loads");
+ });
+
+ const below_viewport_srcdoc_iframe_onload = t_below_viewport_srcdoc.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "The window load event should have fired before " +
+ "the below-viewport srcdoc iframe loads");
+ });
+
+ // Onload handler for below-viewport data url iframe.
+ const below_viewport_data_url_iframe_onload = t_below_viewport_data_url.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "The window load event should have fired before " +
+ "the below-viewport data url iframe loads");
+ });
+
+ // Onload handler for below-viewport blob url iframe.
+ const below_viewport_blob_url_iframe_onload = t_below_viewport_blob_url.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "The window load event should have fired before " +
+ "the below-viewport blob url iframe loads");
+ });
+
+</script>
+
+<body>
+ <iframe id="in_viewport" src="resources/subframe.html?in-viewport"
+ loading="lazy" width="200px" height="100px"
+ onload="in_viewport_iframe_onload();"></iframe>
+
+ <div style="height:2000vh;"></div>
+ <iframe id="below_viewport" src="resources/subframe.html?below-viewport"
+ loading="lazy" width="200px" height="100px"
+ onload="below_viewport_iframe_onload();"></iframe>
+ <iframe id="below_viewport_srcdoc"
+ srcdoc="<body><img src='/common/square.png?below-viewport'
+ onload='parent.below_viewport_srcdoc_iframe_subresource_onload();'></body>"
+ loading="lazy" width="200px" height="100px"
+ onload="below_viewport_srcdoc_iframe_onload();"></iframe>
+ <iframe id="below_viewport_data_url"
+ src="data:text/html,<p>Subframe</p>"
+ loading="lazy" width="200px" height="100px"
+ onload="below_viewport_data_url_iframe_onload();"></iframe>
+ <!-- This iframe has its `src` set to a blob URL dynamically above -->
+ <iframe id="below_viewport_blob_url"
+ loading="lazy" width="200px" height="100px"
+ onload="below_viewport_blob_url_iframe_onload();"></iframe>
+
+
+
+ <!-- This async script loads very slowly in order to ensure that, if the
+ below_viewport* elements have started loading, it has a chance to finish
+ loading before window load event fires, so that the test will dependably
+ fail in that case instead of potentially passing depending on how long
+ different resource fetches take. -->
+ <script async src="/common/slow.py"></script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-modify-scrolling-attr-to-yes-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-modify-scrolling-attr-to-yes-ref.html
new file mode 100644
index 0000000000..3758ea2cab
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-modify-scrolling-attr-to-yes-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<title>iframe with scrolling attr equals yes</title>
+<link rel="author" title="Jinfeng Ma" href="mailto:majinfeng1@xiaomi.org">
+
+<p>Test passes if you can see the scrollbars of the iframe displayed below.</p>
+<iframe src="support/iframe-which-content-height-equals-400px.html" scrolling="yes" width="200px" height="100px">
+</iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-modify-scrolling-attr-to-yes.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-modify-scrolling-attr-to-yes.html
new file mode 100644
index 0000000000..9d85aa543d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-modify-scrolling-attr-to-yes.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>modify iframe scrolling attr to yes</title>
+<link rel="author" title="Jinfeng Ma" href="mailto:majinfeng1@xiaomi.org">
+<link rel="help" href="https://www.w3.org/TR/html401/present/frames.html#adef-scrolling">
+<link rel="match" href="iframe-modify-scrolling-attr-to-yes-ref.html">
+
+<p>Test passes if you can see the scrollbars of the iframe displayed below.</p>
+<iframe src="support/iframe-which-content-height-equals-400px.html" scrolling="no" width="200px" height="100px">
+</iframe>
+
+<script>
+ let iframe = document.querySelector("iframe");
+ iframe.onload = function () {
+ iframe.scrolling = 'yes';
+ };
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-network-error.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-network-error.sub.html
new file mode 100644
index 0000000000..4dd3012af4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-network-error.sub.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Network errors with iframe elements</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+async_test(t => {
+ const iframe = document.createElement("iframe");
+ iframe.src = "//{{hosts[][nonexistent]}}/";
+ iframe.onload = () => t.done();
+ iframe.onerror = t.unreached_func("error event must not fire");
+ document.body.append(iframe);
+}, "new iframe: nonexistent host");
+
+async_test(t => {
+ const iframe = document.createElement("iframe");
+ iframe.src = "../resources/not-embeddable.html";
+ iframe.onload = () => t.done();
+ iframe.onerror = t.unreached_func("error event must not fire");
+ document.body.append(iframe);
+}, "new iframe: X-Frame-Options prevents embedding");
+
+async_test(t => {
+ const iframe = document.createElement("iframe");
+ iframe.src = "/common/blank.html";
+ iframe.name = "existingIframe1";
+ iframe.onload = t.step_func(() => {
+ iframe.onload = () => t.done();
+ iframe.onerror = t.unreached_func("error event must not fire 2");
+
+ frames.existingIframe1.location.href = "//{{hosts[][nonexistent]}}/";
+ });
+ iframe.onerror = t.unreached_func("error event must not fire 1");
+ document.body.append(iframe);
+}, "navigating an existing iframe: nonexistent host");
+
+async_test(t => {
+ const iframe = document.createElement("iframe");
+ iframe.src = "/common/blank.html";
+ iframe.name = "existingIframe2";
+ iframe.onload = t.step_func(() => {
+ iframe.onload = () => t.done();
+ iframe.onerror = t.unreached_func("error event must not fire 2");
+
+ frames.existingIframe2.location.href = "../resources/not-embeddable.html";
+ });
+ iframe.onerror = t.unreached_func("error event must not fire 1");
+ document.body.append(iframe);
+}, "navigating an existing iframe: X-Frame-Options prevents embedding");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-nosrc.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-nosrc.html
new file mode 100644
index 0000000000..57189a0b88
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-nosrc.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>Check processing of iframe without src and srcdoc attribute</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/iframe-embed-object.html#process-the-iframe-attributes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+<iframe></iframe>
+<script>
+ let iframe = document.querySelector("iframe");
+
+ async_test(t => {
+ let originDoc = iframe.contentDocument;
+ window.addEventListener("load", t.step_func_done(() => {
+ assert_equals(iframe.contentDocument, originDoc, "contentDocument shouldn't be changed");
+ }));
+ }, "iframe.contentDocument should not be changed");
+
+ async_test(t => {
+ iframe.addEventListener("load", t.unreached_func());
+ window.addEventListener("load", () => t.done());
+ }, "load event of iframe should not be fired after processing the element");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-synchronously-discard.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-synchronously-discard.html
new file mode 100644
index 0000000000..c339525ebd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-synchronously-discard.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>IFrame discards are processed synchronously</title>
+<body></body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ async_test(function(t) {
+ var child = document.createElement("iframe");
+ child.src = "support/blank.htm?1";
+ child.onload = t.step_func(function () {
+ var childWindow = child.contentWindow;
+ var grandchild = childWindow.document.createElement("iframe");
+ grandchild.src = "blank.htm?2";
+ grandchild.onload = t.step_func(function () {
+ var grandchildWindow = grandchild.contentWindow;
+ assert_equals(child.contentWindow, childWindow, "child window");
+ assert_equals(childWindow.parent, window, "child parentage");
+ assert_equals(grandchild.contentWindow, grandchildWindow, "grandchild window");
+ assert_equals(grandchildWindow.parent, childWindow, "grandchild parentage");
+ document.body.removeChild(child);
+ assert_equals(child.contentWindow, null, "child should be discarded");
+ assert_equals(childWindow.parent, null, "child window should be discarded");
+ assert_equals(grandchild.contentWindow, null, "grandchild should be discarded");
+ assert_equals(grandchildWindow.parent, null, "grandchild window should be discarded");
+ t.done();
+ });
+ childWindow.document.body.appendChild(grandchild);
+ });
+ document.body.appendChild(child);
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-with-base-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-with-base-ref.html
new file mode 100644
index 0000000000..21f11d1953
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-with-base-ref.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<html>
+<head>
+ <title>iframe Without Base Tag</title>
+</head>
+<body>
+ <iframe src="support/blank.htm">
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-with-base.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-with-base.html
new file mode 100644
index 0000000000..4637a41a47
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe-with-base.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+<head>
+ <title>iframe With Base Tag</title>
+ <link rel="match" href="iframe-with-base-ref.html">
+ <base href="support/">
+</head>
+<body>
+ <iframe src="blank.htm">
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_harness.js b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_harness.js
new file mode 100644
index 0000000000..2b43c54e2f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_harness.js
@@ -0,0 +1,27 @@
+function get_test_results(id) {
+ async_test(function(test) {
+ test.step_timeout(loop, 100);
+ function loop() {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', 'stash.py?id=' + id);
+ xhr.onload = test.step_func(function() {
+ assert_equals(xhr.status, 200);
+ if (xhr.responseText) {
+ assert_equals(xhr.responseText, "OK");
+ test.done();
+ } else {
+ test.step_timeout(loop, 100);
+ }
+ });
+ xhr.send();
+ }
+ });
+}
+
+function send_test_results(results) {
+ var ok = true;
+ for (result in results) { ok = ok && results[result]; }
+ var xhr = new XMLHttpRequest();
+ xhr.open('POST', 'stash.py?id=' + results.id);
+ xhr.send(ok ? "OK" : "FAIL: " + JSON.stringify(results));
+}
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_01.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_01.htm
new file mode 100644
index 0000000000..fd65f93298
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_01.htm
@@ -0,0 +1,61 @@
+<!DOCTYPE html><html><head><title>javascript: URL creating a document in an about:blank iframe</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#process-the-iframe-attributes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log">FAILED (test did not run)</div>
+
+<iframe src="about:blank" name="ifr1"></iframe>
+<iframe src="" name="ifr2"></iframe>
+<iframe src="./" name="ifr3"></iframe>
+
+<script>
+var test = async_test();
+var results = {};
+var expected = {
+ ifr1:{url:"about:blank", sameDom: true},
+ ifr2:{url:"about:blank", sameDom: true},
+ ifr3:{url: location.href.replace(/\/[^\/]*$/, '/'), sameDom: true },
+ ifr4:{url:"about:blank", sameDom: true},
+ ifr5:{url:"about:blank", sameDom: true}
+}
+
+var js_url = 'javascript:"<html><script>var sameDom = false; try{var cn = top.document.body.className;sameDom = true;}catch(e){}; parent.postMessage( {url: document.URL, name: name, sameDom: sameDom}, \'*\')<\/script><body><p>JS-generated document</p></body></<html>";'
+window.addEventListener('message', function(e){
+ var ifr = e.data.name;
+ results[ifr] = e.data;
+ test.step(function(){
+ assert_equals(results[ifr].url, expected[ifr].url);
+ assert_equals(results[ifr].sameDom, expected[ifr].sameDom);
+ }, 'Testing URL and details of IFRAME ' + ifr);
+ if(Object.keys(results).length === Object.keys(expected).length){
+ test.done();
+ }
+}, false);
+
+var ifr = document.createElement('iframe');
+ifr.name = 'ifr4';
+document.body.appendChild(ifr);
+
+window.onload = function () {
+ for (var i = 0, frame, frames = document.getElementsByTagName('iframe'); frame = frames[i]; i++) {
+ try{
+ frame.src = js_url;
+ }catch(e){
+ results[frame.name] = 'Exception on setting!';
+ }
+ };
+
+ // An iframe with an initial src of a javascript: URL should also have a
+ // document URL of about:blank.
+ var ifr = document.createElement('iframe');
+ ifr.name = 'ifr5';
+ ifr.src = js_url;
+ document.body.appendChild(ifr);
+}
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_navigate_ancestor-1.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_navigate_ancestor-1.sub.html
new file mode 100644
index 0000000000..5e3b7682cc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_navigate_ancestor-1.sub.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ async_test(t => {
+ window.addEventListener('message', t.step_func_done(e => {
+ assert_equals(e.data, "can navigate");
+ }));
+ }, "A => B => B: B should be able to navigate B.");
+</script>
+<iframe src="https://{{hosts[][www]}}:{{ports[https][0]}}/html/semantics/embedded-content/the-iframe-element/support/iframe-tried-to-be-navigated-by-its-child.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_remove_src.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_remove_src.html
new file mode 100644
index 0000000000..f0ff9ff508
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_remove_src.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test that removing the src attribute of an iframe loads about:blank
+ instead of whatever was loaded previously.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+ <script>
+ var iframe;
+ var t = async_test();
+ t.step(setupFrame);
+
+ function setupFrame() {
+ iframe = document.createElement("iframe");
+ iframe.src = URL.createObjectURL(new Blob(["text"], { type: "text/html" }));
+ iframe.onload = t.step_func(blobLoaded);
+ document.body.appendChild(iframe);
+ }
+
+ var removalRunning = false;
+ function blobLoaded() {
+ assert_equals(iframe.contentDocument.location.protocol, "blob:",
+ "Should have loaded the blob");
+ assert_equals(iframe.contentDocument.documentElement.textContent, "text",
+ "Should have loaded the blob text");
+ iframe.onload = t.step_func_done(aboutBlankLoaded);
+ removalRunning = true;
+ iframe.removeAttribute("src");
+ removalRunning = false;
+ }
+
+ function aboutBlankLoaded() {
+ assert_false(removalRunning, "Should not have loaded about:blank sync");
+ assert_equals(iframe.contentDocument.location.href, "about:blank",
+ "Should have loaded about:blank");
+ assert_equals(iframe.contentDocument.documentElement.textContent, "",
+ "Should have loaded the about:blank text");
+ }
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_script.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_script.html
new file mode 100644
index 0000000000..3e65d91984
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_script.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: iframe_sandbox_allow_scripts</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+<script>
+ // Set up all our script stuff before the iframe starts loading, so we don't
+ // miss any messages from it.
+ var step1 = false;
+ var t = async_test("iframe_sandbox_allow_scripts");
+
+ setup({timeout:1000});
+ window.addEventListener("message", callback1, false);
+
+ function run() {
+ window.removeEventListener("message", callback1, false);
+ document.getElementById("testIframe").sandbox = "allow-scripts";
+ document.getElementById("testIframe").contentWindow.location.reload();
+ window.addEventListener("message", callback2, false);
+ }
+
+ function callback1(e) {
+ step1= !step1;
+ }
+
+ function callback2(e) {
+ t.step(function () {
+ assert_false(step1, "[allow-scripts] is not set.");
+ assert_equals(e.data, "Script executed", "[allow-scripts] is set.");
+ });
+ t.done();
+ }
+
+ // Make sure the iframe loads before we mess with it.
+ window.addEventListener("load", function() {
+ // The load event might fire before a message from the child comes in...
+ // Wait a bit to see if that message does come in.
+ setTimeout(run, 500);
+ });
+</script>
+<iframe id="testIframe" src="support/sandbox_allow_script.html" sandbox="allow-same-origin" style="display:none"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation-1.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation-1.html
new file mode 100644
index 0000000000..b033ec44d2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Check that sandboxed iframe can perform navigation on the top frame
+ when allow-top-navigation is set</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ // We are the main test page. Open a popup, so that we can
+ // can experiment navigation of the top frame.
+ async_test(t => {
+ window.addEventListener("message", t.step_func_done(e => {
+ assert_equals(e.data, "can navigate");
+ e.source.close();
+ }));
+ let sandbox = "allow-top-navigation allow-scripts";
+ window.open("support/load-into-the-iframe.html?sandbox=" + sandbox);
+ }, "Frames with `allow-top-navigation` should be able to navigate the top frame.");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation-2.html
new file mode 100644
index 0000000000..bd7d92c0dd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation-2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Check that sandboxed iframe cannot perform navigation on the top
+ frame when allow-top-navigation is not set</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ // We are the main test page. Open a popup, so that we can
+ // can experiment navigation of the top frame.
+ async_test(t => {
+ window.addEventListener("message", t.step_func_done(e => {
+ assert_equals(e.data, "cannot navigate");
+ e.source.close();
+ }));
+ window.open('support/load-into-the-iframe.html');
+ }, "Frames without `allow-top-navigation` should not be able to navigate the top frame.");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation-3.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation-3.html
new file mode 100644
index 0000000000..c3e5dc1fd6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation-3.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Check that sandboxed iframe can perform navigation on the top frame
+ when allow-top-navigation is set (even when
+ allow-top-navigation-by-user-activation is set)</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ // We are the main test page. Open a popup, so that we can
+ // can experiment navigation of the top frame.
+ async_test(t => {
+ window.addEventListener("message", t.step_func_done(e => {
+ assert_equals(e.data, "can navigate");
+ e.source.close();
+ }));
+ // Specifying both allow-top-navigation and
+ // allow-top-navigation-by-user-activation is a document conformance
+ // error: allow-top-navigation-by-user-activation will have no effect.
+ let sandbox = "allow-top-navigation allow-top-navigation-by-user-activation allow-scripts";
+ window.open("support/load-into-the-iframe.html?sandbox=" + sandbox);
+ }, "Frames with `allow-top-navigation` should be able to navigate the top frame even when `allow-top-navigation-by-user-activation` is set.");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation_by_user_activation-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation_by_user_activation-manual.html
new file mode 100644
index 0000000000..0fa9de7c12
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation_by_user_activation-manual.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+ <style>
+ iframe { width: 400px; height: 300px;}
+ </style>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ setup({explicit_timeout: true});
+
+ async_test(function(t) {
+ window.addEventListener("message", t.step_func_done(function(e) {
+ assert_equals(e.data, "BLOCKED", "The message should say 'BLOCKED'.");
+ }));
+ }, "The sandboxed iframe should post a message saying the top navigation was blocked when no user gesture.");
+ </script>
+</head>
+<body>
+ <p>This tests that an iframe in sandbox with 'allow-top-navigation-by-user-activation'
+ can navigate the top level page, if it is trigged by a user gesture.</p>
+ <p>Click on the button in the iframe and it should navigate the top page.</p>
+ <iframe id="i" sandbox="allow-scripts allow-top-navigation-by-user-activation" src="support/iframe-that-performs-top-navigation.html"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation_by_user_activation_without_user_gesture.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation_by_user_activation_without_user_gesture.html
new file mode 100644
index 0000000000..042851bbb4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_allow_top_navigation_by_user_activation_without_user_gesture.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+ window.addEventListener("message", t.step_func_done(function(e) {
+ assert_equals(e.data, "PASS", "The message should say 'PASS' instead of 'FAIL'");
+ }));
+}, "The sandboxed iframe should post a message saying the test was in the state of 'PASS'.");
+</script>
+</head>
+<body>
+ <p>This tests that an iframe in sandbox with 'allow-top-navigation-by-user-activation'
+ cannot navigate its top level page, if it is not trigged by a user gesture.</p>
+ <iframe sandbox='allow-top-navigation-by-user-activation allow-scripts' src="support/iframe-that-performs-top-navigation-without-user-gesture-failed.html"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_allow_downloads.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_allow_downloads.tentative.html
new file mode 100644
index 0000000000..26b8b393ab
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_allow_downloads.tentative.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>&lt;a download&gt; triggered download in sandbox is allowed by allow-downloads.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src="support/iframe_sandbox_download_helper.js"></script>
+<body>
+<script>
+"use strict";
+
+const attributes_list = [
+ '',
+ 'target="_blank"',
+ 'target="_blank" rel="noopener"',
+];
+
+const download_flags = [false, true];
+
+attributes_list.forEach(attributes =>
+ download_flags.forEach(download_flag =>
+ async_test(t => {
+ const download_token = token();
+ let iframe = document.createElement("iframe");
+ iframe.srcdoc = `<a ${attributes}>Download</a>`;
+ iframe.sandbox = "allow-same-origin allow-popups allow-downloads";
+ iframe.addEventListener('load', t.step_func(function () {
+ if (attributes !== '' || download_flag) {
+ // Specifiying `download` or a `target` should not trigger a
+ // navigation in this iframe.
+ iframe.contentWindow.addEventListener(
+ "unload", t.unreached_func("Unexpected navigation."));
+ }
+ let anchor = iframe.contentDocument.getElementsByTagName('a')[0];
+ anchor.href = "support/download_stash.py?token=" + download_token;
+ if (download_flag) anchor.download = null;
+ anchor.click();
+ AssertDownloadSuccess(t, download_token, DownloadVerifyDelay());
+ }), { once: true });
+
+ document.body.appendChild(iframe);
+ }, `<a ${attributes} ${download_flag ? "download" : ""}> triggered ` +
+ `download in sandbox is allowed by allow-downloads.`)));
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_block_downloads.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_block_downloads.tentative.html
new file mode 100644
index 0000000000..df46932319
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_block_downloads.tentative.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>&lt;a download&gt; triggered download in sandbox is blocked.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src="support/iframe_sandbox_download_helper.js"></script>
+<body>
+<script>
+"use strict";
+
+async_test(t => {
+ const download_token = token();
+ var iframe = document.createElement("iframe");
+ iframe.srcdoc = "<a>Download</a>";
+ iframe.sandbox = "allow-same-origin";
+ iframe.onload = t.step_func(function () {
+ iframe.contentWindow.addEventListener(
+ "unload", t.unreached_func("Unexpected navigation."));
+ let anchor = iframe.contentDocument.getElementsByTagName('a')[0];
+ anchor.href = `support/download_stash.py?token=${download_token}` +
+ `&finish-delay=${StreamDownloadFinishDelay()}`;
+ anchor.download = null;
+ anchor.click();
+ AssertDownloadFailure(t, download_token, StreamDownloadFinishDelay() +
+ DownloadVerifyDelay());
+ });
+
+ document.body.appendChild(iframe);
+}, "<a download> triggered download in sandbox is blocked.");
+
+async_test(t => {
+ const download_token = token();
+ let iframe = document.createElement("iframe");
+ iframe.srcdoc = '<a>Download</a>';
+ iframe.sandbox = "allow-same-origin";
+ iframe.onload = t.step_func(function () {
+ iframe.contentWindow.addEventListener(
+ "unload", t.unreached_func("Unexpected navigation."));
+ let anchor = iframe.contentDocument.getElementsByTagName('a')[0];
+ anchor.href = `support/download_stash.py?token=${download_token}`;
+ anchor.download = null;
+ anchor.click();
+ AssertDownloadFailure(t, download_token, DownloadVerifyDelay());
+ });
+
+ document.body.appendChild(iframe);
+}, '<a download> triggered download in sandbox is blocked ' +
+ 'before a request is made.');
+
+['', 'target="_blank" ', 'target="_blank" rel="noopener" '].forEach(
+ attributes => async_test(t => {
+ const download_token = token();
+ let iframe = document.createElement("iframe");
+ iframe.srcdoc = `<a ${attributes}>Download</a>`;
+ iframe.sandbox = "allow-same-origin allow-popups";
+ iframe.onload = t.step_func(function () {
+ iframe.contentWindow.addEventListener(
+ "unload", t.unreached_func("Unexpected navigation."));
+ let anchor = iframe.contentDocument.getElementsByTagName('a')[0];
+ anchor.href = `support/download_stash.py?token=${download_token}` +
+ `&finish-delay=${StreamDownloadFinishDelay()}`;
+ anchor.click();
+ AssertDownloadFailure(t, download_token, StreamDownloadFinishDelay() +
+ DownloadVerifyDelay());
+ });
+
+ document.body.appendChild(iframe);
+ }, `<a ${attributes}> triggered download in sandbox is blocked.`));
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-1.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-1.html
new file mode 100644
index 0000000000..ce171bfb8e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>iframe sandbox without allow_modals (alert)</title>
+<link rel="author" title="Igalia" href="https://www.igalia.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe sandbox="allow-scripts"></iframe>
+<script src="support/iframe_sandbox_block_modals.js"></script>
+<script>
+ runTest("alert", undefined);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html
new file mode 100644
index 0000000000..fbd4d23d01
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>iframe sandbox without allow_modals (confirm)</title>
+<link rel="author" title="Igalia" href="https://www.igalia.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe sandbox="allow-scripts"></iframe>
+<script src="support/iframe_sandbox_block_modals.js"></script>
+<script>
+ runTest("confirm", false);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html
new file mode 100644
index 0000000000..5712301180
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>iframe sandbox without allow_modals (prompt)</title>
+<link rel="author" title="Igalia" href="https://www.igalia.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe sandbox="allow-scripts"></iframe>
+<script src="support/iframe_sandbox_block_modals.js"></script>
+<script>
+ runTest("prompt", null);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-4.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-4.html
new file mode 100644
index 0000000000..f750e345ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-4.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>iframe sandbox without allow_modals (print)</title>
+<link rel="author" title="Igalia" href="https://www.igalia.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe sandbox="allow-scripts"></iframe>
+<script src="support/iframe_sandbox_block_modals.js"></script>
+<script>
+ runTest("print", undefined);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_ancestor-1.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_ancestor-1.html
new file mode 100644
index 0000000000..4cf48184c9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_ancestor-1.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Check that sandboxed iframe can not navigate their ancestors</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ var t = async_test();
+ onmessage = t.step_func_done(function(e) {
+ assert_equals(e.data, "can not navigate", "Should have the right message");
+ });
+</script>
+<iframe sandbox="allow-scripts" src="support/iframe-tried-to-be-navigated-by-its-child.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_ancestor-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_ancestor-2.html
new file mode 100644
index 0000000000..159491c73c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_ancestor-2.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Check that unsandboxed iframe can navigate their ancestors</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ var t = async_test();
+ onmessage = t.step_func_done(function(e) {
+ assert_equals(e.data, "can navigate", "Should have the right message");
+ });
+</script>
+<iframe src="support/iframe-tried-to-be-navigated-by-its-child.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_descendants.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_descendants.html
new file mode 100644
index 0000000000..0934adfa82
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_descendants.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Check that sandboxed iframe can navigate their descendants</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ var t = async_test();
+ onmessage = t.step_func_done(function(e) {
+ assert_equals(e.data, "can navigate", "Should have the right message");
+ });
+</script>
+<iframe sandbox="allow-scripts" src="support/iframe-trying-to-navigate-its-child.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_history_go_back-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_history_go_back-2.html
new file mode 100644
index 0000000000..7a94f1ce4a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_history_go_back-2.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Check that sandboxed iframe can navigate their self</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ var t = async_test();
+ onmessage = t.step_func((e) => {
+ if (e.data == 'pushstatebackdone') t.done();
+ });
+
+ function doNavigation() {
+ frames[0].postMessage('pushstateback', '*');
+ }
+</script>
+<iframe id="child_frame" sandbox="allow-scripts" src="support/iframe-tried-to-be-navigated-by-history.html" onload="doNavigation();"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_history_go_back.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_history_go_back.html
new file mode 100644
index 0000000000..7026edf8f9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_history_go_back.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Check that sandboxed iframe can not navigate their ancestors</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ var t = async_test();
+ onpopstate = t.unreached_func('no pop state');
+
+ function doNavigation() {
+ history.pushState( {state: "one past"}, 'page 2', '');
+ frames[0].postMessage('back', '*');
+ t.step_timeout(() => {
+ t.done();
+ }, 1000);
+ }
+</script>
+<iframe id="child_frame" sandbox="allow-scripts" src="support/iframe-tried-to-be-navigated-by-history.html" onload="doNavigation();"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_history_go_forward.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_history_go_forward.html
new file mode 100644
index 0000000000..e9d1def099
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_history_go_forward.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Check that sandboxed iframe can not navigate their ancestors</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ var t = async_test();
+ var pop_state_count = 0;
+ onpopstate = t.step_func((e) => {
+ pop_state_count++;
+ if (pop_state_count == 1) {
+ // Should not generate a pop state
+ frames[0].postMessage('forward', '*');
+ t.step_timeout(() => {
+ t.done();
+ }, 1000);
+ } else if (pop_state_count > 1) {
+ assert_unreached('no pop state');
+ }
+ });
+
+ function doNavigation() {
+ history.pushState( {state: "one past"}, 'page 2', '');
+ // Should generate a pop state
+ history.back();
+ }
+</script>
+<iframe id="child_frame" sandbox="allow-scripts" src="support/iframe-tried-to-be-navigated-by-history.html" onload="doNavigation();"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_itself.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_itself.html
new file mode 100644
index 0000000000..12c4e0ca50
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_itself.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Check that sandboxed iframe can navigate itself</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ var t = async_test();
+ onmessage = t.step_func_done(function(e) {
+ assert_equals(e.data, "can navigate", "Should have the right message");
+ });
+</script>
+<iframe sandbox="allow-scripts" src="support/iframe-trying-to-navigate-itself.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_other_frame_popup.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_other_frame_popup.sub.html
new file mode 100644
index 0000000000..19704b38a3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_other_frame_popup.sub.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Check that sandboxed iframe can not navigate other frame's popup</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+
+// This HTML file is loaded 3 times.
+// (1) As the initial test file (mode = '').
+// (2) In the popup window (mode = 'popup').
+// (3) In the sandboxed iframe (mode = 'iframe').
+// Note: The sandboxed iframe (3) tries to navigate the popup window (2) to
+// a new mode=iframenavigated URL. But this must be blocked because (3) is not
+// the 'one permitted sandboxed navigator'.
+// https://html.spec.whatwg.org/multipage/origin.html#one-permitted-sandboxed-navigator
+(() => {
+ const mode = '{{GET[mode]}}';
+ if (mode == 'popup') {
+ // (2): Loaded in the popup window.
+ return;
+ }
+ if (mode == 'iframe') {
+ // (3): Loaded in the sandboxed iframe.
+ try {
+ // Attempts to navigate the popup window (2).
+ parent.document.popupWin.location = location.href + 'navigated';
+ } catch (e) {
+ parent.postMessage('cannot navigate');
+ }
+ return;
+ }
+ if (mode == 'iframenavigated') {
+ // This URL page must not be loaded.
+ opener.postMessage('can navigate');
+ return;
+ }
+
+ // (1): Loaded as the initial test file.
+ promise_test(async t => {
+ // Opens a popup window to load the page (2).
+ document.popupWin = window.open(location.href + '?mode=popup', '_blank');
+ t.add_cleanup(() => document.popupWin.close());
+ await new Promise(resolve => {
+ document.popupWin.addEventListener('load', resolve);
+ });
+
+ // Adds an iframe to load the page (3).
+ const iframe = document.createElement('iframe');
+ t.add_cleanup(() => iframe.remove());
+ iframe.sandbox = 'allow-popups allow-same-origin allow-scripts';
+ iframe.src = location.href + '?mode=iframe';
+ const message_promise = new Promise(resolve => {
+ window.addEventListener('message', (e) => { resolve(e.data); });
+ });
+ document.body.appendChild(iframe);
+
+ const result = await message_promise;
+ assert_equals(result, 'cannot navigate');
+ }, "Sandboxed iframe can not navigate other frame's popup");
+
+})();
+</script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_allow_downloads.sub.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_allow_downloads.sub.tentative.html
new file mode 100644
index 0000000000..6b3b3104ef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_allow_downloads.sub.tentative.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Navigation resulted download in sandbox is allowed by allow-downloads.</title>
+<meta name="timeout" content="long" />
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src="support/iframe_sandbox_download_helper.js"></script>
+<body>
+<script>
+"use strict";
+
+async_test(t => {
+ const token = "{{$id:uuid()}}";
+ var iframe = document.createElement("iframe");
+ iframe.srcdoc = "<a>Download</a>";
+ iframe.sandbox = "allow-same-origin allow-downloads";
+ iframe.onload = t.step_func(function () {
+ iframe.contentWindow.addEventListener(
+ "unload", t.unreached_func("Unexpected navigation."));
+ var anchor = iframe.contentDocument.getElementsByTagName('a')[0];
+ // Set |finish-delay| to let the server stream a response over a period
+ // of time, so it's able to catch potential download cancellation by
+ // detecting a socket close.
+ anchor.href = "support/download_stash.py?token=" + token + "&finish-delay=" + StreamDownloadFinishDelay();
+ anchor.click();
+ AssertDownloadSuccess(t, token, StreamDownloadFinishDelay() + DownloadVerifyDelay());
+ });
+
+ document.body.appendChild(iframe);
+}, "Navigation resulted download in sandbox is allowed by allow-downloads.");
+
+async_test(t => {
+ const token = "{{$id:uuid()}}";
+ var iframe = document.createElement("iframe");
+
+ const folder = location.origin+"/html/semantics/embedded-content/the-iframe-element/";
+ const href = `${folder}support/download_stash.py?token=${token}&finish-delay=${StreamDownloadFinishDelay() }`;
+ const objectDoc =`<a href="${href}">download</a>
+ <${"script"}> document.querySelector("a").click(); </${"script"}>`;
+
+ iframe.srcdoc = `<object data='data:text/html,${objectDoc}'></object>`;
+ iframe.sandbox = "allow-same-origin allow-scripts allow-downloads";
+ iframe.addEventListener("load",()=>{
+ AssertDownloadSuccess(t, token, StreamDownloadFinishDelay() + DownloadVerifyDelay());
+ })
+ document.body.appendChild(iframe);
+}, "Navigation resulted download in sandbox from <object> is allowed by allow-downloads.");
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_block_downloads.sub.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_block_downloads.sub.tentative.html
new file mode 100644
index 0000000000..87d1d722d5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_block_downloads.sub.tentative.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Navigation resulted download in sandbox is blocked.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src="support/iframe_sandbox_download_helper.js"></script>
+<body>
+<script>
+"use strict";
+
+async_test(t => {
+ const token = "{{$id:uuid()}}";
+ var iframe = document.createElement("iframe");
+ iframe.srcdoc = "<a>Download</a>";
+ iframe.sandbox = "allow-same-origin";
+ iframe.onload = t.step_func(function () {
+ iframe.contentWindow.addEventListener(
+ "unload", t.unreached_func("Unexpected navigation."));
+ var anchor = iframe.contentDocument.getElementsByTagName('a')[0];
+ // Set |finish-delay| to let the server stream a response over a period
+ // of time, so it's able to catch potential download cancellation by
+ // detecting a socket close.
+ anchor.href = "support/download_stash.py?token=" + token + "&finish-delay=" + StreamDownloadFinishDelay();
+ anchor.click();
+ AssertDownloadFailure(t, token, StreamDownloadFinishDelay() + DownloadVerifyDelay());
+ });
+
+ document.body.appendChild(iframe);
+}, "Navigation resulted download in sandbox is blocked.");
+
+
+async_test(t => {
+ const token = "{{$id:uuid()}}";
+ var iframe = document.createElement("iframe");
+
+ const folder = location.origin+"/html/semantics/embedded-content/the-iframe-element/";
+ const href = `${folder}support/download_stash.py?token=${token}&finish-delay=${StreamDownloadFinishDelay() }`;
+ const objectDoc =`<a href="${href}">download</a>
+ <${"script"}> document.querySelector("a").click(); </${"script"}>`;
+
+ iframe.srcdoc = `<object data='data:text/html,${objectDoc}'></object>`;
+ iframe.sandbox = "allow-same-origin allow-scripts";
+ iframe.addEventListener("load",()=>{
+ AssertDownloadFailure(t, token, StreamDownloadFinishDelay() + DownloadVerifyDelay());
+ })
+ document.body.appendChild(iframe);
+}, "Navigation resulted download in sandbox from <object> is blocked.");
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-1.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-1.html
new file mode 100644
index 0000000000..342d422036
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-1.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Check that popups from a sandboxed iframe escape the sandbox if
+ allow-popups-to-escape-sandbox is used</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox">
+</iframe>
+<script>
+ var t = async_test();
+ var ourOrigin;
+ onmessage = t.step_func(function(e) {
+ assert_equals(e.data, "hello", "This is our origin getter message");
+ ourOrigin = e.origin;
+
+ onmessage = t.step_func_done(function(e) {
+ assert_equals(e.origin, "null", "It came from a sandboxed iframe");
+ assert_equals(e.data.data, undefined, "Should have the right message");
+ assert_equals(e.data.origin, ourOrigin, "Should have escaped the sandbox");
+ });
+
+ document.querySelector("iframe").src = "iframe_sandbox_popups_helper-1.html";
+ });
+ postMessage("hello", "*");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-2.html
new file mode 100644
index 0000000000..40ffbb1e02
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-2.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Check that popups from a sandboxed iframe escape the sandbox if
+ allow-popups-to-escape-sandbox is used</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox">
+</iframe>
+<script>
+ var t = async_test();
+ var ourOrigin;
+ onmessage = t.step_func(function(e) {
+ assert_equals(e.data, "hello", "This is our origin getter message");
+ ourOrigin = e.origin;
+
+ onmessage = t.step_func_done(function(e) {
+ assert_equals(e.origin, "null", "It came from a sandboxed iframe");
+ assert_equals(e.data.data, undefined, "Should have the right message");
+ assert_equals(e.data.origin, ourOrigin, "Should have escaped the sandbox");
+ });
+
+ var iframe = document.querySelector("iframe");
+ iframe.onload = function() {
+ frames[0].postMessage("start", "*");
+ }
+ iframe.src = "iframe_sandbox_popups_helper-2.html";
+ });
+ addEventListener("load", function() {
+ postMessage("hello", "*");
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html
new file mode 100644
index 0000000000..2d35fd5fc1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Check that popups from a sandboxed iframe escape the sandbox if
+ allow-popups-to-escape-sandbox is used</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox">
+</iframe>
+<script>
+ var t = async_test();
+ var ourOrigin;
+ onmessage = t.step_func(function(e) {
+ assert_equals(e.data, "hello", "This is our origin getter message");
+ ourOrigin = e.origin;
+
+ onmessage = t.step_func_done(function(e) {
+ assert_equals(e.origin, "null", "It came from a sandboxed iframe");
+ assert_equals(e.data.data, undefined, "Should have the right message");
+ assert_equals(e.data.origin, ourOrigin, "Should have escaped the sandbox");
+ });
+
+ document.querySelector("iframe").src = "iframe_sandbox_popups_helper-3.html";
+ });
+ postMessage("hello", "*");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_helper-1.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_helper-1.html
new file mode 100644
index 0000000000..6b120f15d0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_helper-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script>
+ if (opener) {
+ // We're the popup. Send back our state. What we really want to send is
+ // our origin, but that will come automatically.
+ opener.postMessage(undefined, "*");
+ self.close();
+ } else {
+ // We're the child. Start listening for messages and open ourselves as the
+ // popup.
+ onmessage = function (e) {
+ parent.postMessage({ data: e.data, origin: e.origin }, "*");
+ };
+ popupWin = window.open(location.href);
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_helper-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_helper-2.html
new file mode 100644
index 0000000000..ea28cf5375
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_helper-2.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<body>
+<script>
+ if (opener) {
+ // We're the popup. Send back our state. What we really want to send is
+ // our origin, but that will come automatically.
+ opener.postMessage(undefined, "*");
+ self.close();
+ } else {
+ // We're the child. Start listening for messages from our parent and open
+ // ourselves as the popup when we get the "start" message.
+ onmessage = function (e) {
+ if (e.data == "start") {
+ // Now listen for messages from the thing we plan to open.
+ onmessage = function(e) {
+ parent.postMessage({ data: e.data, origin: e.origin }, "*");
+ }
+
+ var a = document.createElement("a");
+ a.href = location.href;
+ a.target = "_blank";
+ a.rel = "opener";
+ document.body.appendChild(a);
+ a.click();
+ }
+ };
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_helper-3.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_helper-3.html
new file mode 100644
index 0000000000..ef3e59037f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_helper-3.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<script>
+ if (opener) {
+ // We're the popup. Send back our state. What we really want to send is
+ // our origin, but that will come automatically.
+ opener.postMessage(undefined, "*");
+ self.close();
+ } else {
+ // We're the child. Start listening for messages and open ourselves as the
+ // popup.
+ onmessage = function (e) {
+ parent.postMessage({ data: e.data, origin: e.origin }, "*");
+ };
+ var popupWin = window.open();
+ popupWin.location.href = location.href;
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html
new file mode 100644
index 0000000000..3dee96d67a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Check that popups from a sandboxed iframe do not escape the sandbox</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ var t = async_test();
+ onmessage = t.step_func_done(function(e) {
+ assert_equals(e.origin, "null", "It came from a sandboxed iframe");
+ assert_equals(e.data.data, undefined, "Should have the right message");
+ assert_equals(e.data.origin, "null", "Should not have escaped the sandbox");
+ });
+</script>
+<iframe sandbox="allow-scripts allow-popups"
+ src="iframe_sandbox_popups_helper-1.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-2.html
new file mode 100644
index 0000000000..27046db744
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-2.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Check that popups from a sandboxed iframe do not escape the sandbox</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ var t = async_test();
+ onmessage = t.step_func_done(function(e) {
+ assert_equals(e.origin, "null", "It came from a sandboxed iframe");
+ assert_equals(e.data.data, undefined, "Should have the right message");
+ assert_equals(e.data.origin, "null", "Should not have escaped the sandbox");
+ });
+ addEventListener("load", function() {
+ frames[0].postMessage("start", "*");
+ });
+</script>
+<iframe sandbox="allow-scripts allow-popups"
+ src="iframe_sandbox_popups_helper-2.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html
new file mode 100644
index 0000000000..556387e14a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Check that popups from a sandboxed iframe do not escape the sandbox</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ var t = async_test();
+ onmessage = t.step_func_done(function(e) {
+ assert_equals(e.origin, "null", "It came from a sandboxed iframe");
+ assert_equals(e.data.data, undefined, "Should have the right message");
+ assert_equals(e.data.origin, "null", "Should not have escaped the sandbox");
+ });
+</script>
+<iframe sandbox="allow-scripts allow-popups"
+ src="iframe_sandbox_popups_helper-3.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_window_open_download_allow_downloads.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_window_open_download_allow_downloads.tentative.html
new file mode 100644
index 0000000000..158fc4f947
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_window_open_download_allow_downloads.tentative.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Downloads triggered by window.open from sandbox are blocked.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src="support/iframe_sandbox_download_helper.js"></script>
+<body>
+<script>
+"use strict";
+
+['', '"_blank"', '"_blank", "noopener"'].forEach(options =>
+ async_test(t => {
+ const download_token = token();
+ let iframe = document.createElement("iframe");
+ const download_link = `support/download_stash.py?token=${download_token}` +
+ `&finish-delay=${StreamDownloadFinishDelay()}`;
+ iframe.srcdoc = `<script>window.open("${download_link}", ${options})</scr` +
+ `ipt>`;
+ iframe.sandbox = "allow-same-origin allow-popups allow-scripts " +
+ "allow-downloads";
+ AssertDownloadSuccess(t, download_token, DownloadVerifyDelay());
+ document.body.appendChild(iframe);
+ }, `window.open(download, ${options}) triggering download in ` +
+ 'sandbox is allowed by allow-downloads.'));
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_window_open_download_block_downloads.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_window_open_download_block_downloads.tentative.html
new file mode 100644
index 0000000000..20423e202f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_window_open_download_block_downloads.tentative.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Downloads triggered by window.open from sandbox are blocked.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src="support/iframe_sandbox_download_helper.js"></script>
+<body>
+<script>
+"use strict";
+
+['', '"_blank"', '"_blank", "noopener"'].forEach(options =>
+ async_test(t => {
+ const download_token = token();
+ let iframe = document.createElement("iframe");
+ const download_link = `support/download_stash.py?token=${download_token}` +
+ `&finish-delay=${StreamDownloadFinishDelay()}`;
+ iframe.srcdoc = `<script>window.open("${download_link}", ${options})</scr` +
+ `ipt>`;
+ iframe.sandbox = "allow-same-origin allow-popups allow-scripts";
+ AssertDownloadFailure(t, download_token, StreamDownloadFinishDelay() +
+ DownloadVerifyDelay());
+ document.body.appendChild(iframe);
+ }, `window.open(download, ${options}) triggering download in ` +
+ 'sandbox is blocked.'));
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_01.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_01.html
new file mode 100644
index 0000000000..900d8cd022
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_01.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>moving modified IFRAME in document (original page about:blank, DOM modification)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#iframe-load-event-steps">
+<iframe src="about:blank"></iframe>
+<div id="target"></div>
+<script>
+setup({ single_test: true });
+onload = function() {
+ var ifr = document.getElementsByTagName('iframe')[0];
+ ifr.contentDocument.body.appendChild(ifr.contentDocument.createElement('p')).textContent = 'Modified document';
+ setTimeout(function() {
+ ifr.onload = function() {
+ assert_equals(ifr.contentDocument.body.textContent.indexOf('Modified'), -1);
+ done();
+ };
+ document.getElementById('target').appendChild(ifr);
+ }, 100);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_02.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_02.html
new file mode 100644
index 0000000000..d82dafe3ce
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_02.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>moving modified IFRAME in document (original page about:blank, document.write modification)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#iframe-load-event-steps">
+<iframe src="about:blank"></iframe>
+<div id="target"></div>
+<script>
+setup({ single_test: true });
+onload = function() {
+ var ifr = document.getElementsByTagName('iframe')[0];
+ ifr.contentDocument.open();
+ ifr.contentDocument.write('Modified document');
+ ifr.contentDocument.close();
+ setTimeout(function() {
+ ifr.onload = function() {
+ assert_equals(ifr.contentDocument.body.textContent.indexOf('Modified'), -1);
+ done();
+ };
+ document.getElementById('target').appendChild(ifr);
+ }, 100);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_03.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_03.html
new file mode 100644
index 0000000000..5db77ad890
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_03.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>moving modified IFRAME in document (original page from server, DOM modification)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#iframe-load-event-steps">
+<iframe src="support/blank.htm"></iframe>
+<div id="target"></div>
+<script>
+setup({ single_test: true });
+onload = function() {
+ var ifr = document.getElementsByTagName('iframe')[0];
+ ifr.contentDocument.body.appendChild(ifr.contentDocument.createElement('p')).textContent = 'Modified document';
+ setTimeout(function() {
+ ifr.onload = function() {
+ assert_equals(ifr.contentDocument.body.textContent.indexOf('Modified'), -1);
+ done();
+ };
+ document.getElementById('target').appendChild(ifr);
+ }, 100);
+}
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_04.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_04.html
new file mode 100644
index 0000000000..bd076948ab
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/move_iframe_in_dom_04.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>moving modified IFRAME in document (original page from server, document.write modification)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#iframe-load-event-steps">
+<iframe src="support/blank.htm"></iframe>
+<div id="target"></div>
+<script>
+setup({ single_test: true });
+onload = function(){
+ var ifr = document.getElementsByTagName('iframe')[0];
+ ifr.contentDocument.open();
+ ifr.contentDocument.write('Modified document');
+ ifr.contentDocument.close();
+ setTimeout(function() {
+ ifr.onload = function () {
+ assert_equals(ifr.contentDocument.body.textContent.indexOf('Modified'), -1);
+ done();
+ };
+ document.getElementById('target').appendChild(ifr);
+ }, 100);
+}
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/multiple-iframes-with-allow-scripts-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/multiple-iframes-with-allow-scripts-crash.html
new file mode 100644
index 0000000000..572f119be8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/multiple-iframes-with-allow-scripts-crash.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <style>
+ iframe {
+ margin: 10px;
+ float: left;
+ width: 300px;
+ height: 300px;
+ border: none;
+ }
+ </style>
+ <iframe sandbox="allow-scripts" src="resources/hello-world.html"> </iframe>
+ <iframe sandbox="allow-scripts" src="resources/hello-world.html"> </iframe>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/empty.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/empty.html
new file mode 100644
index 0000000000..763b0739be
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/empty.html
@@ -0,0 +1 @@
+<!DOCTYPE html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/hello-world.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/hello-world.html
new file mode 100644
index 0000000000..0d1111b48c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/hello-world.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+ * { color: 'green'; }
+ </style>
+ </head>
+ <body>
+ <div><span style="color: green">Hello world!</span></div>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/iframe-loading-lazy-in-viewport.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/iframe-loading-lazy-in-viewport.html
new file mode 100644
index 0000000000..1efd2bd056
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/iframe-loading-lazy-in-viewport.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<iframe id="iframe" loading="lazy" src="subframe.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/post-origin-to-opener.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/post-origin-to-opener.html
new file mode 100644
index 0000000000..bb625942f8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/post-origin-to-opener.html
@@ -0,0 +1,3 @@
+<script>
+ opener.postMessage({origin: window.origin}, "*");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/sandbox-top-navigation-helper.js b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/sandbox-top-navigation-helper.js
new file mode 100644
index 0000000000..413f392dfc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/sandbox-top-navigation-helper.js
@@ -0,0 +1,76 @@
+// To use this file, use the following imports:
+// // META: script=/common/dispatcher/dispatcher.js
+// // META: script=/common/get-host-info.sub.js
+// // META: script=/common/utils.js
+// // META: script=/resources/testdriver.js
+// // META: script=/resources/testdriver-vendor.js
+// // META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// // META: script=./resources/sandbox-top-navigation-helper.js
+
+// Helper file that provides various functions to test top-level navigation
+// with various frame and sandbox flag configurations.
+
+async function createNestedIframe(parent, origin, frame_sandbox, header_sandbox)
+{
+ let headers = [];
+ if (header_sandbox) {
+ headers.push([
+ "Content-Security-Policy",
+ "sandbox allow-scripts " + header_sandbox
+ ]);
+ }
+ let iframe_attributes = {};
+ if (frame_sandbox) {
+ iframe_attributes.sandbox = "allow-scripts " + frame_sandbox;
+ }
+ return parent.addIframe({
+ origin: origin,
+ scripts: [
+ '/resources/testdriver.js',
+ '/resources/testdriver-driver.js',
+ '/resources/testdriver-vendor.js'
+ ],
+ headers: headers,
+ }, iframe_attributes);
+}
+
+async function attemptTopNavigation(iframe, should_succeed) {
+ let did_succeed;
+ try {
+ await iframe.executeScript(() => {
+ window.top.location.href = "https://google.com";
+ });
+ did_succeed = true;
+ } catch (e) {
+ did_succeed = false;
+ }
+
+ assert_equals(did_succeed, should_succeed,
+ should_succeed ?
+ "The navigation should succeed." :
+ "The navigation should fail.");
+}
+
+async function setupTest() {
+ const rcHelper = new RemoteContextHelper();
+ return rcHelper.addWindow(/*config=*/ null, /*options=*/ {});
+}
+
+async function activate(iframe) {
+ return iframe.executeScript(async () => {
+ let b = document.createElement("button");
+ document.body.appendChild(b);
+
+ // Since test_driver.bless() does not play nicely with the remote context
+ // helper, this is a workaround to trigger user activation in the iframe.
+ // This adds a button to the iframe and then simulates hitting the 'tab' key
+ // twice. Once to focus on the button, and once to trigger user activation
+ // in the iframe (user activation is given to the frame that has focus when
+ // the tab key is pressed, not the frame that ends up getting focus). Note
+ // that this will result in both the parent and this frame getting user
+ // activation. Note that this currently only works for iframes nested 1
+ // level deep.
+ test_driver.set_test_context(window.top);
+ return test_driver.send_keys(document.body, "\uE004\uE004");
+ });
+}
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/subframe.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/subframe.html
new file mode 100644
index 0000000000..07cb999afa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/subframe.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+ <p>Subframe</p>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/unload-reporter.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/unload-reporter.html
new file mode 100644
index 0000000000..18599b2a6e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/resources/unload-reporter.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<h1>I'll report to my parent when I'm unloaded</h1>
+
+<script>
+ window.onbeforeunload = e => {
+ parent.postMessage('unloading', '*');
+ };
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/same_origin_child.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/same_origin_child.html
new file mode 100644
index 0000000000..a36e231fa2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/same_origin_child.html
@@ -0,0 +1,12 @@
+<script src="iframe_harness.js"></script>
+<body>
+ <iframe src="same_origin_grandchild.html"></iframe>
+</body>
+<script>
+ send_test_results({
+ "id": '08782f28-e313-47ae-8cd7-419f3e194b0a',
+ "parent": window.parent !== window,
+ "grandparent": window.parent.parent === window.parent,
+ "top": window.top === window.parent,
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/same_origin_grandchild.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/same_origin_grandchild.html
new file mode 100644
index 0000000000..e7a2293b76
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/same_origin_grandchild.html
@@ -0,0 +1,11 @@
+<script src="iframe_harness.js"></script>
+<body>
+</body>
+<script>
+ send_test_results({
+ "id": '66de8d44-7da7-47c7-9a52-41cba4f22bfe',
+ "parent": window.parent !== window,
+ "grandparent": window.parent.parent !== window.parent,
+ "top": window.top === window.parent.parent,
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/same_origin_parentage.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/same_origin_parentage.html
new file mode 100644
index 0000000000..a163eb8eec
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/same_origin_parentage.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Check the frame heriarchy</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="iframe_harness.js"></script>
+<body>
+ <iframe src="same_origin_child.html"></iframe>
+</body>
+<script>
+ get_test_results('17381dae-9c3e-4661-9f2b-28eb07a5f2fc');
+ get_test_results('08782f28-e313-47ae-8cd7-419f3e194b0a');
+ get_test_results('66de8d44-7da7-47c7-9a52-41cba4f22bfe');
+ send_test_results({
+ "id": '17381dae-9c3e-4661-9f2b-28eb07a5f2fc',
+ "parent": window.parent === window,
+ "top": window.top === window,
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-ascii-case-insensitive.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-ascii-case-insensitive.html
new file mode 100644
index 0000000000..fd77bfa42f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-ascii-case-insensitive.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>iframe 'sandbox' ASCII case insensitive</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+async_test(function(t) {
+ let iframe = document.createElement('iframe');
+ iframe.setAttribute('sandbox', 'allow-same-or\u0130gin');
+ iframe.setAttribute('hidden', '');
+
+ assert_true(iframe.sandbox.supports('allow-same-origin'), 'supports the allow-same-origin token');
+
+ iframe.src = 'support/blank.htm';
+ iframe.onload = t.step_func_done(function() {
+ try {
+ assert_equals(iframe.contentDocument, null, 'child document not reachable');
+ } catch (e) {
+ // The assert_equals throwing is a pass.
+ }
+ });
+ document.body.appendChild(iframe);
+}, document.title + ', allow-same-or\u0130gin');
+
+async_test(function(t) {
+ let iframe = document.createElement('iframe');
+ iframe.setAttribute('sandbox', 'allow-\u017Fcripts');
+ iframe.setAttribute('hidden', '');
+
+ assert_true(iframe.sandbox.supports('allow-scripts'), 'supports the allow-scripts token');
+
+ window.onmessage = t.unreached_func('no scripts should run in the iframe');
+ iframe.src = 'support/sandbox_allow_script.html';
+ iframe.onload = t.step_func(function() {
+ t.step_timeout(t.step_func_done(), 100);
+ });
+ document.body.appendChild(iframe);
+}, document.title + ', allow-\u017Fcripts');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-inherit-to-blank-document-unsandboxed-frame.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-inherit-to-blank-document-unsandboxed-frame.html
new file mode 100644
index 0000000000..0f35f28709
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-inherit-to-blank-document-unsandboxed-frame.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+ // Sandbox flags are inherited from a document toward every frame it creates,
+ // which then is inherited to every new document created in this frame.
+ //
+ // Using the flag 'allow-popups-to-escape-sandbox' inhibits this inheritance
+ // mechanism when the new frame is a popup.
+ //
+ // Sandbox flags are not inherited from the initiator/creator when loading a
+ // local scheme document unlike CSP (tested in
+ // ./sandbox-inherit-to-blank-document-unsandboxed.html)
+ //
+ // This tests in particular the initial empty document and the first
+ // about:blank navigation and verifies that no sandbox is applied on the
+ // popups.
+ promise_test(async test => {
+ const msg = await new Promise(r => window.addEventListener("message", r));
+ assert_false(msg.data.access_initial_navigation_to_about_blank_throws,
+ "Failed to access initial about:blank popup, it is probably sandboxed"
+ );
+ assert_false(msg.data.access_first_navigation_to_about_blank_throws,
+ "Failed to access navigation to about:blank, it is probably sandboxed"
+ );
+ assert_false(msg.data.access_after_delay_initial_navigation_to_about_blank_throws,
+ "Failed to access navigation to about:blank, it is probably sandboxed"
+ );
+ assert_false(msg.data.access_after_delay_first_navigation_to_about_blank_throws,
+ "Failed to access navigation to about:blank, it is probably sandboxed"
+ );
+ }, "Popup do not inherit sandbox, because of " +
+ "'allow-popups-to-escape-sandbox'. The document isn't sandboxed.")
+
+</script>
+<iframe
+ sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox"
+ srcdoc="
+ <script>
+ let access_initial_navigation_to_about_blank_throws = false;
+ let access_first_navigation_to_about_blank_throws = false;
+ let access_after_delay_initial_navigation_to_about_blank_throws = false;
+ let access_after_delay_first_navigation_to_about_blank_throws = false;
+ const initial_about_blank_window =
+ window.open('/common/blank.html?pipe=status(204)');
+ try {
+ initial_about_blank_window.origin;
+ } catch(e) {
+ access_initial_navigation_to_about_blank_throws = true;
+ }
+ const renavigated_about_blank_window = window.open('about:blank');
+ try {
+ renavigated_about_blank_window.origin;
+ } catch(e) {
+ access_first_navigation_to_about_blank_throws = true;
+ }
+ setTimeout(() => {
+ try {
+ initial_about_blank_window.origin;
+ } catch(e) {
+ access_after_delay_initial_navigation_to_about_blank_throws = true;
+ }
+ try {
+ renavigated_about_blank_window.origin;
+ } catch(e) {
+ access_after_delay_first_navigation_to_about_blank_throws = true;
+ }
+ top.postMessage({
+ 'access_initial_navigation_to_about_blank_throws':
+ access_initial_navigation_to_about_blank_throws,
+ 'access_first_navigation_to_about_blank_throws':
+ access_first_navigation_to_about_blank_throws,
+ 'access_after_delay_initial_navigation_to_about_blank_throws':
+ access_after_delay_initial_navigation_to_about_blank_throws,
+ 'access_after_delay_first_navigation_to_about_blank_throws':
+ access_after_delay_first_navigation_to_about_blank_throws
+ }, '*');
+ }, 500);
+ </script>"
+>
+</iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-inherit-to-blank-document-unsandboxed.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-inherit-to-blank-document-unsandboxed.html
new file mode 100644
index 0000000000..2c6f0bd6a7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-inherit-to-blank-document-unsandboxed.html
@@ -0,0 +1,100 @@
+<!--
+Content-Security-Policy: sandbox allow-scripts
+ allow-popups
+ allow-popups-to-escape-sandbox
+-->
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+
+<script>
+
+// Sandbox flags are inherited from a document toward every frame it creates,
+// which then is inherited to every new document created in this frame.
+
+// Using the flag 'allow-popups-to-escape-sandbox' inhibits this inheritance
+// mechanism when the new frame is a popup.
+//
+// Sandbox flags can also be set via CSP. CSP are inherited from a document
+// toward every other documents its creates that are loading with a local scheme.
+// In particular, this includes:
+// - The initial empty document
+// - The first about:blank navigation. See (note)
+// - Any about:blank navigation.
+//
+// Both mechanism are at play here.
+//
+// Note: As of 2021, Chrome handles the very first navigation to about:blank in
+// a frame synchronously instead of asynchronously. This is the only navigation
+// behaving this way. As a result, inheritance of sandbox is different and needs
+// to be tested separately.
+// See also:
+// https://docs.google.com/document/d/1KY0DCaoKjUPbOX28N9KWvBjbnAfQEIRTaLbZUq9EkK8
+
+test(test => {
+ assert_equals(window.origin, 'null');
+}, "Document is sandboxed via its CSP.");
+
+promise_test(async test => {
+ // The navigation will be canceled (204 no content). As a result, the
+ // document in the popup must still be the initial empty document.
+ const w = window.open("/common/blank.html?pipe=status(204)");
+
+ // The initial empty document is sandboxed, because it inherited CSP from
+ // its opener. However this is impossible to verify. There are cross-origin
+ // access restrictions and an about:blank document can't do much on its own.
+ // We try to identify that the document is sandboxed by accessing a
+ // cross-origin restricted API.
+ assert_throws_dom(
+ "SecurityError", () => { w.origin },
+ "Access before timeout throws");
+
+ // Test after a 500ms timeout, delay after which we expect asynchronous
+ // navigations to be canceled.
+ await new Promise(r => setTimeout(r, 500) );
+
+ // The about:blank must still be sandboxed.
+ assert_throws_dom(
+ "SecurityError", () => { w.origin },
+ "Access after timeout throws");
+}, "The initial empty document inherit sandbox via CSP.");
+
+// Regression test for https://crbug.com/1190065
+promise_test(async test => {
+ const w = window.open("about:blank");
+
+ // The about:blank document is sandboxed, because it inherited CSP from its
+ // opener. However this is impossible to verify. There are cross-origin
+ // access restrictions and an about:blank document can't do much on its own.
+ // We try to identify that the document is sandboxed by accessing a
+ // cross-origin restricted API.
+ assert_throws_dom(
+ "SecurityError", () => { w.origin },
+ "Access before timeout throws");
+
+ // Test after a 500ms timeout, delay after which we expect asynchronous
+ // about:blank navigation to be completed.
+ await new Promise(r => setTimeout(r, 500) );
+
+ // The about:blank must still be sandboxed.
+ assert_throws_dom(
+ "SecurityError", () => { w.origin },
+ "Access after timeout throws");
+}, "The synchronous re-navigation to about:blank inherits sandbox via CSP");
+
+async_test(test => {
+ window.addEventListener("message", test.step_func_done(e => {
+ assert_equals(e.data.origin, (new URL(location)).origin,
+ "popup is not sandboxed");
+ }));
+ window.open("./resources/post-origin-to-opener.html");
+}, "Popup do not inherit sandbox, because of 'allow-popups-to-escape-sandbox'" +
+ " the document doesn't inherit CSP. The document isn't sandboxed")
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-inherit-to-blank-document-unsandboxed.html.headers b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-inherit-to-blank-document-unsandboxed.html.headers
new file mode 100644
index 0000000000..9850d21f3c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-inherit-to-blank-document-unsandboxed.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts allow-popups allow-popups-to-escape-sandbox
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-toggle-in-inactive-document-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-toggle-in-inactive-document-crash.html
new file mode 100644
index 0000000000..654542f6a8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-toggle-in-inactive-document-crash.html
@@ -0,0 +1,9 @@
+<body>
+<iframe id="i"></iframe>
+<script>
+var saved_i = i;
+var saved_i_doc = i.contentDocument;
+i.remove();
+saved_i_doc.adoptNode(saved_i);
+saved_i.sandbox.toggle("1");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child-special-cases.tentative.sub.window.js b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child-special-cases.tentative.sub.window.js
new file mode 100644
index 0000000000..8681411dd7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child-special-cases.tentative.sub.window.js
@@ -0,0 +1,47 @@
+// META: title=Top-level navigation tests with cross origin & user activated child frames
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=./resources/sandbox-top-navigation-helper.js
+
+'use strict';
+
+/* ------------------------- USER ACTIVATION TESTS ------------------------- */
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_ORIGIN", "allow-top-navigation-by-user-activation", "");
+ await activate(iframe_1);
+
+ await attemptTopNavigation(iframe_1, true);
+}, "Allow top with user activation + user activation");
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_ORIGIN", "allow-top-navigation-by-user-activation", "");
+
+ await attemptTopNavigation(iframe_1, false);
+}, "allow-top-navigation-by-user-activation set but no sticky activation");
+
+/* ---------------------- CROSS ORIGIN (A -> B) TESTS ---------------------- */
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_REMOTE_ORIGIN", "allow-top-navigation", "");
+
+ await attemptTopNavigation(iframe_1, true);
+}, "A cross-origin frame with frame sandbox flags can navigate top");
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_REMOTE_ORIGIN", "", "allow-top-navigation");
+
+ await attemptTopNavigation(iframe_1, false);
+}, "A cross-origin frame with delivered sandbox flags can not navigate top");
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child.tentative.sub.window.js b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child.tentative.sub.window.js
new file mode 100644
index 0000000000..53faa99a40
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child.tentative.sub.window.js
@@ -0,0 +1,56 @@
+// META: title=Top-level navigation tests with child frames
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=./resources/sandbox-top-navigation-helper.js
+
+'use strict';
+
+/* ----------------------- SAME ORIGIN (A -> A) TESTS ----------------------- */
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_ORIGIN", "", "allow-top-navigation allow-same-origin");
+
+ await attemptTopNavigation(iframe_1, true);
+}, "A same-origin frame with delivered sandbox flags can navigate top");
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_ORIGIN", "allow-top-navigation allow-same-origin", "");
+
+ await attemptTopNavigation(iframe_1, true);
+}, "A same-origin frame with frame sandbox flags can navigate top");
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_ORIGIN", "", "");
+
+ await attemptTopNavigation(iframe_1, true);
+}, "A same-origin unsandboxed frame can navigate top");
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_ORIGIN", "",
+ "allow-top-navigation allow-top-navigation-by-user-activation allow-same-origin");
+
+ await attemptTopNavigation(iframe_1, true);
+}, "A frame with both top navigation delivered sandbox flags uses the less \
+ restrictive one");
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_ORIGIN",
+ "allow-top-navigation allow-top-navigation-by-user-activation", "");
+
+ await attemptTopNavigation(iframe_1, true);
+}, "A frame with both top navigation frame sandbox flags uses the less \
+ restrictive one");
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-escalate-privileges.tentative.sub.window.js b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-escalate-privileges.tentative.sub.window.js
new file mode 100644
index 0000000000..a5cda9b0b9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-escalate-privileges.tentative.sub.window.js
@@ -0,0 +1,63 @@
+// META: title=Top-level navigation tests with frames that try to give themselves top-nav permission
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=./resources/sandbox-top-navigation-helper.js
+
+'use strict';
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_REMOTE_ORIGIN", "", "");
+ const iframe_2 = await createNestedIframe(iframe_1,
+ "HTTP_REMOTE_ORIGIN", "allow-top-navigation", "");
+
+ await attemptTopNavigation(iframe_2, false);
+}, "A cross origin unsandboxed frame can't escalate privileges in a child \
+ frame");
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_REMOTE_ORIGIN", "allow-top-navigation", "");
+ const iframe_2 = await createNestedIframe(iframe_1,
+ "OTHER_ORIGIN", "", "");
+
+ await attemptTopNavigation(iframe_2, true);
+}, "An unsandboxed grandchild inherits its parents ability to navigate top.");
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_ORIGIN", "", "");
+ const iframe_2 = await createNestedIframe(iframe_1,
+ "HTTP_ORIGIN", "allow-top-navigation", "");
+
+ await attemptTopNavigation(iframe_2, true);
+}, "A same-origin grandchild with frame allow-top can navigate top");
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_ORIGIN", "", "");
+ const iframe_2 = await createNestedIframe(iframe_1,
+ "HTTP_ORIGIN", "", "allow-top-navigation");
+
+ await attemptTopNavigation(iframe_2, false);
+}, "A sandboxed same-origin grandchild without allow-same-origin can't \
+ escalate its own top-nav privileges");
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_ORIGIN", "", "");
+ const iframe_2 = await createNestedIframe(iframe_1,
+ "HTTP_ORIGIN", "", "allow-same-origin allow-top-navigation");
+
+ await attemptTopNavigation(iframe_2, true);
+}, "A sandboxed same-origin grandchild with allow-same-origin can \
+ give itself top-nav privileges");
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-grandchild.tentative.sub.window.js b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-grandchild.tentative.sub.window.js
new file mode 100644
index 0000000000..a07148f802
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-grandchild.tentative.sub.window.js
@@ -0,0 +1,50 @@
+// META: title=Top-level navigation tests with grandchild frames
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=./resources/sandbox-top-navigation-helper.js
+
+'use strict';
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_ORIGIN", "", "");
+ const iframe_2 = await createNestedIframe(iframe_1,
+ "HTTP_ORIGIN", "allow-scripts", "");
+
+ await attemptTopNavigation(iframe_2, false);
+}, "A fully sandboxed same-origin grandchild can't navigate top");
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_ORIGIN", "", "");
+ const iframe_2 = await createNestedIframe(iframe_1,
+ "HTTP_ORIGIN", "", "");
+
+ await attemptTopNavigation(iframe_2, true);
+}, "An unsandboxed same-origin grandchild can navigate top");
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_REMOTE_ORIGIN", "", "");
+ const iframe_2 = await createNestedIframe(iframe_1,
+ "HTTP_ORIGIN", "", "");
+
+ await attemptTopNavigation(iframe_2, true);
+}, "A same-origin grandchild in a cross-origin parent can navigate top");
+
+promise_test(async t => {
+ const main = await setupTest();
+ const iframe_1 = await createNestedIframe(main,
+ "HTTP_REMOTE_ORIGIN", "", "");
+ const iframe_2 = await createNestedIframe(iframe_1,
+ "HTTP_ORIGIN", "allow-top-navigation allow-same-origin", "");
+
+ await attemptTopNavigation(iframe_2, true);
+}, "A same-origin sandboxed grandchild in a cross-origin parent can navigate top"); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_001.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_001.htm
new file mode 100644
index 0000000000..97af61163a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_001.htm
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: Allow script execution inside iframe with sandbox attribute when sandbox="allow-scripts".</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-scripts-browsing-context-flag" />
+ <meta name="assert" content="Allow script execution inside iframe with sandbox attribute when sandbox='allow-scripts'." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("Allow script execution inside iframe with sandbox attribute when sandbox='allow-scripts'.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "script ran");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 8000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <iframe src="support/iframe_sandbox_001.htm" sandbox="allow-scripts" style="display: none"></iframe>
+ <div id=log></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_002.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_002.htm
new file mode 100644
index 0000000000..4291674275
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_002.htm
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: Allow autoplay for HTML5 Video inside iframe with sandbox attribute if sandbox='allow-scripts'.</title>
+ <meta name=timeout content=long>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script>
+ async_test(function (t) {
+ var callback = t.step_func_done(function(event) {
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "play event fired");
+ });
+
+ window.addEventListener("message", callback, false);
+ }, "Allow autoplay for HTML5 Video inside iframe with sandbox attribute if sandbox='allow-scripts'.");
+ </script>
+ <iframe src="support/iframe_sandbox_002.htm" sandbox="allow-scripts" style="display: none"></iframe>
+ <div id=log></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_003-manual.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_003-manual.htm
new file mode 100644
index 0000000000..6363900bed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_003-manual.htm
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: Block autofocus on form control inside iframe with sandbox attribute.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-automatic-features-browsing-context-flag" />
+ <meta name="assert" content="Block autofocus on form control inside iframe with sandbox attribute." />
+ <script src="support/sandbox_helper.js" type="text/javascript"></script>
+</head>
+<body>
+ <pre>Description: Block autofocus on form controls inside iframe with sandbox attribute.</pre>
+ <table id='testtable' border='1'>
+ <tr>
+ <td>Test Result</td>
+ <td>Test Assertion</td>
+ </tr>
+ <tr>
+ <td id='test_0_result'>Manual</td>
+ <td id='test_0_assertion'>Test passes if caret (text cursor) is not on the textbox in the below iframe.</td>
+ </tr>
+ </table>
+ <br />
+ <div id="testframe">
+ <pre>iframe with sandbox</pre>
+ <iframe src="support/iframe_sandbox_003.htm" sandbox style="height: 100px; width: 400px;"></iframe>
+ </div>
+ <script type="text/javascript">
+ DisableTestForNonSupportingBrowsers();
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_004.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_004.htm
new file mode 100644
index 0000000000..08b32f0158
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_004.htm
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Sandbox: Block plugins inside iframe with sandbox attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!--
+ https://github.com/whatwg/html/issues/3958
+ https://github.com/whatwg/html/pull/6946
+-->
+
+<iframe sandbox="allow-same-origin" src="support/iframe_sandbox_004.htm" height="400" width ="600"></iframe>
+
+<script>
+"use strict";
+setup({ explicit_done: true });
+
+window.onload = () => {
+ test(() => {
+ const object = document.querySelector("iframe").contentWindow.document.querySelector("object");
+ const rect = object.getBoundingClientRect();
+ assert_less_than(rect.width, 300);
+ assert_less_than(rect.height, 300);
+ }, "Fallback content is always displayed for sandboxed PDFs");
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_005.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_005.htm
new file mode 100644
index 0000000000..7b1e60e7e9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_005.htm
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: Block script execution inside iframe with sandbox attribute.</title>
+ <meta name="timeout" content="long">
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-scripts-browsing-context-flag" />
+ <meta name="assert" content="Block script execution inside iframe with sandbox attribute." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("Block script execution inside iframe with sandbox attribute.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_true(!event);
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <iframe src="support/iframe_sandbox_001.htm" sandbox style="display: none"></iframe>
+ <div id=log></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_006-manual.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_006-manual.htm
new file mode 100644
index 0000000000..1935f439f8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_006-manual.htm
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Sandbox: Allow form submission inside sandbox iframe when sandbox='allow-forms'</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-forms-browsing-context-flag" />
+ <meta name="assert" content="Allow form submission inside sandbox iframe when sandbox='allow-forms'." />
+ <script src="support/sandbox_helper.js" type="text/javascript"></script>
+</head>
+<body>
+ <pre>Description: Allow form submission inside iframe with sandbox attribute if sandbox='allow-forms'.</pre>
+ <table id='testtable' border='1'>
+ <tr>
+ <td>Test Result</td>
+ <td>Test Assertion</td>
+ </tr>
+ <tr>
+ <td id='test_0_result'>Manual</td>
+ <td id='test_0_assertion'>
+ <div>Steps:</div>
+ <div>1. Click button "Submit Form".</div>
+ <br />
+ <div>Test passes if there is no red on the page and if the word "PASS" appears in the below iframe after following the above steps.</div>
+ </td>
+ </tr>
+ </table>
+ <br />
+ <div id="testframe">
+ <pre>iframe with sandbox="allow-forms"</pre>
+ <iframe src="support/iframe_sandbox_006.htm" sandbox="allow-forms" style="height: 100px; width: 300px;"></iframe>
+ </div>
+ <script type="text/javascript">
+ DisableTestForNonSupportingBrowsers();
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_007-manual.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_007-manual.htm
new file mode 100644
index 0000000000..dfed9a6f0f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_007-manual.htm
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Sandbox: Block form submission inside sandbox iframe</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-forms-browsing-context-flag" />
+ <meta name="assert" content="Block form submission inside sandbox iframe." />
+ <script src="support/sandbox_helper.js" type="text/javascript"></script>
+</head>
+<body>
+ <pre>Description: Block form submission inside iframe with sandbox attribute.</pre>
+ <table id='testtable' border='1'>
+ <tr>
+ <td>Test Result</td>
+ <td>Test Assertion</td>
+ </tr>
+ <tr>
+ <td id='test_0_result'>Manual</td>
+ <td id='test_0_assertion'>
+ <div>Steps:</div>
+ <div>1. Click button "Submit Form".</div>
+ <br />
+ <div>Test passes if there is no red on the page and there is no navigation in the below iframe after following the above steps.</div>
+ </td>
+ </tr>
+ </table>
+ <br />
+ <div id="testframe">
+ <pre>iframe with sandbox="allow-scripts allow-same-origin allow-top-navigation"</pre>
+ <iframe src="support/iframe_sandbox_007.htm" sandbox="allow-scripts allow-same-origin allow-top-navigation" style="height: 100px; width: 300px;"></iframe>
+ </div>
+ <script type="text/javascript">
+ DisableTestForNonSupportingBrowsers();
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_008-manual.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_008-manual.htm
new file mode 100644
index 0000000000..9e479a7899
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_008-manual.htm
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: Allow sandboxed iframe content to navigate the sandboxed browsing context itself.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-navigation-browsing-context-flag" />
+ <meta name="assert" content="Allow sandboxed iframe content to navigate the sandboxed browsing context itself." />
+ <script src="support/sandbox_helper.js" type="text/javascript"></script>
+</head>
+<body>
+ <pre>Description: Allow sandboxed iframe content to navigate the sandboxed browsing context itself.</pre>
+ <table id='testtable' border='1'>
+ <tr>
+ <td>Test Result</td>
+ <td>Test Assertion</td>
+ </tr>
+ <tr>
+ <td id='test_0_result'>Manual</td>
+ <td id='test_0_assertion'>
+ <div>Steps:</div>
+ <div>1. Click link "Click here to perform self navigation".</div>
+ <br />
+ <div>Test passes if there is no red on the page and the word "PASS" appears in the below iframe after following the above steps.</div>
+ </td>
+ </tr>
+ </table>
+ <br />
+ <div id="testframe">
+ <pre>iframe with sandbox=""</pre>
+ <iframe id="iframe1" name="iframe1" src="support/iframe_sandbox_008.htm" sandbox="" style="height: 100px; width: 350px;"></iframe>
+ </div>
+ <script type="text/javascript">
+ DisableTestForNonSupportingBrowsers();
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_010-manual.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_010-manual.htm
new file mode 100644
index 0000000000..41802be775
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_010-manual.htm
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: Block window.open() API inside iframe with sandbox attribute.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-navigation-browsing-context-flag" />
+ <meta name="assert" content="Block window.open() API inside iframe with sandbox attribute." />
+ <script src="support/sandbox_helper.js" type="text/javascript"></script>
+</head>
+<body>
+ <pre>Description: Block window.open() API inside iframe with sandbox attribute.</pre>
+ <table id='testtable' border='1'>
+ <tr>
+ <td>Test Result</td>
+ <td>Test Assertion</td>
+ </tr>
+ <tr>
+ <td id='test_0_result'>Manual</td>
+ <td id='test_0_assertion'>
+ <div>Steps:</div>
+ <div>1. Click button "Click here to call window.open() API".</div>
+ <br />
+ <div>Test passes if there is no red on the page and no new window opens. The user agent may offer the user the option of allowing a new window to open.</div>
+ </td>
+ </tr>
+ </table>
+ <br />
+ <div id="testframe">
+ <pre>iframe with sandbox="allow-scripts allow-same-origin allow-forms allow-top-navigation"</pre>
+ <iframe src="support/iframe_sandbox_010.htm" sandbox="allow-scripts allow-same-origin allow-forms allow-top-navigation" style="height: 100px; width: 450px;"></iframe>
+ </div>
+ <script type="text/javascript">
+ DisableTestForNonSupportingBrowsers();
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_011.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_011.htm
new file mode 100644
index 0000000000..ce3ee1a7d0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_011.htm
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: iframe sandbox attribute value support DOMTokenList interface.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#the-iframe-element" />
+ <meta name="assert" content="iframe sandbox attribute value support DOMTokenList interface." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id=log></div>
+ <iframe id="iframe1" src="about:blank" sandbox="allow-scripts allow-same-origin allow-forms" style="display : none"></iframe>
+ <script type="text/javascript">
+
+ test(function() {
+ var iframeEle = document.getElementById("iframe1");
+ assert_equals(iframeEle.sandbox.length, 3)
+ }, "DOMTokenList length")
+
+ test(function() {
+ var iframeEle = document.getElementById("iframe1");
+ assert_equals(iframeEle.sandbox.item(1), "allow-same-origin")
+ }, "DOMTokenList item(index)")
+
+ test(function() {
+ var iframeEle = document.getElementById("iframe1");
+ assert_true(iframeEle.sandbox.contains("allow-forms"))
+ }, "DOMTokenList contains(DomString)")
+
+ test(function() {
+ var iframeEle = document.getElementById("iframe1");
+ iframeEle.sandbox.add("ALLOW-SANDBOX");
+ assert_true(iframeEle.sandbox.contains("ALLOW-SANDBOX"))
+ }, "DOMTokenList add(DomString)")
+
+ test(function() {
+ var iframeEle = document.getElementById("iframe1");
+ iframeEle.sandbox.remove("ALLOW-SANDBOX");
+ assert_false(iframeEle.sandbox.contains("ALLOW-SANDBOX"))
+ }, "DOMTokenList remove(DomString)")
+
+ test(function() {
+ var iframeEle = document.getElementById("iframe1");
+ iframeEle.sandbox.remove("ALLOW-SANDBOX");
+ assert_true(
+ iframeEle.sandbox.toggle("allow-top-navigation") && iframeEle.sandbox.contains("allow-top-navigation") &&
+ !iframeEle.sandbox.toggle("allow-top-navigation") && !iframeEle.sandbox.contains("allow-top-navigation")
+ )
+ }, "DOMTokenList toggle(DomString) - Returns true if token is now present (it was added); returns false if it is not (it was removed).")
+
+ test(function() {
+ var iframeEle = document.getElementById("iframe1");
+ assert_equals(iframeEle.sandbox.value, iframeEle.sandbox.toString())
+ }, "DOMTokenList sandbox.toString()")
+
+ test(function() {
+ var iframeEle = document.getElementById("iframe1");
+ iframeEle.sandbox.remove("ALLOW-SANDBOX");
+ assert_true(iframeEle.sandbox.contains("allow-scripts") != iframeEle.sandbox.contains("Allow-SCRIPTS"))
+ }, "DOMTokenList case sensitivity")
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_012.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_012.htm
new file mode 100644
index 0000000000..7642162d5a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_012.htm
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: value of sandbox attribute must be an unordered set of unique space-separated tokens.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#attr-iframe-sandbox" />
+ <meta name="assert" content="value of sandbox attribute must be an unordered set of unique space-separated tokens." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("value of sandbox attribute must be an unordered set of unique space-separated tokens.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "cookies are R/W");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+
+ <iframe style="display:none" src="support/iframe_sandbox_012.htm" sandbox=" Allow-Scripts Allow-Same-Origin "></iframe>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_013.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_013.htm
new file mode 100644
index 0000000000..c434989706
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_013.htm
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: value of sandbox attribute must be an unordered set of unique space-separated tokens.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#attr-iframe-sandbox" />
+ <meta name="assert" content="value of sandbox attribute must be an unordered set of unique space-separated tokens." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("value of sandbox attribute must be an unordered set of unique space-separated tokens.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "cookies are R/W");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+
+ <iframe style="display:none" src="support/iframe_sandbox_012.htm" sandbox="
+ allow-scripts
+ allow-same-origin
+ "></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_014.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_014.htm
new file mode 100644
index 0000000000..d979c91977
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_014.htm
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: value of sandbox attribute must be an unordered set of unique space-separated tokens.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#attr-iframe-sandbox" />
+ <meta name="assert" content="value of sandbox attribute must be an unordered set of unique space-separated tokens." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("value of sandbox attribute must be an unordered set of unique space-separated tokens.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "cookies are R/W");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+
+ <iframe style="display:none" src="support/iframe_sandbox_012.htm" sandbox=" allow-scripts allow-same-origin "></iframe>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_015.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_015.htm
new file mode 100644
index 0000000000..6fd5405c73
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_015.htm
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: value of sandbox attribute must be an unordered set of unique space-separated tokens.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#attr-iframe-sandbox" />
+ <meta name="assert" content="value of sandbox attribute must be an unordered set of unique space-separated tokens." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("value of sandbox attribute must be an unordered set of unique space-separated tokens.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "cookies are R/W");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+
+ <iframe style="display:none" src="support/iframe_sandbox_012.htm" sandbox="&#32ALLOW-SCRIPTS&#32allow-same-origin&#32"></iframe>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_016.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_016.htm
new file mode 100644
index 0000000000..4c5f043d2b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_016.htm
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: value of sandbox attribute must be an unordered set of unique space-separated tokens.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#attr-iframe-sandbox" />
+ <meta name="assert" content="value of sandbox attribute must be an unordered set of unique space-separated tokens." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("value of sandbox attribute must be an unordered set of unique space-separated tokens.");
+
+ function callback(event)
+ {
+ t.step(function() {
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "cookies are R/W");
+ });
+ t.done();
+ }
+
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+
+ <iframe style="display:none" src="support/iframe_sandbox_012.htm" sandbox="&#13ALLOW-SCRIPTS&#13allow-same-origin&#13"></iframe>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_017.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_017.htm
new file mode 100644
index 0000000000..f46f22e15b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_017.htm
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: value of sandbox attribute must be an unordered set of unique space-separated tokens.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#attr-iframe-sandbox" />
+ <meta name="assert" content="value of sandbox attribute must be an unordered set of unique space-separated tokens." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("value of sandbox attribute must be an unordered set of unique space-separated tokens.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "cookies are R/W");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+
+ <iframe style="display:none" src="support/iframe_sandbox_012.htm" sandbox="&#12ALLOW-SCRIPTS&#12allow-same-origin&#12"></iframe>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_018.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_018.htm
new file mode 100644
index 0000000000..b5e5927ce4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_018.htm
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: value of sandbox attribute must be an unordered set of unique space-separated tokens.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#attr-iframe-sandbox" />
+ <meta name="assert" content="value of sandbox attribute must be an unordered set of unique space-separated tokens." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("value of sandbox attribute must be an unordered set of unique space-separated tokens.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "cookies are R/W");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+
+ <iframe style="display:none" src="support/iframe_sandbox_012.htm" sandbox="&#10ALLOW-SCRIPTS&#10allow-same-origin&#10"></iframe>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_019.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_019.htm
new file mode 100644
index 0000000000..a503531f81
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_019.htm
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: value of sandbox attribute must be an unordered set of unique space-separated tokens.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#attr-iframe-sandbox" />
+ <meta name="assert" content="value of sandbox attribute must be an unordered set of unique space-separated tokens." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("value of sandbox attribute must be an unordered set of unique space-separated tokens.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "cookies are R/W");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+
+ <iframe style="display:none" src="support/iframe_sandbox_012.htm" sandbox="&#9ALLOW-SCRIPTS&#9allow-same-origin&#9"></iframe>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_020-manual.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_020-manual.htm
new file mode 100644
index 0000000000..ed46b3b298
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_020-manual.htm
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: Nested iframes cannot have less sandbox restrictions than their most restrictive ancestor iframe.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#attr-iframe-sandbox" />
+ <meta name="assert" content="Nested iframes cannot have less sandbox restrictions than their most restrictive ancestor iframe." />
+ <script src="support/sandbox_helper.js" type="text/javascript"></script>
+</head>
+<body>
+ <pre>Description: Nested iframes cannot have less sandbox restrictions than their most restrictive ancestor iframe.</pre>
+ <div>This test is to verify script is blocked inside nested iframes if the top-most sandbox iframe has no 'allow-scripts' token.</div>
+ <br />
+ <table id='testtable' border='1'>
+ <tr>
+ <td>Test Result</td>
+ <td>Test Assertion</td>
+ </tr>
+ <tr>
+ <td id='test_0_result'>Manual</td>
+ <td id='test_0_assertion'>Test passes if there is no red on the page.</td>
+ </tr>
+ </table>
+ <br />
+ <div id="testframe">
+ <div style="font-weight:bold">Top-most iframe with sandbox=""</div>
+ <iframe id="iframe1" name="iframe1" src="support/iframe_sandbox_020.htm" sandbox="" style="height: 330px; width: 400px;"></iframe>
+ </div>
+ <script type="text/javascript">
+ DisableTestForNonSupportingBrowsers();
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_021-manual.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_021-manual.htm
new file mode 100644
index 0000000000..0d149d3974
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_021-manual.htm
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: Nested iframes cannot have less sandbox restrictions than their most restrictive ancestor iframe.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#attr-iframe-sandbox" />
+ <meta name="assert" content="Nested iframes cannot have less sandbox restrictions than their most restrictive ancestor iframe." />
+ <script src="support/sandbox_helper.js" type="text/javascript"></script>
+</head>
+<body>
+ <pre>Description: Nested iframes cannot have less sandbox restrictions than their most restrictive ancestor iframe.</pre>
+ <div>This test is to verify script is allowed inside nested iframes if any of the conditions below are true</div>
+ <div>1. both parent sandbox and child sandbox have 'allow-scripts' token.</div>
+ <div>2. parent sandbox has 'allow-scripts' token and nested child iframe has no sandbox attribute.</div>
+ <div>3. parent iframe has no sandbox attribute and child iframe has sandbox='allow-scripts' token.</div>
+ <div>4. both parent and child iframes have no sandbox attribute.</div>
+ <br />
+ <table id='testtable' border='1'>
+ <tr>
+ <td>Test Result</td>
+ <td>Test Assertion</td>
+ </tr>
+ <tr>
+ <td id='test_0_result'>Manual</td>
+ <td id='test_0_assertion'>Test passes if there is no red on the page.</td>
+ </tr>
+ </table>
+ <br />
+ <div id="testframe">
+ <div style="float: left; border: 1px solid; padding: 5px;">
+ <div style="font-weight: bold">Top-most iframe with sandbox="allow-scripts"</div>
+ <iframe id="iframe1" src="support/iframe_sandbox_021.htm" sandbox="allow-scripts" style="height: 330px; width: 400px;"></iframe>
+ </div>
+ <div style="float: left; border: 1px solid; padding: 5px; margin-left: 20px;">
+ <div style="font-weight: bold">Top-most iframe without sandbox attribute</div>
+ <iframe id="iframe2" src="support/iframe_sandbox_021.htm" style="height: 330px; width: 400px;"></iframe>
+ </div>
+ </div>
+ <script type="text/javascript">
+ DisableTestForNonSupportingBrowsers();
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_022-manual.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_022-manual.htm
new file mode 100644
index 0000000000..ebfca06a61
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_022-manual.htm
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: allow sandbox iframe to navigate their top-level browsing context if sandbox="allow-top-navigation".</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-scripts-browsing-context-flag" />
+ <meta name="assert" content="Allow sandbox iframe to navigate their top-level browsing context if sandbox='allow-top-navigation'." />
+ <script src="support/sandbox_helper.js" type="text/javascript"></script>
+</head>
+<body>
+ <pre>Description: Allow sandbox iframe to navigate its top-level browsing context if sandbox='allow-top-navigation'.</pre>
+ <table id='testtable' border='1'>
+ <tr>
+ <td>Test Result</td>
+ <td>Test Assertion</td>
+ </tr>
+ <tr>
+ <td id='test_0_result'>Manual</td>
+ <td id='test_0_assertion'>
+ <div>Steps:</div>
+ <div>1. Click link "Open the link in top window".</div>
+ <br />
+ <div>Test passes if there is no red on the page and no top-level navigation after following the above steps.</div>
+ </td>
+ </tr>
+ </table>
+ <br />
+ <div id="testframe">
+ <pre>iframe with sandbox="allow-top-navigation"</pre>
+ <iframe src="support/iframe_sandbox_022.htm" sandbox="allow-top-navigation" style="height: 100px; width: 450px;"></iframe>
+ </div>
+ <script type="text/javascript">
+ DisableTestForNonSupportingBrowsers();
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_023.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_023.htm
new file mode 100644
index 0000000000..78cf35d2bb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_023.htm
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Sandbox: Allow sandbox iframe to access other content from the same origin if sandbox="allow-same-origin".</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-origin-browsing-context-flag" />
+ <meta name="assert" content=" Allow sandbox iframe to access other content from the same origin if sandbox='allow-same-origin'." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("Allow sandbox iframe to access other content from the same origin if sandbox='allow-same-origin'");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "window.parent.document");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <iframe src="support/iframe_sandbox_023.htm" sandbox="allow-scripts allow-same-origin" style="display:none"></iframe>
+ <div id=log></div>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_024.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_024.htm
new file mode 100644
index 0000000000..530162e5b3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_024.htm
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Sandbox: document.cookie access is allowed inside iframe with sandbox="allow-same-origin".</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-origin-browsing-context-flag" />
+ <meta name="assert" content="document.cookie access is allowed inside iframe with sandbox='allow-same-origin'." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("document.cookie access is allowed inside iframe with sandbox='allow-same-origin'.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "cookies are R/W");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+ <iframe src="support/iframe_sandbox_024.htm" sandbox="allow-scripts allow-same-origin" style="display:none"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_025.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_025.htm
new file mode 100644
index 0000000000..96783062bf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_025.htm
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: Allow parent content to access sandbox child iframe content when sandbox='allow-same-origin</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-origin-browsing-context-flag" />
+ <meta name="assert" content="Allow parent content to access sandbox child iframe content when sandbox='allow-same-origin'" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("Allow parent content to access sandbox child iframe content when sandbox='allow-same-origin'");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(document.getElementById('sandboxIframe').contentDocument.title, "Page with a message");
+ });
+ t.done();
+ }
+ </script>
+ <div id=log></div>
+
+ <iframe id='sandboxIframe' src="support/standalone-iframe-content.htm" sandbox="allow-same-origin" onload="callback()" style="display : none"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_026.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_026.htm
new file mode 100644
index 0000000000..694a43dad3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_026.htm
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Sandbox: Allow localStorage and sessionStorage access inside iframe with sandbox='allow-same-origin allow-scripts'.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-origin-browsing-context-flag" />
+ <meta name="assert" content="Allow localStorage and sessionStorage access inside iframe with sandbox='allow-same-origin allow-scripts'." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("Allow localStorage and sessionStorage access inside iframe with sandbox='allow-same-origin allow-scripts'");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "access to window.localStorage and window.sessionStorage");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+ <iframe src="support/iframe_sandbox_026.htm" sandbox="allow-scripts allow-same-origin" style="display : none"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_027.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_027.htm
new file mode 100644
index 0000000000..aa28505c5f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_027.htm
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Sandbox: Allow XMLHttpRequest inside iframe with the sandbox attribute if sandbox='allow-same-origin'.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-origin-browsing-context-flag" />
+ <meta name="assert" content="Allow XMLHttpRequest in an iframe with the sandbox attribute if sandbox='allow-same-origin'." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("Allow XMLHttpRequest in an iframe with the sandbox attribute if sandbox='allow-same-origin'.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "access to window.XMLHttpRequest");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+ <iframe src="support/iframe_sandbox_027.htm" sandbox="allow-scripts allow-same-origin" style="display : none"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_028.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_028.htm
new file mode 100644
index 0000000000..76afcdbe3c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_028.htm
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Sandbox: Block sandbox iframe from accessing other content from the same origin.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-origin-browsing-context-flag" />
+ <meta name="assert" content="Block sandbox iframe from accessing other content from the same origin." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("Block sandbox iframe from accessing other content from the same origin.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "!window.parent.document");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <iframe src="support/iframe_sandbox_028.htm" sandbox="allow-scripts" style="display:none"></iframe>
+ <div id=log></div>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_029.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_029.htm
new file mode 100644
index 0000000000..d458e04cd3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_029.htm
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Sandbox: Block document.cookie inside iframe with the sandbox attribute.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-origin-browsing-context-flag" />
+ <meta name="assert" content="Block document.cookie inside iframe with the sandbox attribute." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("Block document.cookie inside iframe with the sandbox attribute.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "cookies are not R/W");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+ <iframe src="support/iframe_sandbox_029.htm" sandbox="allow-scripts" style="display:none"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_030.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_030.htm
new file mode 100644
index 0000000000..d6bb6cc7ba
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_030.htm
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 Sandbox: Block parent content to access sandbox child iframe content when sandbox attribute exists</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-origin-browsing-context-flag" />
+ <meta name="assert" content="Block parent content to access sandbox child iframe content when sandbox attribute exists" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("Block parent content to access sandbox child iframe content when sandbox attribute exists");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ try { document.getElementById('sandboxIframe').contentDocument.title; assert_true(false);}
+ catch(e) {assert_true(true);}
+ });
+ t.done();
+ }
+ </script>
+ <div id=log></div>
+
+ <iframe id='sandboxIframe' src="support/standalone-iframe-content.htm" sandbox onload="callback()" style="display : none"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_031.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_031.htm
new file mode 100644
index 0000000000..3c65d416c5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_031.htm
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Sandbox: Block localStorage and sessionStorage inside iframe with the sandbox attribute.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-origin-browsing-context-flag" />
+ <meta name="assert" content="Block localStorage and sessionStorage inside iframe with the sandbox attribute." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("Block localStorage and sessionStorage inside iframe with the sandbox attribute.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "no access to window.localStorage and window.sessionStorage");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+ <iframe src="support/iframe_sandbox_031.htm" sandbox="allow-scripts" style="display : none"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_032.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_032.htm
new file mode 100644
index 0000000000..4e6293949a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/sandbox_032.htm
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Sandbox: Block XMLHttpRequest in an iframe with the sandbox attribute.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="http://dev.w3.org/html5/spec/Overview.html#sandboxed-origin-browsing-context-flag" />
+ <meta name="assert" content="Block XMLHttpRequest inside sandbox iframe." />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="text/javascript">
+
+ var t = async_test("Block XMLHttpRequest in an iframe with the sandbox attribute.");
+
+ function callback(event)
+ {
+ t.step(function(){
+ assert_true('sandbox' in document.createElement('iframe'));
+ assert_equals(event.data, "no access to window.XMLHttpRequest");
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(callback, 4000);
+ window.addEventListener("message", callback, false);
+ </script>
+ <div id=log></div>
+ <iframe src="support/iframe_sandbox_032.htm" sandbox="allow-scripts" style="display : none"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/src-repeated-in-ancestor.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/src-repeated-in-ancestor.html
new file mode 100644
index 0000000000..2f77dfe164
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/src-repeated-in-ancestor.html
@@ -0,0 +1,138 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Navigation should not occur when `src` matches the location of a anscestor browsing context</title>
+<script>
+// Avoid recursion in non-conforming browsers
+if (parent !== window && parent.title == window.title) {
+ window.stop();
+}
+</script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+/**
+ * This test uses the `beforeunload` event to detect navigation. Because that
+ * event is fired synchronously in response to "process the iframe attributes",
+ * a second "control" iframe may be used to verify cases where navigation
+ * should *not* occur. `Promise.race` ensures that tests complete as soon as
+ * possible.
+ *
+ * Although the specification dictates that the `beforeunload` event must be
+ * emitted synchronously during navigation, a number of user agents do not
+ * adhere to this requirement. WPT includes a dedicated test for synchronous
+ * emission of the event [1]. This test is authored to support non-standard
+ * behavior in order to avoid spuriously passing in those UAs.
+ *
+ * [1] https://github.com/web-platform-tests/wpt/pull/12343
+ */
+'use strict';
+
+function when(target, eventName) {
+ return new Promise(function(resolve, reject) {
+ target.addEventListener(eventName, function() {
+ resolve();
+ }, { once: true });
+ target.addEventListener('error', function() {
+ reject(new Error('Error while waiting for ' + eventName));
+ }, { once: true });
+ });
+}
+
+function init(doc, t) {
+ var iframes = [doc.createElement('iframe'), doc.createElement('iframe')];
+
+ iframes.forEach(function(iframe) {
+ iframe.src = '/common/blank.html';
+ doc.body.appendChild(iframe);
+
+ t.add_cleanup(function() {
+ iframe.parentNode.removeChild(iframe);
+ });
+ });
+
+ return Promise.all([when(iframes[0], 'load'), when(iframes[1], 'load')])
+ .then(function() { return iframes; });
+}
+
+// This test verifies that navigation does occur; it is intended to validate
+// the utility functions.
+promise_test(function(t) {
+ return init(document, t)
+ .then(function(iframes) {
+ var subjectNavigation = when(iframes[0].contentWindow, 'beforeunload');
+ var controlNavigation = when(iframes[1].contentWindow, 'beforeunload');
+
+ iframes[0].src = '/common/blank.html?2';
+ iframes[1].src = '/common/blank.html?3';
+
+ return Promise.all([subjectNavigation, controlNavigation]);
+ });
+}, 'different path name');
+
+promise_test(function(t) {
+ return init(document, t)
+ .then(function(iframes) {
+ var subjectNavigation = when(iframes[0].contentWindow, 'beforeunload');
+ var controlNavigation = when(iframes[1].contentWindow, 'beforeunload');
+
+ iframes[0].src = location.href;
+ iframes[1].src = '/common/blank.html?4';
+
+ return Promise.race([
+ subjectNavigation.then(function() {
+ assert_unreached('Should not navigate');
+ }),
+ controlNavigation
+ ]);
+ });
+}, 'same path name, no document fragment');
+
+promise_test(function(t) {
+ return init(document, t)
+ .then(function(iframes) {
+ var subjectNavigation = when(iframes[0].contentWindow, 'beforeunload');
+ var controlNavigation = when(iframes[1].contentWindow, 'beforeunload');
+
+ iframes[0].src = location.href + '#something-else';
+ iframes[1].src = '/common/blank.html?5';
+
+ return Promise.race([
+ subjectNavigation.then(function() {
+ assert_unreached('Should not navigate');
+ }),
+ controlNavigation
+ ]);
+ });
+}, 'same path name, different document fragment');
+
+promise_test(function(t) {
+ var intermediate = document.createElement('iframe');
+
+ document.body.appendChild(intermediate);
+
+ t.add_cleanup(function() {
+ intermediate.parentNode.removeChild(intermediate);
+ });
+ intermediate.contentDocument.open();
+ intermediate.contentDocument.write('<body></body>');
+ intermediate.contentDocument.close();
+
+ return init(intermediate.contentDocument, t)
+ .then(function(iframes) {
+ var subjectNavigation = when(iframes[0].contentWindow, 'beforeunload');
+ var controlNavigation = when(iframes[1].contentWindow, 'beforeunload');
+
+ iframes[0].src = location.href;
+ iframes[1].src = '/common/blank.html?6';
+
+ return Promise.race([
+ subjectNavigation.then(function() {
+ assert_unreached('Should not navigate');
+ }),
+ controlNavigation
+ ]);
+ });
+}, 'same path name, no document fragement (intermediary browsing context)');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc-anchor.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc-anchor.html
new file mode 100644
index 0000000000..cf26c28f08
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc-anchor.html
@@ -0,0 +1,17 @@
+<title>Verify srcdoc content loads when src is about:srcdoc#foo.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe id=myframe srcdoc='srcdoc_text' src='about:srcdoc#foo'></iframe>
+
+<script>
+ async_test(function(t) {
+ // Verify that the srcdoc content is loaded before we start.
+ window.onload = t.step_func_done(() => {
+ assert_true(typeof myframe.contentDocument !== 'undefined',
+ 'iframe has contentDocument');
+ assert_equals(myframe.contentDocument.body.innerText, 'srcdoc_text',
+ 'iframe contains srcdoc content');
+ }, '');
+ }, 'Verify srcdoc content loads when src is about:srcdoc#foo.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc-attribute-reset.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc-attribute-reset.html
new file mode 100644
index 0000000000..452a984afb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc-attribute-reset.html
@@ -0,0 +1,33 @@
+<title>Verify that clearing srcdoc resets the iframe's content.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/iframe-embed-object.html#process-the-iframe-attributes">
+<link rel="author" title="James MacLean" href="mailto:wjmaclean@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe id=myframe srcdoc='srcdoc_text'></iframe>
+<script>
+ 'use strict';
+
+ async_test(function(t) {
+ window.onload = () => {
+ // Verify that the srcdoc content is loaded before we start.
+ t.step(() => {
+ assert_true(typeof myframe.contentDocument !== 'undefined',
+ 'iframe has contentDocument');
+ assert_equals(myframe.contentDocument.body.innerText, 'srcdoc_text',
+ 'iframe contains srcdoc content');
+ });
+
+ myframe.onload = t.step_func_done(function() {
+ assert_true(typeof myframe.contentDocument !== 'undefined',
+ 'iframe has contentDocument');
+ assert_equals(myframe.contentDocument.body.innerText, '',
+ 'iframe content is empty');
+ });
+
+ // Don't remove srcdoc until the initial load has completed, and the
+ // frame's onload handler is in place.
+ myframe.removeAttribute('srcdoc');
+ };
+ }, 'Verify that the frame reloads with empty body after we remove srcdoc.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc_change_hash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc_change_hash.html
new file mode 100644
index 0000000000..fe72333d1a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc_change_hash.html
@@ -0,0 +1,68 @@
+<title>same-document navigation inside an srcdoc iframe using location.hash</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>
+<script>
+ promise_test(async () => {
+ // Wait until 'document' is available.
+ await new Promise(resolve => window.addEventListener('load', resolve));
+
+ // Create an iframe, wait until is is loaded.
+ let iframe = document.createElement('iframe');
+ await new Promise(resolve => {
+ iframe.srcdoc = "srcdoc document";
+ iframe.onload = resolve;
+ document.body.appendChild(iframe);
+ });
+
+ assert_equals(iframe.contentDocument.body.innerText, "srcdoc document");
+ assert_equals(iframe.contentWindow.location.href, "about:srcdoc");
+
+ function iframeHashChanged() {
+ return new Promise(resolve => {
+ iframe.contentWindow.onhashchange = resolve;
+ })
+ }
+
+ // 1) hash = "1".
+ {
+ let hash_changed = iframeHashChanged();
+ await test_driver.bless("hash = '1'", () => {
+ iframe.contentWindow.location.hash = "1";
+ });
+ await hash_changed;
+ assert_equals(iframe.contentWindow.location.href, "about:srcdoc#1");
+ }
+
+ // 2) hash = "2".
+ {
+ let hash_changed = iframeHashChanged();
+ await test_driver.bless("hash = '2'", () => {
+ iframe.contentWindow.location.hash = "2";
+ });
+ await hash_changed;
+ assert_equals(iframe.contentWindow.location.href, "about:srcdoc#2");
+ }
+
+ // 3) history.back().
+ {
+ let hash_changed = iframeHashChanged();
+ await test_driver.bless("history.back()", () => {
+ history.back();
+ });
+ await hash_changed;
+ assert_equals(iframe.contentWindow.location.href, "about:srcdoc#1");
+ }
+
+ // 4) history.forward().
+ {
+ let hash_changed = iframeHashChanged();
+ await test_driver.bless("history.forward()", () => {
+ history.forward();
+ });
+ await hash_changed;
+ assert_equals(iframe.contentWindow.location.href, "about:srcdoc#2");
+ }
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc_process_attributes.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc_process_attributes.html
new file mode 100644
index 0000000000..0bd9f9b229
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/srcdoc_process_attributes.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Whenever `srcdoc` attribute is set, changed, or removed, the UA must process the &lt;iframe> attributes</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element:process-the-iframe-attributes-2">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+function createIFrameWithBlobSrc() {
+ var iframe = document.createElement("iframe");
+ iframe.src = URL.createObjectURL(new Blob(["src"], {type: "text/html"}));
+ return iframe;
+}
+
+async_test(function(t) {
+ var iframe = createIFrameWithBlobSrc();
+ var isAdded = false;
+ iframe.onload = t.step_func(function() {
+ assert_equals(iframe.contentDocument.location.protocol, "blob:");
+ assert_equals(iframe.contentDocument.body.textContent, "src");
+
+ iframe.onload = t.step_func_done(function() {
+ assert_true(isAdded);
+ assert_equals(iframe.contentDocument.location.href, "about:srcdoc");
+ assert_equals(iframe.contentDocument.body.textContent, "srcdoc");
+ });
+
+ iframe.setAttribute("srcdoc", "srcdoc");
+ isAdded = true;
+ });
+
+ document.body.appendChild(iframe);
+}, "Adding `srcdoc` attribute triggers attributes processing");
+
+async_test(function(t) {
+ var iframe = createIFrameWithBlobSrc();
+ var isChanged = false;
+ iframe.srcdoc = "old";
+ iframe.onload = t.step_func(function() {
+ assert_equals(iframe.contentDocument.location.href, "about:srcdoc");
+ assert_equals(iframe.contentDocument.body.textContent, "old");
+
+ iframe.onload = t.step_func_done(function() {
+ assert_true(isChanged);
+ assert_equals(iframe.contentDocument.location.href, "about:srcdoc");
+ assert_equals(iframe.contentDocument.body.textContent, "new");
+ });
+
+ iframe.srcdoc = "new";
+ isChanged = true;
+ });
+
+ document.body.appendChild(iframe);
+}, "Setting `srcdoc` (via property) triggers attributes processing");
+
+async_test(function(t) {
+ var iframe = createIFrameWithBlobSrc();
+ var isRemoved = false;
+ iframe.srcdoc = "srcdoc";
+ iframe.onload = t.step_func(function() {
+ assert_equals(iframe.contentDocument.location.href, "about:srcdoc");
+ assert_equals(iframe.contentDocument.body.textContent, "srcdoc");
+
+ iframe.onload = t.step_func_done(function() {
+ assert_true(isRemoved);
+ assert_equals(iframe.contentDocument.location.protocol, "blob:");
+ assert_equals(iframe.contentDocument.body.textContent, "src");
+ });
+
+ iframe.removeAttribute("srcdoc");
+ isRemoved = true;
+ });
+
+ document.body.appendChild(iframe);
+}, "Removing `srcdoc` attribute triggers attributes processing");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/stash.py b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/stash.py
new file mode 100644
index 0000000000..2404380881
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/stash.py
@@ -0,0 +1,5 @@
+def main(request, response):
+ if request.method == u'POST':
+ request.server.stash.put(request.GET[b"id"], request.body)
+ return u''
+ return request.server.stash.take(request.GET[b"id"])
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/blank.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/blank.htm
new file mode 100644
index 0000000000..18ecdcb795
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/blank.htm
@@ -0,0 +1 @@
+<html></html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/document-with-embedded-svg.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/document-with-embedded-svg.html
new file mode 100644
index 0000000000..a81d13d9a8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/document-with-embedded-svg.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<body>
+<script>
+["embed", "frame", "iframe", "object"].forEach(name => {
+ const frame = document.body.appendChild(document.createElement(name));
+ const attr = name !== "object" ? "src" : "data";
+ frame[attr] = "svg.svg";
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/download_stash.py b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/download_stash.py
new file mode 100644
index 0000000000..11366a9c3f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/download_stash.py
@@ -0,0 +1,32 @@
+import time
+
+def main(request, response):
+ token = request.GET[b"token"]
+ response.status = 200
+ response.headers.append(b"Content-Type", b"text/html")
+
+ # Make sure to disable sniffing because it would read enough bytes to finish
+ # the load, before even giving the client application a chance to cancel it.
+ response.headers.append(b"X-Content-Type-Options", b"nosniff")
+
+ if b"verify-token" in request.GET:
+ if request.server.stash.take(token):
+ return u'TOKEN_SET'
+ return u'TOKEN_NOT_SET'
+
+ if b"finish-delay" not in request.GET:
+ # <a download>
+ request.server.stash.put(token, True)
+ return
+
+ # navigation to download
+ response.headers.append(b"Content-Disposition", b"attachment")
+ response.write_status_headers()
+ finish_delay = float(request.GET[b"finish-delay"]) / 1E3
+ count = 10
+ single_delay = finish_delay / count
+ for i in range(count): # pylint: disable=unused-variable
+ time.sleep(single_delay)
+ if not response.writer.write_content(b"\n"):
+ return
+ request.server.stash.put(token, True)
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-checks-contentDocument.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-checks-contentDocument.html
new file mode 100644
index 0000000000..bc35a977e8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-checks-contentDocument.html
@@ -0,0 +1,3 @@
+<script>
+ parent.testContentDocument();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-opens-modals.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-opens-modals.html
new file mode 100644
index 0000000000..50f56c6278
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-opens-modals.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script>
+function openModal(name) {
+ switch (name) {
+ case "alert":
+ return alert("MESSAGE");
+ break;
+ case "confirm":
+ return confirm("MESSAGE?");
+ break;
+ case "prompt":
+ return prompt("MESSAGE:", "DEFAULT VALUE");
+ break;
+ case "print":
+ return print();
+ break;
+ }
+}
+
+onmessage = function(e) {
+ parent.postMessage(openModal(e.data), "*");
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-performs-top-navigation-on-popup.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-performs-top-navigation-on-popup.html
new file mode 100644
index 0000000000..9b9eae8a72
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-performs-top-navigation-on-popup.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+window.onload = function() {
+ try {
+ top.location = "iframe-that-send-message-to-the-opener.html";
+ } catch(e) {
+ top.postMessage("cannot navigate", "*");
+ }
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-performs-top-navigation-without-user-gesture-failed.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-performs-top-navigation-without-user-gesture-failed.html
new file mode 100644
index 0000000000..0436d56df9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-performs-top-navigation-without-user-gesture-failed.html
@@ -0,0 +1,16 @@
+<html>
+<body>
+The top navigation from this iframe should be blocked. This text should appear.
+<script>
+window.onload = function()
+{
+ try {
+ top.location = "navigation-changed-iframe.html";
+ top.postMessage("FAIL", "*");
+ } catch(e) {
+ top.postMessage("PASS", "*");
+ }
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-performs-top-navigation.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-performs-top-navigation.html
new file mode 100644
index 0000000000..c855ca3bab
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-performs-top-navigation.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+ <script>
+ function performTest()
+ {
+ try {
+ top.location = "navigation-changed-iframe.html";
+ } catch(e) {
+ top.postMessage("BLOCKED", "*");
+ }
+ }
+ </script>
+</head>
+<body onload="performTest();">
+ <p>This doc tried to navigate the top page when loaded, which should fail since it's not trigged by user activation while in a sandboxed frame with 'allow-top-navigtaion-by-user-activation'. <br> <br>
+ Click the button below, the top navigation should succeed with a new page saying "PASSED: Navigation succeeded." in browsers supporting the 'allow-top-navigtaion-by-user-activation' iframe@sandbox keyword (eg., Chrome v58+); Otherwise, the top navigation should fail.
+ </p>
+ <button id="b" onclick="performTest();">Navigate the top page</button>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-send-message-to-the-opener.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-send-message-to-the-opener.html
new file mode 100644
index 0000000000..0dc3b1122a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-send-message-to-the-opener.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+opener.postMessage('can navigate', '*');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-tries-to-navigate-parent-and-sends-result-to-grandparent.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-tries-to-navigate-parent-and-sends-result-to-grandparent.html
new file mode 100644
index 0000000000..4b8930de42
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-that-tries-to-navigate-parent-and-sends-result-to-grandparent.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<p>This is a frame that tries to navigate its parent.</p>
+<script>
+window.onload = function() {
+ try {
+ parent.location.href = "data:text/html,\u003c!DOCTYPE html\u003e\u003cp\u003eIf this message appears, then this frame has been navigated by its child.\u003c/p\u003e\u003cscript\u003eparent.postMessage('can navigate', '*');\u003c/script\u003e";
+ } catch(e) {
+ parent.parent.postMessage("can not navigate", "*");
+ }
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-tried-to-be-navigated-by-history.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-tried-to-be-navigated-by-history.html
new file mode 100644
index 0000000000..c4ba8011f9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-tried-to-be-navigated-by-history.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<p>This is a frame that tries to navigate via history API.</p>
+<script>
+window.onmessage = (e) => {
+ if (e.data == 'back') {
+ history.back();
+ } else if (e.data == 'forward') {
+ history.forward();
+ } else if (e.data = 'pushstateback') {
+ onpopstate = (e) => {
+ parent.postMessage('pushstatebackdone', '*');
+ };
+
+ history.pushState({someState: 'blah'}, '');
+ history.back();
+ }
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-tried-to-be-navigated-by-its-child.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-tried-to-be-navigated-by-its-child.html
new file mode 100644
index 0000000000..50edc878ad
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-tried-to-be-navigated-by-its-child.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<p>If this message appears, then this frame has not been navigated by its child.</p>
+<iframe src="iframe-that-tries-to-navigate-parent-and-sends-result-to-grandparent.html">
+</iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-trying-to-navigate-its-child.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-trying-to-navigate-its-child.html
new file mode 100644
index 0000000000..9ac754c418
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-trying-to-navigate-its-child.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<iframe src="data:text/html,If this message appears, then this frame has not been navigated by its parent."></iframe>
+<script>
+window.onload = function() {
+ try {
+ document.querySelector("iframe").contentWindow.location.href = "data:text/html,\u003c!DOCTYPE html\u003e\u003cp\u003eIf this message appears, then this frame has been navigated by its parent.\u003c/p\u003e\u003cscript\u003eparent.parent.postMessage('can navigate', '*');\u003c/script\u003e";
+ } catch(e) {
+ parent.postMessage("can not navigate", "*");
+ }
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-trying-to-navigate-itself.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-trying-to-navigate-itself.html
new file mode 100644
index 0000000000..6755d295aa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-trying-to-navigate-itself.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<p>If this message appears, then this frame has not been navigated.</p>
+<script>
+window.onload = function() {
+ try {
+ location.href = "data:text/html,\u003c!DOCTYPE html\u003e\u003cp\u003eIf this message appears, then this frame has been navigated.\u003c/p\u003e\u003cscript\u003eparent.postMessage('can navigate', '*');\u003c/script\u003e";
+ } catch(e) {
+ parent.postMessage("can not navigate", "*");
+ }
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-which-content-height-equals-400px.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-which-content-height-equals-400px.html
new file mode 100644
index 0000000000..bb3dbd3118
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-which-content-height-equals-400px.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body style="width: 200px; height: 400px">
+iframe content
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-with-object.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-with-object.html
new file mode 100644
index 0000000000..c983a9d36d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe-with-object.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Display:none iframe with object tag</title>
+<script>
+ // If this document is loaded as a display:none iframe, forcing an update here
+ // means we do not need a style or layout update after the object is inserted
+ // below because we are not being rendered.
+ document.documentElement.offsetTop;
+</script>
+<object data="data:text/html,frame"></object>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_001.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_001.htm
new file mode 100644
index 0000000000..051ca5ecd7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_001.htm
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Page with script</title>
+</head>
+<body>
+ <script type="text/javascript">
+ parent.window.postMessage("script ran", "*");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_002.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_002.htm
new file mode 100644
index 0000000000..e637847714
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_002.htm
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 video with autoplay attribute.</title>
+ <script src="/common/media.js"></script>
+</head>
+<body>
+ <script>
+ function do_play(event) {
+ parent.window.postMessage("play event fired", "*");
+ }
+
+ document.write(
+ "<video id='video0' src='" + getVideoURI("/media/green-at-15") + "'" +
+ " autoplay onplay='do_play(event);'>"
+ );
+ </script>
+ Your browser does not support HTML5 video.
+ </video>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_003.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_003.htm
new file mode 100644
index 0000000000..621ece79af
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_003.htm
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>autofocus on form control</title>
+</head>
+<body>
+ <div>Below form control has autofocus attribute set.</div><br />
+ <form action="">
+ <span>Textbox: </span><input autofocus="autofocus" type="text" name="movie" />
+ </form>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_004.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_004.htm
new file mode 100644
index 0000000000..661afb0943
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_004.htm
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>object tag</title>
+</head>
+<body>
+ <object type="application/pdf" width="600" height="600" data="sandbox.pdf">
+ Fallback content
+ </object>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_006.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_006.htm
new file mode 100644
index 0000000000..42542ae147
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_006.htm
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Form submission</title>
+</head>
+<body>
+ <form id="form1" action="standalone-pass.htm">
+ <span>Name: </span><input type="text" name="name" value="browser" /><br />
+ <input id="submitButton" type="submit" value="Submit Form" />
+ </form>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_007.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_007.htm
new file mode 100644
index 0000000000..fc01557c75
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_007.htm
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Form submission</title>
+</head>
+<body>
+ <form id="form1" action="standalone-fail.htm">
+ <span>Name: </span><input type="text" name="name" value="browser" /><br />
+ <input id="submitButton" type="submit" value="Submit Form" />
+ </form>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_008.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_008.htm
new file mode 100644
index 0000000000..ebd8279675
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_008.htm
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Page with hyperlink and target set to self</title>
+</head>
+<body>
+ <a id="hyperlink" href="standalone-pass.htm" target="_self">Click here to perform self navigation</a>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_010.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_010.htm
new file mode 100644
index 0000000000..27fc4209f7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_010.htm
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Page with window.open()</title>
+</head>
+<body>
+ <button type="button" onclick="javascript:window.open('standalone-fail.htm')">Click here to call window.open() API</button>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_012.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_012.htm
new file mode 100644
index 0000000000..b1e8f92fb4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_012.htm
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Page with access to document.cookie</title>
+</head>
+<body>
+ <script type="text/javascript">
+ cookie = document.cookie;
+ document.cookie = "name=browser";
+ parent.window.postMessage("cookies are R/W", "*");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_020.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_020.htm
new file mode 100644
index 0000000000..fd0d6bb6fb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_020.htm
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Page with iframes</title>
+</head>
+<body>
+ <table cellpadding="5" cellspacing="10">
+ <tr>
+ <td>
+ <span>child iframe with sandbox="allow-scripts" attribute</span><br />
+ <iframe id="Iframe1" src="iframe_sandbox_020a.htm" sandbox="allow-scripts" style="height: 50px; width: 250px;"></iframe>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span>child iframe with sandbox="" attribute</span><br />
+ <iframe id="Iframe2" src="iframe_sandbox_020a.htm" sandbox="" style="height: 50px; width: 250px;"></iframe>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span>child iframe without sandbox attribute</span><br />
+ <iframe id="Iframe3" src="iframe_sandbox_020a.htm" style="height: 50px; width: 250px;"></iframe>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_020a.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_020a.htm
new file mode 100644
index 0000000000..ccfa4eae2e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_020a.htm
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Page with script</title>
+</head>
+<body>
+ <div>Script Execution: <span id="scriptExecute" style="Color: Green">Blocked</span></div>
+ <script type="text/javascript">
+ document.getElementById("scriptExecute").innerHTML = "Not Blocked";
+ document.getElementById("scriptExecute").style.color = "Red";
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_021.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_021.htm
new file mode 100644
index 0000000000..63f5892456
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_021.htm
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Page with iframes</title>
+</head>
+<body>
+ <table cellpadding="5" cellspacing="10">
+ <tr>
+ <td>
+ <span>child iframe with sandbox="allow-scripts" attribute</span><br />
+ <iframe id="Iframe1" src="iframe_sandbox_021a.htm" sandbox="allow-scripts" style="height: 50px; width: 250px;"></iframe>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span>child iframe with sandbox="" attribute</span><br />
+ <iframe id="Iframe2" src="iframe_sandbox_020a.htm" sandbox="" style="height: 50px; width: 250px;"></iframe>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span>child iframe without sandbox attribute</span><br />
+ <iframe id="Iframe3" src="iframe_sandbox_021a.htm" style="height: 50px; width: 250px;"></iframe>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_021a.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_021a.htm
new file mode 100644
index 0000000000..a42520d7e1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_021a.htm
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Page with script</title>
+</head>
+<body>
+ <div>Script Execution: <span id="scriptExecute" style="Color: Red">Blocked</span></div>
+ <script type="text/javascript">
+ document.getElementById("scriptExecute").innerHTML = "Allowed";
+ document.getElementById("scriptExecute").style.color = "Green";
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_022.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_022.htm
new file mode 100644
index 0000000000..87082e51dc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_022.htm
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>target=_top</title>
+</head>
+<body>
+ <div>hyperlink with target=_top</div>
+ <br />
+ <a href="standalone-pass.htm" target="_top">Open the link in top window</a>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_023.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_023.htm
new file mode 100644
index 0000000000..a65db539bb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_023.htm
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head><title>Access parent dom</title>
+</head>
+<body>
+ <script type="text/javascript">
+ if (window.parent.document)
+ {
+ parent.window.postMessage("window.parent.document", "*");
+ }else{
+ parent.window.postMessage("!window.parent.document", "*");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_024.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_024.htm
new file mode 100644
index 0000000000..1b0996e589
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_024.htm
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head><title>Page with access to document.cookie</title>
+</head>
+<body>
+ <div>Cookie Read: <span id="readCookie"></span></div>
+ <script type="text/javascript">
+ cookie = document.cookie;
+ document.cookie = "name=browser";
+ parent.window.postMessage("cookies are R/W", "*");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_026.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_026.htm
new file mode 100644
index 0000000000..7171cf7721
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_026.htm
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head><title>Page with access to localStorage and sessionStorage</title>
+</head>
+<body>
+ <script type="text/javascript">
+ if (window.localStorage && window.sessionStorage) {
+ parent.window.postMessage("access to window.localStorage and window.sessionStorage", "*");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_027.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_027.htm
new file mode 100644
index 0000000000..c1a48cdef9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_027.htm
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head><title>XMLHttpRequest</title>
+</head>
+<body>
+ <script type="text/javascript">
+ xhrRequest = new XMLHttpRequest();
+
+ xhrRequest.onreadystatechange = function () {
+ if (xhrRequest.readyState == 4 && xhrRequest.status == 200) {
+ //xhr successful
+ parent.window.postMessage("access to window.XMLHttpRequest", "*");
+ }
+ }
+
+ xhrRequest.open("GET", "standalone-pass.htm", true);
+ xhrRequest.send();
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_028.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_028.htm
new file mode 100644
index 0000000000..d7ca761441
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_028.htm
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head><title>Access parent dom</title>
+</head>
+<body>
+ <script type="text/javascript">
+ try
+ {
+ if (window.parent.document)
+ {
+ parent.window.postMessage("window.parent.document", "*");
+ }
+ }
+ catch(e)
+ {
+ parent.window.postMessage("!window.parent.document", "*");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_029.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_029.htm
new file mode 100644
index 0000000000..5d5c720bd8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_029.htm
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head><title>Page with access to document.cookie</title>
+</head>
+<body>
+ <div>Cookie Read: <span id="readCookie"></span></div>
+ <script type="text/javascript">
+ try
+ {
+ cookie = document.cookie;
+ document.cookie = "name=browser";
+ parent.window.postMessage("cookies are R/W", "*");
+ }catch(e)
+ {
+ parent.window.postMessage("cookies are not R/W", "*");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_031.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_031.htm
new file mode 100644
index 0000000000..fb987dac38
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_031.htm
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head><title>Page with access to localStorage and sessionStorage</title>
+</head>
+<body>
+ <script type="text/javascript">
+ try
+ {
+ if (window.localStorage && window.sessionStorage) {
+ parent.window.postMessage("access to window.localStorage and window.sessionStorage", "*");
+ }
+ }
+ catch(e)
+ {
+ parent.window.postMessage("no access to window.localStorage and window.sessionStorage", "*");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_032.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_032.htm
new file mode 100644
index 0000000000..6059b7df43
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_032.htm
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head><title>XMLHttpRequest</title>
+</head>
+<body>
+ <script type="text/javascript">
+
+ try
+ {
+ xhrRequest = new XMLHttpRequest();
+
+ xhrRequest.onreadystatechange = function () {
+ if (xhrRequest.readyState == 4 && xhrRequest.status == 200) {
+ //xhr successful
+ parent.window.postMessage("access to window.XMLHttpRequest", "*");
+ }
+ }
+
+ xhrRequest.open("GET", "standalone-pass.htm", true);
+ xhrRequest.send();
+
+ }catch(e){}
+
+ parent.window.postMessage("no access to window.XMLHttpRequest", "*");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_block_modals.js b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_block_modals.js
new file mode 100644
index 0000000000..67733d8101
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_block_modals.js
@@ -0,0 +1,18 @@
+function runTest(modalName, expectedValue) {
+ let timeOutForFailingToOpenModal = 500;
+ let startTime;
+ async_test(t => {
+ let iframe = document.querySelector("iframe");
+ iframe.onload = t.step_func(() => {
+ window.addEventListener("message", t.step_func_done(e => {
+ // This tests work by checking the call to open the modal diaglog will return immediately (or at least within timeOutForFailingToOpenModal).
+ // If the modal dialog is not blocked, then it will wait for user input and the test will time out.
+ assert_less_than(new Date().getTime() - startTime, timeOutForFailingToOpenModal, "Call to open modal dialog did not return immediately");
+ assert_equals(e.data, expectedValue, "Call to open modal dialog did not return expected value");
+ }));
+ startTime = new Date().getTime();
+ iframe.contentWindow.postMessage(modalName, "*");
+ });
+ iframe.src = "support/iframe-that-opens-modals.html";
+ }, "Frames without `allow-modals` should not be able to open modal dialogs");
+}
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_download_helper.js b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_download_helper.js
new file mode 100644
index 0000000000..7090e7662c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_download_helper.js
@@ -0,0 +1,37 @@
+function StreamDownloadFinishDelay() {
+ return 1000;
+}
+
+function DownloadVerifyDelay() {
+ return 1000;
+}
+
+function VerifyDownload(test_obj, token, timeout, expect_download) {
+ var verify_token = test_obj.step_func(function () {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', 'support/download_stash.py?verify-token&token=' + token);
+ xhr.onload = test_obj.step_func(function(e) {
+ if (expect_download) {
+ if (xhr.response != "TOKEN_SET") {
+ // Always retry, and rely on the test timeout to conclude that
+ // download didn't happen and to fail the test.
+ test_obj.step_timeout(verify_token, DownloadVerifyDelay());
+ return;
+ }
+ } else {
+ assert_equals(xhr.response, "TOKEN_NOT_SET", "Expect no download to happen, but got one.");
+ }
+ test_obj.done();
+ });
+ xhr.send();
+ });
+ test_obj.step_timeout(verify_token, timeout);
+}
+
+function AssertDownloadSuccess(test_obj, token, timeout) {
+ VerifyDownload(test_obj, token, timeout, true);
+}
+
+function AssertDownloadFailure(test_obj, token, timeout) {
+ VerifyDownload(test_obj, token, timeout, false);
+}
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/load-into-the-iframe.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/load-into-the-iframe.html
new file mode 100644
index 0000000000..9e08eb587a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/load-into-the-iframe.html
@@ -0,0 +1,24 @@
+
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <iframe sandbox="allow-scripts"></iframe>
+ <script>
+ let frame = document.querySelector("iframe");
+ let sandbox = new URL(location.href).searchParams.get("sandbox");
+ if (sandbox) {
+ frame.sandbox = sandbox;
+ }
+ // We're the popup (i.e. a top frame). Load into the iframe the page
+ // trying to modifying the top frame and transmit the result to our
+ // opener.
+ onmessage = function(e) {
+ opener.postMessage(e.data, "*")
+ }
+ frame.src = "iframe-that-performs-top-navigation-on-popup.html";
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/navigation-changed-iframe.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/navigation-changed-iframe.html
new file mode 100644
index 0000000000..abe0e78dfe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/navigation-changed-iframe.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+ <script>
+ function fireSentinel()
+ {
+ document.getElementsByTagName('h4')[0].innerHTML = document.domain;
+ }
+ </script>
+</head>
+<body onload="fireSentinel();">
+ <h4>DOMAIN</h4>
+ <p>PASSED: Navigation succeeded.</p>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox.pdf b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox.pdf
new file mode 100644
index 0000000000..0e16bc8d93
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox.pdf
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox.pdf.headers b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox.pdf.headers
new file mode 100644
index 0000000000..5a8e57e482
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox.pdf.headers
@@ -0,0 +1 @@
+Content-Type: application/pdf
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox_allow_script.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox_allow_script.html
new file mode 100644
index 0000000000..a2d5b73eda
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox_allow_script.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: sandbox_allow_scripts</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<div id="test">Before change</div>
+<script>
+ parent.window.postMessage("Script executed", "*");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox_helper.js b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox_helper.js
new file mode 100644
index 0000000000..26aa67faf4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/sandbox_helper.js
@@ -0,0 +1,14 @@
+function IsSandboxSupported() {
+ if ('sandbox' in document.createElement('iframe')) {
+ return true;
+ }
+ return false;
+}
+
+function DisableTestForNonSupportingBrowsers() {
+ //check if sandbox is supported by the browser
+ if (!IsSandboxSupported()) {
+ document.getElementById('testframe').innerHTML = "FAIL: Your browser does not support the sandbox attribute on the iframe element.";
+ document.getElementById('testframe').style.color = "Red";
+ }
+}
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/standalone-fail.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/standalone-fail.htm
new file mode 100644
index 0000000000..29ef4d5abb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/standalone-fail.htm
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Page with FAIL message</title>
+</head>
+<body>
+ <div style="color: Red">FAIL!!!</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/standalone-iframe-content.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/standalone-iframe-content.htm
new file mode 100644
index 0000000000..b26f7fda75
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/standalone-iframe-content.htm
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Page with a message</title>
+</head>
+<body>
+ <div>Hello World.</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/standalone-pass.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/standalone-pass.htm
new file mode 100644
index 0000000000..9d1b2530fe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/standalone-pass.htm
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Page with PASS message</title>
+</head>
+<body>
+ <div style="color: Green">PASS!!!</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/svg.svg b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/svg.svg
new file mode 100644
index 0000000000..1570afcadc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/svg.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"><rect height="100" width="100"/></svg>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/3.jpg b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/3.jpg
new file mode 100644
index 0000000000..d30ac2ac36
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/3.jpg
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.html
new file mode 100644
index 0000000000..73b937f67f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>404 response with actual image data should be rendered and load event is fired</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<img id="img">
+
+<script>
+ async_test(t => {
+ var img = document.getElementById("img");
+ img.onload = t.step_func_done(e => {
+ assert_equals(e.type, "load", "image.onload() called");
+ });
+ img.onerror = t.unreached_func("image.onerror() was not supposed to be called");
+ img.src = "404-response-with-actual-image-data.py";
+ }, "404 response with actual image data should be rendered and load event is fired");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.py b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.py
new file mode 100644
index 0000000000..083aa90b41
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.py
@@ -0,0 +1,4 @@
+from base64 import decodebytes
+
+def main(req, res):
+ return 404, [(b'Content-Type', b'image/png')], decodebytes(b"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAhSURBVDhPY3wro/KfgQLABKXJBqMGjBoAAqMGDLwBDAwAEsoCTFWunmQAAAAASUVORK5CYII=")
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/Image-constructor.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/Image-constructor.html
new file mode 100644
index 0000000000..3cfef5f4f9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/Image-constructor.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<title>DOM Image constructor Test</title>
+<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element" />
+<meta name="assert" content="Tests the Image constructor for the img-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+
+<div id="log"></div>
+<script>
+ test(function() {
+ var img = new Image();
+ assert_true(img != undefined);
+ }, "Image constructor works");
+
+ test(function() {
+ assert_equals(Image.prototype, HTMLImageElement.prototype);
+ }, "Image and HTMLImageElement share a prototype");
+
+ test(function() {
+ assert_true((new Image()).localName === "img");
+ }, "Image localName is img");
+
+ test(function() {
+ assert_true((new Image()).namespaceURI === "http://www.w3.org/1999/xhtml");
+ }, "Image namespace URI is correct");
+
+ test(function() {
+ assert_equals(Image.name, "Image", "Image name should be Image (not HTMLImageElement)");
+ assert_equals(Object.getPrototypeOf(Image), Function.prototype, "Image's prototype is Function.prototype");
+ assert_equals(Image.prototype, HTMLImageElement.prototype, "Image.prototype is same as HTMLImageElement.prototype");
+ assert_equals(Object.getPrototypeOf(new Image()), HTMLImageElement.prototype, "new Image()'s prototype is HTMLImageElement.prototype ");
+ assert_equals(Object.getPrototypeOf(Image.prototype), HTMLElement.prototype, "Image.prototype's prototype is HTMLElement.prototype");
+
+ const desc = Object.getOwnPropertyDescriptor(Image, "prototype");
+ assert_false(desc.configurable, "Image.prototype is not configurable");
+ assert_false(desc.enumerable, "Image.prototype is not enumerable");
+ assert_false(desc.writable, "Image.prototype is not writable");
+ }, "NamedConstructor creates the correct object structure.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adopt-from-image-document.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adopt-from-image-document.html
new file mode 100644
index 0000000000..30729fc81c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adopt-from-image-document.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>Adopt img from image document</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+<link rel="match" href="document-base-url-ref.html">
+<!-- Counteract any style added by the image document -->
+<style>img { width: initial; height: initial; }</style>
+<iframe></iframe>
+<script>
+ var iframe = document.querySelector('iframe');
+ iframe.onload = function() {
+ let img = iframe.contentDocument.body.firstChild;
+ document.body.appendChild(img);
+ iframe.remove();
+ };
+ iframe.src = 'resources/cat.jpg';
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adoption.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adoption.html
new file mode 100644
index 0000000000..15e02bcf51
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adoption.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Adopting an image updates the image data</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+
+<!-- tests -->
+
+<div id="adoptTest1"></div>
+<picture id="adoptTest2">
+<source srcset="/images/green-2x2.png">
+</picture>
+
+<script>
+function resolve(url) {
+ if (url === "") {
+ return url;
+ }
+ var a = document.createElement('a');
+ a.href = url;
+ return a.href;
+}
+
+function t(desc, data, expect) {
+ async_test(function(t) {
+ var d = (new DOMParser()).parseFromString(data, 'text/html');
+ var i = d.querySelector('img');
+ i.onerror = this.unreached_func('got unexpected error event');
+ i.onload = this.step_func_done(function() {
+ assert_equals(i.currentSrc, resolve(expect));
+ });
+ var n = d.querySelector('[adopt-node]');
+ document.adoptNode(n);
+ }, desc);
+}
+
+onload = function() {
+
+ t('img (src only)',
+ '<img src="/images/green-1x1.png" adopt-node>',
+ '/images/green-1x1.png');
+
+ t('img (src only), parent is picture',
+ '<picture adopt-node><img src="/images/green-1x1.png"></picture>',
+ '/images/green-1x1.png');
+
+ t('img (src only), previous sibling is source',
+ '<picture adopt-node><source srcset="/images/green-1x1.png"><img src="/images/green-2x2.png"></picture>',
+ '/images/green-1x1.png');
+
+ t('img (srcset 1 cand)',
+ '<img srcset="/images/green-1x1.png" adopt-node>',
+ '/images/green-1x1.png');
+
+ t('img (srcset 1 cand), parent is picture',
+ '<picture adopt-node><img srcset="/images/green-1x1.png"></picture>',
+ '/images/green-1x1.png');
+
+ t('img (srcset 1 cand), previous sibling is source',
+ '<picture adopt-node><source srcset="/images/green-1x1.png"><img srcset="/images/green-2x2.png"></picture>',
+ '/images/green-1x1.png');
+
+ async_test(function(t) {
+ var d = (new DOMParser()).parseFromString('<template><img src="/images/green-1x1.png"></template>', 'text/html');
+ var i = d.querySelector('template').content.querySelector('img').cloneNode(1);
+ i.onerror = this.unreached_func('got unexpected error event');
+ i.onload = this.step_func_done(function() {
+ assert_equals(i.currentSrc, resolve('/images/green-1x1.png'));
+ });
+
+ document.getElementById('adoptTest1').appendChild(i);
+ }, 'adopt a cloned img in template');
+
+ async_test(function(t) {
+ var preload = new Image();
+ preload.src = '/images/green-1x1.png?' + Math.random();
+ preload.onload = t.step_func(function() {
+ var d = (new DOMParser()).parseFromString('<img src="' + preload.src + '">', 'text/html');
+ var i = d.querySelector('img');
+ i.onerror = this.unreached_func('got unexpected error event');
+ i.onload = this.step_func_done(function() {
+ assert_equals(i.currentSrc, resolve("/images/green-2x2.png"));
+ });
+
+ var p = document.getElementById('adoptTest2');
+ p.appendChild(i);
+ });
+ }, 'adoption is from appendChild');
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/already-loaded-image-sync-width.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/already-loaded-image-sync-width.html
new file mode 100644
index 0000000000..4a63bd7a7a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/already-loaded-image-sync-width.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Image dimensions are available synchronously after changing src to an already-loaded image</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=1797798">
+<img id="existing">
+<script>
+ let src = "/images/green.png";
+ let existing = document.getElementById("existing");
+ async_test(function(t) {
+ let tmp = document.createElement("img");
+ tmp.src = src;
+ tmp.onload = t.step_func_done(function() {
+ existing.src = src;
+ assert_equals(existing.width, 100);
+ assert_equals(existing.height, 50);
+ });
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-onload.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-onload.html
new file mode 100644
index 0000000000..5fc5cb8b61
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-onload.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+<title>Ensure images from available images list can be drawn to a canvas</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-list-of-available-images">
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ async_test(function(t) {
+ var i = new Image();
+ i.onerror = t.unreached_func();
+ i.onload = t.step_func(function() {
+ var i2 = new Image();
+ // Potentially start multiple image loading tasks by performing several
+ // relevant mutations. This could lead to an invalid state later in an
+ // erroneous implementation.
+ i2.crossOrigin = true;
+ // Start an image loading task that is expected to short-circuit since
+ // the requested image is present in the list of available images.
+ i2.src = "3.jpg";
+ i2.onerror = t.unreached_func();
+ // Ensure the loaded image is in a state that is usable by a 2d canvas.
+ i2.onload = t.step_func_done(function() {
+ var c = document.createElement('canvas');
+ var ctx = c.getContext('2d');
+ ctx.drawImage(i2, 0, 0);
+ });
+ });
+ // Request an image which should be added to the list of available images.
+ i.src = "3.jpg";
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-ref.html
new file mode 100644
index 0000000000..8061abae50
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-ref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<img src="3.jpg">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images.html
new file mode 100644
index 0000000000..779ff97868
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Ensure images from available images list are rendered</title>
+<meta charset="utf-8">
+<link rel="match" href="available-images-ref.html">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+<div id="log"></div>
+<script>
+ var i = new Image();
+ i.onload = function() {
+ var i2 = new Image();
+ i2.src = "3.jpg";
+ document.body.appendChild(i2);
+ document.documentElement.classList.remove("reftest-wait");
+ };
+ i.src = "3.jpg";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/below-viewport-image-loading-lazy-load-event.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/below-viewport-image-loading-lazy-load-event.html
new file mode 100644
index 0000000000..b1dee3a3ca
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/below-viewport-image-loading-lazy-load-event.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<head>
+ <title>Below-viewport loading=lazy images do not block the window load event
+ when scrolled into viewport</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<body>
+ <!-- When this image loads, we will scroll the below-viewport loading=lazy
+ images into the viewport. This happens before the window load event is
+ fired -->
+ <img id="scroll_trigger"
+ src="resources/image.png?scroll-trigger&pipe=trickle(d1)"
+ onload="scroll_trigger_img.resolve();" onerror="scroll_trigger_img.reject();">
+ <!-- This image blocks the window load event for 2 seconds -->
+ <img src="resources/image.png?window-load-blocking&pipe=trickle(d2)">
+
+ <div style="height:1000vh"></div>
+ <!-- These images must load because they intersect the viewport, but they must
+ not block the window load event, because they are loading=lazy -->
+ <img id="visible"
+ src="resources/image.png?visible&pipe=trickle(d3)" loading="lazy"
+ onload="visible_img.resolve();" onerror="visible_img.reject();">
+ <img id="visibility_hidden" style="visibility:hidden;"
+ src="resources/image.png?visibility_hidden&pipe=trickle(d3)" loading="lazy"
+ onload="visibility_hidden_img.resolve();" onerror="visibility_hidden_img.reject();">
+</body>
+
+<script>
+ const scroll_trigger_img = new ElementLoadPromise("visible");
+ const visible_img = new ElementLoadPromise("visible");
+ const visibility_hidden_img = new ElementLoadPromise("visibility_hidden");
+
+ async_test(t => {
+ let has_window_loaded = false;
+
+ scroll_trigger_img.promise
+ .then(t.step_func(() => {
+ assert_false(has_window_loaded,
+ "The scroll_trigger image should load before the window " +
+ "load event fires");
+ visibility_hidden_img.element().scrollIntoView();
+ }))
+ .catch(t.unreached_func("The scroll_trigger image should load"));
+
+ window.addEventListener("load", t.step_func(() => {
+ has_window_loaded = true;
+ }));
+
+ Promise.all([visible_img.promise, visibility_hidden_img.promise])
+ .then(t.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "The window load event should fire before the " +
+ "below-viewport loading=lazy images load");
+ assert_true(visible_img.element().complete,
+ "The below-viewport loading=lazy visible image is complete");
+ assert_true(visibility_hidden_img.element().complete,
+ "The below-viewport loading=lazy visibility:hidden image is complete");
+ }))
+ .catch(t.unreached_func("The images should load successfully"));
+
+ }, "Below-viewport loading=lazy images do not block the window load event when " +
+ "scrolled into viewport");
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/brokenimg.jpg b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/brokenimg.jpg
new file mode 100644
index 0000000000..ccff177ae9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/brokenimg.jpg
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<html>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/basic.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/basic.html
new file mode 100644
index 0000000000..f7d47b3640
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/basic.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<title>img current pixel density basic</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<img src="/images/green-256x256.png" data-expect="256">
+<img srcset="/images/green-256x256.png 1x" data-expect="256">
+<img srcset="/images/green-256x256.png 1.6x" data-expect="160">
+<img srcset="/images/green-256x256.png 2x" data-expect="128">
+<img srcset="/images/green-256x256.png 10000x" data-expect="0">
+<img srcset="/images/green-256x256.png 9e99999999999999999999999x" data-expect="0">
+<img srcset="/images/green-256x256.png 256w" sizes="256px" data-expect="256">
+<img srcset="/images/green-256x256.png 512w" sizes="256px" data-expect="128">
+<img srcset="/images/green-256x256.png 256w" sizes="512px" data-expect="512">
+<img srcset="/images/green-256x256.png 256w" sizes="1px" data-expect="1">
+<img srcset="/images/green-256x256.png 256w" sizes="0px" data-expect="0">
+<!-- SVG -->
+<img srcset="data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='-1%20-1%202%202'%20width='20'%20height='20'><circle%20r='1'/></svg> 2x" data-expect="10">
+<img srcset="data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='-1%20-1%202%202'%20width='20'><circle%20r='1'/></svg> 2x" data-expect="10">
+<img srcset="data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='-1%20-1%202%202'%20height='20'><circle%20r='1'/></svg> 2x" data-expect="10">
+<script>
+setup({explicit_done:true});
+onload = function() {
+ [].forEach.call(document.images, function(img) {
+ test(function() {
+ var expected = parseFloat(img.dataset.expect);
+ assert_equals(img.width, expected, 'width');
+ assert_equals(img.height, expected, 'height');
+ assert_equals(img.clientWidth, expected, 'clientWidth');
+ assert_equals(img.clientHeight, expected, 'clientHeight');
+ assert_equals(img.naturalWidth, expected, 'naturalWidth');
+ assert_equals(img.naturalHeight, expected, 'naturalHeight');
+ }, img.outerHTML);
+ });
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/error.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/error.html
new file mode 100644
index 0000000000..5e328b5e2d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/error.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<title>img current pixel density error</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<img id=ref src="404" alt="testing">
+<img srcset="404" alt="testing">
+<img srcset="404 0.5x" alt="testing">
+<img srcset="404 2x" alt="testing">
+<img srcset="404 100w" alt="testing">
+<img srcset="404 100w" sizes="500px" alt="testing">
+<picture><img src="404 100w" sizes="500px" alt="testing"></picture>
+<script>
+setup({explicit_done:true});
+onload = function() {
+ var ref = document.getElementById("ref");
+ var expected_width = ref.width;
+ var expected_height = ref.height;
+ [].forEach.call(document.images, function(img) {
+ test(function() {
+ assert_not_equals(expected_width, 0, 'expected_width');
+ assert_not_equals(expected_height, 0, 'expected_height');
+ assert_equals(img.width, expected_width, 'width');
+ assert_equals(img.height, expected_height, 'height');
+ assert_equals(img.naturalWidth, 0, 'naturalWidth');
+ assert_equals(img.naturalHeight, 0, 'naturalHeight');
+ }, img.outerHTML);
+ });
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/currentSrc-blob-cache.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/currentSrc-blob-cache.html
new file mode 100644
index 0000000000..a5e108dcd6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/currentSrc-blob-cache.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>currentSrc is right even if underlying image is a shared blob</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=1625786">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<img id="first">
+<img id="second">
+<script>
+promise_test(async t => {
+ let canvas = document.createElement("canvas");
+ canvas.width = 100;
+ canvas.height = 100;
+ let ctx = canvas.getContext("2d");
+ ctx.fillStyle = "green";
+ ctx.rect(0, 0, 100, 100);
+ ctx.fill();
+
+ let blob = await new Promise(resolve => canvas.toBlob(resolve));
+
+ let first = document.querySelector("#first");
+ let second = document.querySelector("#second");
+
+ let firstLoad = new Promise(resolve => {
+ first.addEventListener("load", resolve, { once: true });
+ });
+
+ let secondLoad = new Promise(resolve => {
+ second.addEventListener("load", resolve, { once: true });
+ });
+
+ let uri1 = URL.createObjectURL(blob);
+ let uri2 = URL.createObjectURL(blob);
+ first.src = uri1;
+ second.src = uri2;
+
+ await firstLoad;
+ await secondLoad;
+
+ assert_equals(first.src, first.currentSrc);
+ assert_equals(second.src, second.currentSrc);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/data-url.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/data-url.html
new file mode 100644
index 0000000000..808b5c884c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/data-url.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>data URL image</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+setup({ single_test: true });
+
+var c = document.createElement("canvas"),
+ con = c.getContext("2d"),
+ img = document.createElement("img")
+img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAA+UlEQVR4nO3RoRHAQBDEsOu/6YR+B2sgIO4Z3919pMwDMCRtHoAhafMADEmbB2BI2jwAQ9LmARiSNg/AkLR5AIakzQMwJG0egCFp8wAMSZsHYEjaPABD0uYBGJI2D8CQtHkAhqTNAzAkbR6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPwJC0eQCGpM0DMCRtHoAhafMADEmbB2BI2jwAQ9LmARiSNg/AkLR5AIakzQMwJG0egCFp8wAMSZsHYEjaPABD0uYBGJI2D8CQtHkAhqTNAzAkbR6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPwJC0eQCGpM0DMCRtHsDjB5K06yueJFXJAAAAAElFTkSuQmCC"
+img.onload = () => {
+ con.drawImage(img, 0, 0)
+ var data = con.getImageData(0, 0, 10, 10) // should not throw as data URLs are same-origin
+ for(var i = 0; i < data.data.length; i++) {
+ var expected = ((i+1) % 4 == 0) ? 255 : 0
+ assert_equals(data.data[i], expected)
+ }
+ c.toDataURL() // shouldn't throw either
+ done()
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-iframe.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-iframe.html
new file mode 100644
index 0000000000..ed14a007a6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-iframe.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>HTMLImageElement.prototype.decode(), iframe tests.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe id="frame_loaded" srcdoc="iframe"></iframe>
+<iframe id="frame_notloaded" srcdoc="iframe"></iframe>
+<iframe id="frame_notloaded2" srcdoc="iframe"></iframe>
+
+<script>
+"use strict";
+
+promise_test(function() {
+ return new Promise(function(resolve, reject) {
+ var frame = document.getElementById("frame_loaded");
+ var img = frame.contentDocument.createElement("img");
+ img.src = "/images/green.png";
+ img.onload = function() {
+ // At this point the frame which created the img is removed, so decode() should fail.
+ frame.parentNode.removeChild(frame);
+ img.decode().then(function() {
+ assert_unreached("Unexpected success");
+ }, function() {
+ resolve();
+ });
+ };
+ });
+}, document.title + " Decode from removed iframe fails (loaded img)");
+
+promise_test(function(t) {
+ var frame = document.getElementById("frame_notloaded");
+ var img = frame.contentDocument.createElement("img");
+ img.src = "/images/green.png";
+ frame.parentNode.removeChild(frame);
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Decode from removed iframe fails (img not loaded)");
+
+promise_test(function(t) {
+ var frame = document.getElementById("frame_notloaded2");
+ var img = frame.contentDocument.createElement("img");
+ img.src = "/images/green.png";
+ // First request a promise, then remove the iframe.
+ var promise = img.decode();
+ frame.parentNode.removeChild(frame);
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Decode from iframe, later removed, fails (img not loaded)");
+
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-image-document.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-image-document.html
new file mode 100644
index 0000000000..e54ae223a0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-image-document.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>HTMLImageElement.prototype.decode(), image document tests.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="frame_imgdoc" src="about:blank"></iframe>
+<script>
+"use strict";
+
+promise_test(function() {
+ return new Promise(function(resolve) {
+ var frame = document.getElementById("frame_imgdoc");
+ // Load an image in the iframe and then replace that.
+ frame.src = "/images/red.png";
+ frame.onload = function() {
+ let img = frame.contentDocument.body.firstElementChild;
+ img.src = "/images/green.png";
+ img.decode().then(function() {
+ resolve();
+ });
+ };
+ });
+}, document.title + " Decode from iframe with image document, succeeds (img not loaded)");
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes-svg.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes-svg.tentative.html
new file mode 100644
index 0000000000..1bc53a1f18
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes-svg.tentative.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>SVGImageElement.prototype.decode(), href mutation tests.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+// src tests
+// -------------------
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.png");
+ var promise = img.decode();
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.svg");
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " xlink:href changes fail decode.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/images/green.png");
+ var promise = img.decode();
+ img.setAttribute('href', "/images/green.svg");
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " href changes fail decode.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.png");
+ var first_promise = img.decode();
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.svg");
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ second_promise
+ ]);
+}, document.title + " xlink:href changes fail decode; following good decode succeeds.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/images/green.png");
+ var first_promise = img.decode();
+ img.setAttribute('href', "/images/green.svg");
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ second_promise
+ ]);
+}, document.title + " href changes fail decode; following good decode succeeds.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.png");
+ var first_promise = img.decode();
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/non/existent/path.png");
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ promise_rejects_dom(t, "EncodingError", second_promise)
+ ]);
+}, document.title + " xlink:href changes fail decode; following bad decode fails.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/images/green.png");
+ var first_promise = img.decode();
+ img.setAttribute('href', "/non/existent/path.png");
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ promise_rejects_dom(t, "EncodingError", second_promise)
+ ]);
+}, document.title + " href changes fail decode; following bad decode fails.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes.html
new file mode 100644
index 0000000000..4b878c1bae
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>HTMLImageElement.prototype.decode(), src/srcset mutation tests.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+// src tests
+// -------------------
+promise_test(function(t) {
+ var img = new Image();
+ img.src = "/images/green.png";
+ var promise = img.decode();
+ img.src = "/images/green.svg";
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " src changes fail decode.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.src = "/images/green.png";
+ var first_promise = img.decode();
+ img.src = "/images/blue.png";
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ second_promise
+ ]);
+}, document.title + " src changes fail decode; following good png decode succeeds.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.src = "/images/green.png";
+ var first_promise = img.decode();
+ img.src = "/images/green.svg";
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ second_promise
+ ]);
+}, document.title + " src changes fail decode; following good svg decode succeeds.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.src = "/images/green.png";
+ var first_promise = img.decode();
+ img.src = "/non/existent/path.png";
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ promise_rejects_dom(t, "EncodingError", second_promise)
+ ]);
+}, document.title + " src changes fail decode; following bad decode fails.");
+
+promise_test(function(t) {
+ return new Promise(function(resolve, reject) {
+ var img = new Image();
+ // We wait for an onload, since the "Updating the image data" spec states
+ // that if a new microtask is scheduled, the old one is canceled so
+ // without the onload, the first decode request would be requested when the
+ // img.src is empty. With an onload, we ensure that the img.src is set and
+ // the image exists before issuing the first decode, then we verify that the
+ // src change to the same value does not prevent that request from
+ // succeeding.
+ img.onload = t.step_func(function() {
+ img.onload = null;
+
+ var first_promise = img.decode();
+ img.src = "/images/green.png";
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ resolve(Promise.all([first_promise, second_promise]));
+ });
+ img.src = "/images/green.png";
+ });
+}, document.title + " src changes to the same path succeed.");
+
+// srcset tests
+// -------------------
+promise_test(function(t) {
+ var img = new Image();
+ img.srcset = "/images/green.png 100w";
+ var promise = img.decode();
+ img.srcset = "/images/green.svg 100w";
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " srcset changes fail decode.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.srcset = "/images/green.png 100w";
+ var first_promise = img.decode();
+ img.srcset = "/images/green.svg 100w";
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ second_promise
+ ]);
+}, document.title + " srcset changes fail decode; following good decode succeeds.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.srcset = "/images/green.png 100w";
+ var first_promise = img.decode();
+ img.srcset = "/non/existent/path.png 100w";
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ promise_rejects_dom(t, "EncodingError", second_promise)
+ ]);
+}, document.title + " srcset changes fail decode; following bad decode fails.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-picture.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-picture.html
new file mode 100644
index 0000000000..2f4d5e7c41
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-picture.html
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>HTMLImageElement.prototype.decode(), picture tests.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<picture>
+<source srcset="/images/green.png">
+<source srcset="/images/blue.png">
+<img id="testimg">
+</picture>
+
+<script>
+"use strict";
+
+promise_test(function() {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ source.srcset = "/images/green.png";
+
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG source decodes with undefined.");
+
+promise_test(function() {
+ var img = document.getElementById("testimg");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with multiple sources decodes with undefined.");
+
+promise_test(function() {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ source.srcset = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIA" +
+ "AAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QUSEioKsy" +
+ "AgywAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAW" +
+ "SURBVAjXY9y3bx8DAwPL58+fGRgYACktBRltLfebAAAAAElFTkSuQmCC";
+
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG data URL source decodes with undefined.");
+
+promise_test(function() {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ source.srcset = "/images/green.svg";
+
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with SVG source decodes with undefined.");
+
+promise_test(function(t) {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ source.srcset = "/non/existent/path.png";
+
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Non-existent source fails decode.");
+
+promise_test(function(t) {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ source.srcset = "data:image/png;base64,iVBO00PDR0BADBEEF00KGg";
+
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Corrupt image in src fails decode.");
+
+promise_test(function(t) {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Image without srcset fails decode.");
+
+promise_test(function() {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ source.srcset = "/images/green.png";
+
+ var first_promise = img.decode();
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ first_promise,
+ second_promise
+ ]);
+}, document.title + " Multiple decodes for images with src succeed.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-svg.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-svg.tentative.html
new file mode 100644
index 0000000000..047470f1e3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-svg.tentative.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>SVGImageElement.prototype.decode(), basic tests.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+// src tests
+// -------------------
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.png");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG xlink:href decodes with undefined.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/images/green.png");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG href decodes with undefined.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAA" +
+ "D91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QUSEioKsyAgyw" +
+ "AAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAWSURBVA" +
+ "jXY9y3bx8DAwPL58+fGRgYACktBRltLfebAAAAAElFTkSuQmCC");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG data URL xlink:href decodes with undefined.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href',
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAA" +
+ "D91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QUSEioKsyAgyw" +
+ "AAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAWSURBVA" +
+ "jXY9y3bx8DAwPL58+fGRgYACktBRltLfebAAAAAElFTkSuQmCC");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG data URL href decodes with undefined.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.svg");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with SVG xlink:href decodes with undefined.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/images/green.svg");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with SVG href decodes with undefined.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/non/existent/path.png");
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Non-existent xlink:href fails decode.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/non/existent/path.png");
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Non-existent href fails decode.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "data:image/png;base64,iVBO00PDR0BADBEEF00KGg");
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Corrupt image in xlink:href fails decode.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "data:image/png;base64,iVBO00PDR0BADBEEF00KGg");
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Corrupt image in href fails decode.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Image without xlink:href or href fails decode.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.png");
+ var first_promise = img.decode();
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ first_promise,
+ second_promise
+ ]);
+}, document.title + " Multiple decodes with a xlink:href succeed.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/images/green.png");
+ var first_promise = img.decode();
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ first_promise,
+ second_promise
+ ]);
+}, document.title + " Multiple decodes with a href succeed.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach-svg.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach-svg.tentative.html
new file mode 100644
index 0000000000..0fc49e6036
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach-svg.tentative.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>SVGImageElement.prototype.decode(), attach to DOM before promise resolves.</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<svg></svg>
+<script>
+"use strict";
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.png");
+ const promise = img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+ // Don't wait for the promise to resolve before attaching the image.
+ // The promise should still resolve successfully.
+ document.querySelector('svg').appendChild(img);
+ return promise;
+}, document.title);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach.html
new file mode 100644
index 0000000000..be680da619
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>HTMLImageElement.prototype.decode(), attach to DOM before promise resolves.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body></body>
+
+<script>
+"use strict";
+
+promise_test(function() {
+ const img = new Image();
+ img.src = "/images/green.png";
+ const promise = img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+ // Don't wait for the promise to resolve before attaching the image.
+ // The promise should still resolve successfully.
+ document.body.appendChild(img);
+ return promise;
+}, document.title);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode.html
new file mode 100644
index 0000000000..fac61a1446
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>HTMLImageElement.prototype.decode(), basic tests.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+// src tests
+// -------------------
+promise_test(function() {
+ var img = new Image();
+ img.src = "/images/green.png";
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG src decodes with undefined.");
+
+promise_test(function() {
+ var img = new Image();
+ img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAA" +
+ "D91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QUSEioKsyAgyw" +
+ "AAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAWSURBVA" +
+ "jXY9y3bx8DAwPL58+fGRgYACktBRltLfebAAAAAElFTkSuQmCC";
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG data URL src decodes with undefined.");
+
+promise_test(function() {
+ var img = new Image();
+ img.src = "/images/green.svg";
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with SVG src decodes with undefined.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.src = "/non/existent/path.png";
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Non-existent src fails decode.");
+
+promise_test(function(t) {
+ var inactive_doc = document.implementation.createHTMLDocument();
+ var img = inactive_doc.createElement("img");
+ img.src = "/images/green.png";
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Inactive document fails decode.");
+
+promise_test(function(t) {
+ var inactive_doc = document.implementation.createHTMLDocument();
+ var img = document.createElement("img");
+ img.src = "/images/green.png";
+ var promise = img.decode();
+ inactive_doc.body.appendChild(img);
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Adopted active image into inactive document fails decode.");
+
+promise_test(function() {
+ var inactive_doc = document.implementation.createHTMLDocument();
+ var img = inactive_doc.createElement("img");
+ img.src = "/images/green.png";
+ document.body.appendChild(img);
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Adopted inactive image into active document succeeds.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.src = "data:image/png;base64,iVBO00PDR0BADBEEF00KGg";
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Corrupt image in src fails decode.");
+
+promise_test(function(t) {
+ var img = new Image();
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Image without src/srcset fails decode.");
+
+promise_test(function() {
+ var img = new Image();
+ img.src = "/images/green.png";
+ var first_promise = img.decode();
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ first_promise,
+ second_promise
+ ]);
+}, document.title + " Multiple decodes for images with src succeed.");
+
+// srcset tests
+// -------------------
+promise_test(function() {
+ var img = new Image();
+ img.srcset = "/images/green.png 100w";
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG srcset decodes with undefined.");
+
+promise_test(function() {
+ var img = new Image();
+ img.srcset = "/images/green.svg 100w";
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with SVG srcset decodes with undefined.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.srcset = "/non/existent/path.png 100w";
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Non-existent srcset fails decode.");
+
+promise_test(function() {
+ var img = new Image();
+ img.srcset = "/images/green.png 100w";
+ var first_promise = img.decode();
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ first_promise,
+ second_promise
+ ]);
+}, document.title + " Multiple decodes for images with srcset succeed.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-detached.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-detached.html
new file mode 100644
index 0000000000..5c68de29e9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-detached.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Detached image blocks load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var img_loaded = false;
+
+var img = new Image();
+img.onload = function() {
+ img_loaded = true;
+};
+img.src = "/images/blue.png?pipe=trickle(d2)";
+
+test(function() {
+ assert_false(img_loaded);
+}, "setting img.src is async");
+
+async_test(function(t) {
+ document.addEventListener("DOMContentLoaded", t.step_func_done(function() {
+ assert_false(img_loaded);
+ }));
+}, "DOMContentLoaded doesn't wait for images");
+
+async_test(function(t) {
+ window.addEventListener("load", t.step_func_done(function() {
+ assert_true(img_loaded);
+ }));
+}, "load waits for images");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-until-move-to-empty-source.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-until-move-to-empty-source.html
new file mode 100644
index 0000000000..7b61606c47
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-until-move-to-empty-source.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Inline image element blocks load until source is changed to empty source</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<img src="/images/blue.png?pipe=trickle(d100)">
+<script>
+
+async_test(t => {
+ const image = document.querySelector("img");
+
+ assert_false(image.complete, "The image is loading initially");
+
+ // Complete the test as soon as we obtained the window "load" event,
+ // which should happen as soon as the image stops loading by moving
+ // to an empty source.
+ window.addEventListener("load", t.step_func_done(() => {
+ assert_true(image.complete, "The image is no longer loading once the window 'load' event is dispatched");
+ }));
+
+ // Stop loading the image.
+ image.src = "";
+}, "Image element delays window's load event until the image changes to empty source");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event.html
new file mode 100644
index 0000000000..ac0cf29d3f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Inline image element blocks load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var img_loaded = false;
+</script>
+<img src="/images/blue.png?pipe=trickle(d2)" onload="img_loaded = true;">
+<script>
+test(function() {
+ assert_false(img_loaded);
+}, "script execution doesn't wait for the image to load");
+
+async_test(function(t) {
+ document.addEventListener("DOMContentLoaded", t.step_func_done(function() {
+ assert_false(img_loaded);
+ }));
+}, "DOMContentLoaded doesn't wait for images");
+
+async_test(function(t) {
+ window.addEventListener("load", t.step_func_done(function() {
+ assert_true(img_loaded);
+ }));
+}, "Image element delays window's load event");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/disconnected-image-loading-lazy.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/disconnected-image-loading-lazy.html
new file mode 100644
index 0000000000..fe6d79fe2a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/disconnected-image-loading-lazy.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+async_test(t => {
+ x = new Image();
+ x.loading = "auto";
+ x.src = "resources/image.png?auto";
+ x.onload = t.step_func_done();
+ t.step_timeout(t.unreached_func("Disconnected loading=auto image loads " +
+ "successfully, and doesn't timeout"), 2000);
+}, "loading=auto for disconnected image");
+
+async_test(t => {
+ x = new Image();
+ x.loading = "eager";
+ x.src = "resources/image.png?eager";
+ x.onload = t.step_func_done();
+ t.step_timeout(t.unreached_func("Disconnected loading=eager image loads " +
+ "successfully, and doesn't timeout"), 2000);
+}, "loading=eager for disconnected image");
+
+async_test(t => {
+ x = new Image();
+ x.loading = "lazy";
+ x.src = "resources/image.png?lazy";
+ x.onload = t.unreached_func("Disconnected loading=lazy image loads lazily.");
+ t.step_timeout(t.step_func_done(), 2000);
+}, "loading=lazy for disconnected image");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-adopt-base-url.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-adopt-base-url.html
new file mode 100644
index 0000000000..ea63114d57
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-adopt-base-url.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Document base URL adopted img test</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element" />
+<link rel="match" href="document-base-url-ref.html">
+<base href="resources/" />
+<iframe></iframe>
+<script>
+ var iframe = document.querySelector('iframe');
+ var i = iframe.contentDocument.createElement('img');
+ i.src = "cat.jpg";
+ document.body.appendChild(i);
+ iframe.remove();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url-ref.html
new file mode 100644
index 0000000000..6e55b21ff0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Document base URL img test</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element" />
+<img src="resources/cat.jpg" alt="cat">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url.html
new file mode 100644
index 0000000000..074209cc04
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Document base URL img test</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element" />
+<link rel="match" href="document-base-url-ref.html">
+<base href="resources/" />
+<img src="cat.jpg" alt="cat">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-destroyed-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-destroyed-crash.html
new file mode 100644
index 0000000000..da43099f71
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-destroyed-crash.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>&lt;img> loading in destroyed document</title>
+<iframe></iframe>
+<script>
+onload = function() {
+ const img = new Image();
+ img.onload = function() {
+ const iframe = document.querySelector('iframe');
+ iframe.contentDocument.createElement('div').innerHTML =
+ `<picture>
+ <source srcset="nonexistent.png">
+ <img src="data:image/gif;base64,R0lGODlhCgAKAIAAAP/MAAAAACH5BAAAAAAALAAAAAAKAAoAAAIIhI+py+0PYysAOw==">
+ </picture>`;
+ iframe.remove();
+ };
+ img.src = 'data:image/gif;base64,R0lGODlhCgAKAIAAAP/MAAAAACH5BAAAAAAALAAAAAAKAAoAAAIIhI+py+0PYysAOw==';
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/iframed.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/iframed.sub.html
new file mode 100644
index 0000000000..0f7ab9ae27
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/iframed.sub.html
@@ -0,0 +1,78 @@
+<!doctype html>
+
+<img
+data-desc="img (no src)"
+data-narrow=""
+data-wide=""
+data-no-change>
+
+<img src=""
+data-desc="img (empty src)"
+data-narrow=""
+data-wide=""
+data-no-change>
+
+<img src="/images/broken.png?30-{{GET[id]}}"
+data-desc="img (src only) broken image"
+data-narrow="/images/broken.png?30-{{GET[id]}}"
+data-wide="/images/broken.png?30-{{GET[id]}}"
+data-no-change>
+
+<img src="/images/green-1x1.png?40-{{GET[id]}}"
+data-desc="img (src only) valid image"
+data-narrow="/images/green-1x1.png?40-{{GET[id]}}"
+data-wide="/images/green-1x1.png?40-{{GET[id]}}"
+data-no-change>
+
+<img srcset="/images/broken.png?50-{{GET[id]}}"
+data-desc="img (srcset 1 cand) broken image"
+data-narrow="/images/broken.png?50-{{GET[id]}}"
+data-wide="/images/broken.png?50-{{GET[id]}}"
+data-no-change>
+
+<img srcset="/images/green-1x1.png?60-{{GET[id]}}"
+data-desc="img (srcset 1 cand) valid image"
+data-narrow="/images/green-1x1.png?60-{{GET[id]}}"
+data-wide="/images/green-1x1.png?60-{{GET[id]}}"
+data-no-change>
+
+<picture>
+<source media="(max-width:500px)" srcset="/images/broken.png?70-{{GET[id]}}">
+<img src="/images/broken.png?71-{{GET[id]}}"
+data-desc="picture: source (max-width:500px) broken image, img broken image"
+data-narrow="/images/broken.png?70-{{GET[id]}}"
+data-wide="/images/broken.png?71-{{GET[id]}}">
+</picture>
+
+<picture>
+<source media="(max-width:500px)" srcset="/images/broken.png?80-{{GET[id]}}">
+<img src="/images/green-2x2.png?81-{{GET[id]}}"
+data-desc="picture: source (max-width:500px) broken image, img valid image"
+data-narrow="/images/broken.png?80-{{GET[id]}}"
+data-wide="/images/green-2x2.png?81-{{GET[id]}}">
+</picture>
+
+<picture>
+<source media="(max-width:500px)" srcset="/images/green-1x1.png?90-{{GET[id]}}">
+<img src="/images/broken.png?91-{{GET[id]}}"
+data-desc="picture: source (max-width:500px) valid image, img broken image"
+data-narrow="/images/green-1x1.png?90-{{GET[id]}}"
+data-wide="/images/broken.png?91-{{GET[id]}}">
+</picture>
+
+<picture>
+<source media="(max-width:500px)" srcset="/images/green-1x1.png?100-{{GET[id]}}">
+<img src="/images/green-2x2.png?101-{{GET[id]}}"
+data-desc="picture: source (max-width:500px) valid image, img valid image"
+data-narrow="/images/green-1x1.png?100-{{GET[id]}}"
+data-wide="/images/green-2x2.png?101-{{GET[id]}}">
+</picture>
+
+<picture>
+<source media="(max-width:500px)" srcset="/images/green-1x1.png?110-{{GET[id]}}">
+<img src="/images/green-1x1.png?110-{{GET[id]}}"
+data-desc="picture: same URL in source (max-width:500px) and img"
+data-narrow="/images/green-1x1.png?110-{{GET[id]}}"
+data-wide="/images/green-1x1.png?110-{{GET[id]}}"
+data-no-change>
+</picture>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/viewport-change.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/viewport-change.html
new file mode 100644
index 0000000000..f6ae65708c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/viewport-change.html
@@ -0,0 +1,65 @@
+<!doctype html>
+<title>img viewport change</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<style>
+.narrow { width:50px }
+.wide { width:1000px }
+</style>
+<div id=log></div>
+<script>
+setup({explicit_done:true});
+
+function resolve(url) {
+ if (url === "") {
+ return url;
+ }
+ var a = document.createElement('a');
+ a.href = url;
+ return a.href;
+}
+
+function insertIframe(className) {
+ var iframe = document.createElement('iframe');
+ iframe.className = className;
+ iframe.src = 'iframed.sub.html?id=' + token();
+ document.body.appendChild(iframe);
+}
+insertIframe('narrow');
+insertIframe('wide');
+
+var start_date = new Date();
+
+onload = function() {
+ var load_time = new Date() - start_date;
+ var iframes = document.getElementsByTagName('iframe');
+ [].forEach.call(iframes, function(iframe) {
+ [].forEach.call(iframe.contentDocument.images, function(img) {
+ var expected = {wide:resolve(img.dataset.wide), narrow:resolve(img.dataset.narrow)};
+ var current = iframe.className;
+ var next = current === 'wide' ? 'narrow' : 'wide';
+ var expect_change = expected[next].indexOf('broken.png') === -1 && !('noChange' in img.dataset);
+
+ test(function() {
+ assert_equals(img.currentSrc, expected[current]);
+ }, img.dataset.desc + ', onload, ' + current);
+
+ async_test(function() {
+ img.onload = this.unreached_func('Got unexpected load event');
+ img.onerror = this.unreached_func('Got unexpected error event');
+ if (expect_change) {
+ img.onload = this.step_func_done(function() {
+ assert_equals(img.currentSrc, expected[next]);
+ });
+ } else {
+ setTimeout(this.step_func_done(), 500 + load_time);
+ }
+ }, img.dataset.desc + ', resize to ' + next);
+ });
+ iframe.classList.toggle('wide');
+ iframe.classList.toggle('narrow');
+ });
+ done();
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/historical-progress-event.window.js b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/historical-progress-event.window.js
new file mode 100644
index 0000000000..7c4e121b7c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/historical-progress-event.window.js
@@ -0,0 +1,16 @@
+async_test(t => {
+ const img = new Image();
+ t.add_cleanup(() => img.remove());
+ img.onloadstart = img.onprogress = img.onloadend = t.unreached_func("progress event fired");
+ img.onload = t.step_func_done(e => {
+ assert_true(e instanceof Event);
+ assert_false(e instanceof ProgressEvent);
+ });
+ img.src = "/images/rrgg-256x256.png";
+ document.body.append(img);
+}, "<img> does not support ProgressEvent or loadstart/progress/loadend");
+
+test(t => {
+ assert_equals(document.body.onloadend, undefined);
+ assert_equals(window.onloadend, undefined);
+}, "onloadend is not exposed");
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-1.jpg b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-1.jpg
new file mode 100644
index 0000000000..2fb0255609
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-1.jpg
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-base-url.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-base-url.html
new file mode 100644
index 0000000000..932cd92b41
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-base-url.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Image load parses URL after microtask runs</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+// See https://github.com/whatwg/html/issues/7383 and
+// https://chromium-review.googlesource.com/c/chromium/src/+/3311225.
+// This test asserts two things:
+// 1.) That Document base URL modifications that take place in between an
+// image loading microtask being scheduled and executed are reflected in
+// the final image request
+// 2.) That subsequent changes to a Document's base URL before an image is
+// inserted into the DOM do not lead to the image being refetched when it
+// is inserted asynchronously later. This is because image insertion is
+// not a relevant mutation
+// (https://html.spec.whatwg.org/#relevant-mutations).
+promise_test(async t => {
+ const image = new Image();
+ image.src = 'green.png';
+
+ // Dynamically insert a <base> tag that should influence the above image
+ // request because the above code triggers a microtask to continue fetching
+ // the image, which will run while we await `loadPromise` below.
+ const base = document.createElement('base');
+ base.setAttribute('href', 'resources/');
+ document.head.append(base);
+
+ const loadPromise = new Promise((resolve, reject) => {
+ image.addEventListener('load', e => {
+ resolve();
+ }, {once: true});
+
+ image.addEventListener('error', e => {
+ reject('The image must load');
+ }, {once: true});
+ });
+
+ // The image should load successfully, since its request was influenced by the
+ // <base> element which points the request to the right directory.
+ await loadPromise;
+
+ // Now manipulate the <base> element to point to a bogus directory.
+ base.setAttribute('href', 'bogus/');
+ document.body.append(image);
+
+ const timeoutPromise = new Promise(resolve => t.step_timeout(resolve, 1500));
+ const imageErrorPromise = new Promise((resolve, reject) => {
+ image.addEventListener('load', e => {
+ reject('The image should not be refetched upon insertion and load, ' +
+ 'because (1) insertion is not a relevant mutation, and (2) the ' +
+ 'new relative URL should not resolve to a real resource');
+ }, {once: true});
+
+ image.addEventListener('error', e => {
+ reject('The image should not be refetched upon insertion, because ' +
+ 'insertion is not a relevant mutation');
+ }, {once: true});
+ });
+
+ await Promise.race([timeoutPromise, imageErrorPromise]);
+}, "An image should not be refetched upon insertion asynchronously after its " +
+ "Document's base URL changes");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change-ref.html
new file mode 100644
index 0000000000..ea80d8b545
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<style>
+#change {
+ height:75px;
+ width:75px;
+}
+</style>
+<img id="change" src="/images/green-16x16.png"></img>
+<img src="/images/green-16x16.png"></img>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change.html
new file mode 100644
index 0000000000..d3e7ee4171
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Composited images correctly re-raster when the image and bounds change</title>
+<meta charset="utf-8">
+<meta name=fuzzy content="maxDifference=0-150;totalPixels=0-296">
+<link rel="match" href="image-compositing-change-ref.html"/>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+<style>
+#change {
+ will-change:transform;
+ height:426px; width:426px;
+}
+</style>
+<img id="change" src="image.png"></img>
+<img id="original" src="../../../../images/green-16x16.png"></img>
+<script>
+window.onload = () => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ let image = document.querySelector('#change');
+ image.style.width = image.style.height = "75px";
+ image.src = original.src;
+
+ requestAnimationFrame(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ });
+ });
+ });
+}
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change-ref.html
new file mode 100644
index 0000000000..852a47687e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<style>
+html { overflow: hidden; }
+#change {
+ will-change:transform;
+ width:200vw;
+ height:200vh;
+ position:absolute;
+ top: 0px;
+ left: 0px;
+}
+</style>
+<img id="change" src="image.png"></img>
+<div id="placeholder" style="position:relative">div</div>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change.html
new file mode 100644
index 0000000000..515f88e3f4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Composited images correctly display under large scale transform changes</title>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<link rel="match" href="image-compositing-large-scale-change-ref.html"/>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+<style>
+html { overflow: hidden; }
+#change {
+ will-change:transform;
+ width:1000px;
+ height:1000px;
+ position:absolute;
+ top: calc(50% - 5px);
+ left: calc(50% - 5px);
+}
+</style>
+<img id="change" src="image.png"></img>
+<div id="placeholder" style="position:relative"></div>
+<script>
+window.onload = () => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ let image = document.querySelector('#change');
+ image.style.transform = 'scale(20)';
+ placeholder.innerText = "div";
+
+ requestAnimationFrame(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ });
+ });
+ });
+}
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-eager.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-eager.html
new file mode 100644
index 0000000000..54e169f867
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-eager.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='eager' load immediately regardless of their
+ position with respect to the viewport</title>
+ <link rel="author" title="Scott Little" href="mailto:sclittle@chromium.org">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t = async_test("Test that images with loading='eager' load " +
+ "immediately regardless of their position with " +
+ "respect to the viewport.");
+
+ let has_in_viewport_loaded = false;
+ const in_viewport_img_onload = t.step_func(() => {
+ assert_false(has_in_viewport_loaded,
+ "The in_viewport element should load only once.");
+ has_in_viewport_loaded = true;
+ });
+
+ let has_below_viewport_loaded = false;
+ const below_viewport_img_onload = t.step_func(() => {
+ assert_false(has_below_viewport_loaded,
+ "The below_viewport element should load only once.");
+ has_below_viewport_loaded = true;
+ });
+
+ window.addEventListener("load", t.step_func_done(() => {
+ assert_true(has_in_viewport_loaded,
+ "The in_viewport element should have loaded before window.load().");
+ assert_true(has_below_viewport_loaded,
+ "The below_viewport element should have loaded before window.load().");
+ }));
+
+</script>
+
+<body>
+ <img id="in_viewport" src="resources/image.png?in-viewport" loading="eager" onload="in_viewport_img_onload();">
+ <div style="height:10000px;"></div>
+ <!-- The below_viewport element loads very slowly in order to ensure that the
+ window load event is blocked on it. -->
+ <img id="below_viewport"
+ src="resources/image.png?below-viewport&pipe=trickle(d2)"
+ loading="eager" onload="below_viewport_img_onload();">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-available.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-available.html
new file mode 100644
index 0000000000..1e58c43c86
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-available.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<title>The list of available images gets checked before deciding to make a load lazy</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#update-the-image-data">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#will-lazy-load-image-steps">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1709577">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<img src="/images/green-256x256.png">
+<div style="height:1000vh;"></div>
+<script>
+promise_test(async t => {
+ await new Promise(resolve => {
+ window.addEventListener("load", resolve);
+ });
+ let nonLazy = document.querySelector("img");
+ assert_equals(nonLazy.width, 256);
+ assert_equals(nonLazy.height, 256);
+
+ let lazy = document.createElement("img");
+ lazy.loading = "lazy";
+ lazy.src = nonLazy.src;
+ document.body.appendChild(lazy);
+
+ await new Promise(resolve => setTimeout(resolve));
+
+ assert_equals(lazy.width, 256, "The list of available images should be checked before delaying the image load");
+ assert_equals(lazy.height, 256, "The list of available images should be checked before delaying the image load");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url-2.html
new file mode 100644
index 0000000000..e3a4a5f96e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url-2.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<head>
+ <title>Deferred loading=lazy images load relative to the document's base URL
+ at parse-time</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="author" title="Raj T" href="mailto:rajendrant@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<script>
+ const below_viewport_img = new ElementLoadPromise("below-viewport");
+
+ let has_window_loaded = false;
+
+ async_test(t => {
+ // Change the document's base URL to a bogus one, and scroll the
+ // below-viewport img into view. When it loads, it should load relative
+ // to the old base URL computed at parse-time.
+ window.addEventListener("load", t.step_func(() => {
+ window.history.pushState(2, document.title,
+ '/invalid-url-where-no-subresources-exist/')
+ has_window_loaded = true;
+ below_viewport_img.element().scrollIntoView();
+ }));
+
+ below_viewport_img.promise.then(t.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "Below-viewport loading=lazy images do not block the " +
+ "window load event");
+ }));
+
+ below_viewport_img.promise.catch(
+ t.unreached_func("The image request should not load relative to the " +
+ "current (incorrect) base URL.")
+ );
+ }, "When a loading=lazy image is loaded, it loads relative to the " +
+ "document's base URL computed at parse-time.");
+</script>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <script>
+ // Change the document's base URL so that the img request parses relative
+ // to it when it sets up the request at parse-time.
+ window.history.pushState(1, document.title, 'resources/')
+ </script>
+ <img id="below-viewport" src="image.png?base-url-2" loading="lazy"
+ onload="below_viewport_img.resolve()"
+ onerror="below_viewport_img.reject()">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url.html
new file mode 100644
index 0000000000..01ce961d0f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<head>
+ <title>Deferred images with loading='lazy' use the original
+ base URL specified at parse-time</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+ <base href='/html/semantics/embedded-content/the-img-element/resources/'>
+</head>
+
+<script>
+ const below_viewport_img = new ElementLoadPromise("below-viewport");
+
+ let has_window_loaded = false;
+
+ async_test(t => {
+ // At this point, the below-viewport image's request has been set-up, and
+ // its URL is the URL that was parsed relative to the document's base URL
+ // at this time. Any changes to the document's base URL from this point
+ // forward should not impact the image when we scroll it in-view. This is
+ // because the next step in the #updating-the-img-data algorithm is to to
+ // fetch the request that has already been set up. Now we'll change the
+ // document's base URL, and scroll the image in-view.
+ window.addEventListener("load", t.step_func(() => {
+ const base = document.querySelector('base');
+ base.href = '/invalid-url-where-no-subresources-exist/';
+ has_window_loaded = true;
+ below_viewport_img.element().scrollIntoView();
+ }));
+
+ below_viewport_img.promise.then(t.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "Below-viewport loading=lazy images do not block the " +
+ "window load event");
+ }));
+
+ below_viewport_img.promise.catch(
+ t.unreached_func("The image request should not load relative to the " +
+ "current (incorrect) base URL.")
+ );
+ }, "When a loading=lazy image is loaded, it loads relative to the " +
+ "document's base URL computed at parse-time.");
+</script>
+
+<body>
+ <div style="height:1000vh"></div>
+ <img id="below-viewport" src="image.png?base-url" loading="lazy"
+ onload="below_viewport_img.resolve()"
+ onerror="below_viewport_img.reject()">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-below-viewport-dynamic.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-below-viewport-dynamic.html
new file mode 100644
index 0000000000..78f18f0c23
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-below-viewport-dynamic.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<head>
+ <title>Below viewport images with loading='lazy' and changed to
+ loading='eager' load and do not block the window load event</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t = async_test("Test that below viewport images with loading='lazy' " +
+ "and changed to loading='eager' load and do not block " +
+ "the window load event.");
+
+ let has_below_viewport_loaded = false;
+ let has_window_loaded = false;
+
+ window.addEventListener("load", t.step_func(function() {
+ assert_false(has_window_loaded,
+ "The window load event should only fire once.");
+ has_window_loaded = true;
+ }));
+
+ const below_viewport_img_onload = t.step_func_done(function() {
+ assert_false(has_below_viewport_loaded,
+ "The in_viewport element should load only once.");
+ assert_true(has_window_loaded,
+ "The window load event should have fired before " +
+ "below_viewport loaded.");
+ has_below_viewport_loaded = true;
+ });
+</script>
+
+<body>
+ <div style="height:10000px;"></div>
+ <img id="below_viewport" src="resources/image.png?below-viewport-dynamic&pipe=trickle(d2)"
+ loading="lazy" onload="below_viewport_img_onload();">
+ <script>
+ assert_false(has_window_loaded,
+ "The window load event should not fire before " +
+ "changing below_viewport to loading='eager'.");
+ document.getElementById("below_viewport").loading = 'eager';
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path-ref.html
new file mode 100644
index 0000000000..05a60034ad
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<img src="resources/image.png">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path.html
new file mode 100644
index 0000000000..55f134a701
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="author" title="Xianzhu Wang" href="mailto:wangxianzhu@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes">
+<link rel="help" href="https://crbug.com/1308299">
+<link rel="match" href="image-loading-lazy-clip-path-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<img id=target loading="lazy"
+ src="resources/image.png"
+ style="vertical-align: middle; clip-path: polygon(0 0, 110% 0, 110% 110%, 0 110%, 0 0)">
+<script>
+ target.onload = takeScreenshot;
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-crossorigin-change.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-crossorigin-change.sub.html
new file mode 100644
index 0000000000..84efc7b0d1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-crossorigin-change.sub.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<head>
+ <title>Deferred images with loading='lazy' use the latest crossorigin attribute</title>
+ <link rel="author" title="Raj T" href="mailto:rajendrant@chromium.org">
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<script>
+ const img = new ElementLoadPromise("cross-origin");
+
+ async_test(function(t) {
+ window.addEventListener("load", t.step_func(() => {
+ // At this point the image's #updating-the-image-data algorithm has been
+ // invoked, and the image request has been deferred. The deferred
+ // cross-origin image request was created with the `no-cors` request mode,
+ // which would succeed to load the cross-origin image.
+ // While the request is deferred, we'll set the `crossorigin` attribute to a
+ // value that would cause the image request to fail. Since `crossorigin`
+ // mutations trigger another #updating-the-image-data invocation (replacing
+ // the first one), when we scroll the image into view, the image should be
+ // fetched with the latest `crossorigin` attribute value, and fail to load.
+ img.element().crossOrigin = 'anonymous';
+ img.element().scrollIntoView();
+ }));
+
+ img.promise
+ .then(t.unreached_func("The image should not load."))
+ .catch(t.step_func(() => { img.element().onload = t.step_func_done(); img.element().src = 'resources/image.png'; }));
+ }, "Test that when deferred image is loaded, it uses the latest crossorigin attribute.");
+</script>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <img id="cross-origin" loading="lazy"
+ src='http://{{hosts[alt][]}}:{{ports[http][0]}}/html/semantics/embedded-content/the-img-element/resources/image.png'
+ onload="img.resolve();" onerror="img.reject();">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https-ref.html
new file mode 100644
index 0000000000..05a60034ad
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<img src="resources/image.png">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https.html
new file mode 100644
index 0000000000..809068dd05
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <title>Lazy loaded Images with data url placeholders can be overwritten by a src change</title>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#update-the-image-data">
+ <link rel="match" href="image-loading-lazy-data-url-to-https-ref.html">
+ <script src="/common/reftest-wait.js"></script>
+</head>
+
+<body>
+ <img id="image" loading="lazy" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 872 490' width='872' height='490' style='background: green' %3E%3C/svg%3E">
+
+<script>
+ const image = document.querySelector('#image');
+
+ window.onload = function() {
+ // trigger intersection observer through forced layout.
+ image.offsetWidth;
+ image.setAttribute("src", 'resources/image.png');
+ setTimeout(() => { takeScreenshot(); }, 100);
+ };
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-different-crossorigin.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-different-crossorigin.html
new file mode 100644
index 0000000000..1ea46ffcc5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-different-crossorigin.html
@@ -0,0 +1,63 @@
+<!doctype html>
+<html>
+<title>Lazyload images cannot load immediately from the list of available images if their tuple doesn't match other images in that list</title>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="author" title="Przemyslaw Gorszkowski" href="mailto:pgorszkowski@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- A `loading=lazy` image will be placed below this div so that it is below
+ the viewport -->
+<div id="img-container"></div>
+<div style="height: 1000vh;"></div>
+<div id="below-viewport-img-container"></div>
+
+<script>
+const image_path = 'resources/image.png?image-lazy-loading-lazy-different-crossorigin-' + Math.random();
+
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = resolve;
+ img.onerror = e => { reject(new Error("The img should not fail to load")) };
+ document.querySelector('#img-container').append(img);
+ img.src = image_path;
+ });
+
+
+ // At this point, the image fetched eagerly above exists in the "list of
+ // available images". As per the spec's #updating-the-image-data algorithm [1]
+ // step 6, the "list of avalable images" is consulted before we take any
+ // lazyload-specific action. This means that lazyload images can load eagerly
+ // if they target a resource in the list of available images (includes cors
+ // settings attribute matching [2]). The image from "img-container" does not
+ // have "crossorigin" attribute and the lazyload image has crossorigin="anonymous".
+ // This means that lazyload image cannot be loaded eagerly from the list of available images.
+ // [1]: https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data
+ // [2]: https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attribute
+ const lazyload_image_promise = new Promise((resolve, reject) => {
+ const img = new Image();
+ img.loading = 'lazy';
+ img.crossOrigin = "anonymous";
+ img.onload = e => {
+ reject("The img should not be loaded from the list of available images because of different 'crossorigin'") };
+ img.onerror = e => { reject("The img should not fail to load") };
+
+ document.querySelector('#below-viewport-img-container').append(img);
+ img.src = image_path;
+ });
+ const timeout_promise = new Promise((resolve, reject) => {
+ t.step_timeout(() => {
+ resolve("The `loading=lazy` image should not load immediately from " +
+ "the list of available images because of different 'crossorigin'");
+ }, 2000);
+ });
+
+ // The `timeout_promise` should resolve first because lazyload image is not
+ // able to eagerly use resource from the "list of available images" if there
+ // is a difference in 'crossorigin'.
+ await Promise.race([lazyload_image_promise, timeout_promise]);
+}, "Lazyload images cannot load immediately from the list of available images if their tuple doesn't match other images in that list");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-empty-src.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-empty-src.html
new file mode 100644
index 0000000000..2a0aefea1d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-empty-src.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<title>Lazy loaded Images handle correctly when setting src to empty</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#update-the-image-data">
+<div id=log></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<img id="image" loading="lazy" src="resources/image.png">
+
+<script>
+ const image = document.querySelector('#image');
+
+async_test(function(t) {
+ image.onerror = t.step_func(function(e) {
+ assert_equals(e.type, "error", "null image source check failed");
+ image.onload = t.step_func(function() {
+ t.done();
+ });
+ image.src = "resources/image.png";
+ });
+ image.src = "";
+}, "lazy loaded image and empty src");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-001.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-001.sub.html
new file mode 100644
index 0000000000..eed3644650
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-001.sub.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<head>
+<title>A below-viewport loading=lazy image in a cross origin iframe loads only
+ when scrolled into viewport</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes">
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+</head>
+
+<iframe id="iframe" width="500px" height="500px"></iframe>
+
+<script>
+promise_test(t => {
+ iframe.src =
+ get_host_info().HTTP_NOTSAMESITE_ORIGIN +
+ new URL("resources/", self.location).pathname +
+ "image-loading-lazy-below-viewport.html";
+
+ // Wait for the frame to report that its window load event fired.
+ return new Promise(resolve => {
+ window.addEventListener("message",
+ event => resolve(event.data), {once: true});
+ }).then(iframe_message => {
+ assert_equals(iframe_message, "window_loaded",
+ "The loading=lazy image should not block the iframe's load " +
+ "event");
+
+ // Tell the iframe to scroll the image element into view.
+ frames[0].postMessage("scroll", "*");
+
+ return new Promise(resolve => {
+ window.addEventListener("message", event => resolve(event.data));
+ });
+ }).then(iframe_message => {
+ assert_equals(iframe_message, "image_loaded",
+ "The below-viewport loading=lazy image should load only " +
+ "once scrolled into the viewport");
+
+ }); // new Promise();
+}); // promise_test.
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-002.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-002.sub.html
new file mode 100644
index 0000000000..85060d2193
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-002.sub.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<head>
+<title>A loading=lazy image in a below-viewport cross-origin iframe loads only
+ when the cross-origin iframe is scrolled into view</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes">
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+</head>
+
+<div style="height:1000vh;"></div>
+<iframe id="iframe" width="500px" height="500px"></iframe>
+
+<script>
+promise_test(t => {
+ iframe.src =
+ get_host_info().HTTP_NOTSAMESITE_ORIGIN +
+ new URL("resources/", self.location).pathname +
+ "image-loading-lazy-in-viewport.html";
+
+ // Wait for the frame to report that its window load event fired.
+ return new Promise(resolve => {
+ window.addEventListener("message",
+ event => resolve(event.data), {once: true});
+ }).then(iframe_message => {
+ assert_equals(iframe_message, "window_loaded",
+ "The loading=lazy image should not block the iframe's load " +
+ "event");
+
+ // Scroll the iframe into view, which also puts the image into view.
+ iframe.scrollIntoView();
+
+ return new Promise(resolve => {
+ window.addEventListener("message", event => resolve(event.data));
+ });
+ }).then(iframe_message => {
+ assert_equals(iframe_message, "image_loaded",
+ "The below-viewport loading=lazy image should load only " +
+ "once scrolled into the viewport");
+
+ }); // new Promise().
+
+}); // promise_test().
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-script-disabled-iframe.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-script-disabled-iframe.html
new file mode 100644
index 0000000000..fbcadd86c9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-script-disabled-iframe.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<head>
+<title>Images with loading='lazy' in script disabled iframe are not handled
+ as 'lazy'</title>
+<link rel="help" href="https://github.com/scott-little/lazyload">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<div style="height:1000vh;"></div>
+<iframe id="iframe" sandbox="allow-same-origin"
+ src="resources/image-loading-lazy-in-viewport.html">
+</iframe>
+<script>
+promise_test(async t => {
+ await new Promise(resolve => iframe.addEventListener("load", resolve));
+
+ const image = iframe.contentDocument.querySelector("img");
+
+ assert_true(image.complete,
+ "lazy-load image shouldn't be honored in script disabled iframe");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-far.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-far.html
new file mode 100644
index 0000000000..065fa939bc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-far.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 10000vh;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id="scroller">
+ <div id="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images do not load when far from viewport."
+ );
+
+ function img_onload() {
+ t.unreached_func(
+ "Lazy-loading image far from viewport should not load."
+ )();
+ }
+
+ t.step_timeout(() => {
+ t.done();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal-far.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal-far.html
new file mode 100644
index 0000000000..20274310f1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal-far.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ display: flex;
+ }
+
+ #spacer {
+ width: 10000vw;
+ height: 130px;
+ flex-shrink: 0;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ flex-shrink: 0;
+ }
+</style>
+
+<div id="scroller">
+ <div id="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images do not load when far from viewport."
+ );
+
+ function img_onload() {
+ t.unreached_func(
+ "Lazy-loading image far from viewport should not load."
+ )();
+ }
+
+ t.step_timeout(() => {
+ t.done();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal.html
new file mode 100644
index 0000000000..124dbaddf8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ display: flex;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ flex-shrink: 0;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ flex-shrink: 0;
+ }
+</style>
+
+<div id="scroller">
+ <div id="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-2.html
new file mode 100644
index 0000000000..5e25f45275
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-2.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id=scroller2>
+ <div id="spacer"></div>
+ <div id="scroller">
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-3.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-3.html
new file mode 100644
index 0000000000..0d6d95826b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-3.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ #scroller3 {
+ width: 120px;
+ height: 120px;
+ overflow: scroll;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id=scroller3>
+ <div id=scroller2>
+ <div id="scroller">
+ <div id="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+ </div>
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-4.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-4.html
new file mode 100644
index 0000000000..312e90bd49
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-4.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ .spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id=scroller2>
+ <div class="spacer"></div>
+ <div id="scroller">
+ <div class="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-5.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-5.html
new file mode 100644
index 0000000000..77bb3004d3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-5.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ display: flex;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ .spacer {
+ width: 130px;
+ height: 130px;
+ flex-shrink: 0;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ flex-shrink: 0;
+ }
+</style>
+
+<div id=scroller2>
+ <div class="spacer"></div>
+ <div id="scroller">
+ <div class="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested.html
new file mode 100644
index 0000000000..cc3e4d5e4b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id=scroller2>
+ <div id="scroller">
+ <div id="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller.html
new file mode 100644
index 0000000000..17f0e7d17a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id="scroller">
+ <div id="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-viewport-dynamic.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-viewport-dynamic.html
new file mode 100644
index 0000000000..39dd5dc1e9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-viewport-dynamic.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<head>
+ <title>In viewport images with loading='lazy' and changed to loading='eager'
+ do not block the window load event</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t = async_test("Test that in viewport images with loading='lazy' and " +
+ "changed to loading='eager' do not block the window " +
+ "load event.");
+
+ let has_in_viewport_loaded = false;
+ let has_window_loaded = false;
+
+ const in_viewport_img_onload = t.step_func_done(function() {
+ assert_false(has_in_viewport_loaded,
+ "The in_viewport element should load only once.");
+ assert_true(has_window_loaded,
+ "The window load event should fire before in_viewport image loads.");
+ has_in_viewport_loaded = true;
+ });
+
+ window.addEventListener("load", t.step_func(function() {
+ assert_false(has_window_loaded,
+ "The window load event should only fire once.");
+ has_window_loaded = true;
+ }));
+</script>
+
+<body>
+ <img id="in_viewport" src="resources/image.png?in-viewport-dynamic&pipe=trickle(d2)"
+ loading="lazy" onload="in_viewport_img_onload();">
+ <script>
+ document.getElementById("in_viewport").loading = 'eager';
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-document.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-document.html
new file mode 100644
index 0000000000..ff7e83105c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-document.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<head>
+<title>Moving loading='lazy' image into another top level document</title>
+<link rel="help" href="https://github.com/scott-little/lazyload">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<div style="height:1000vh;"></div>
+<img loading="lazy"
+ src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC">
+<script>
+promise_test(async t => {
+ let image_loaded = false;
+ const img = document.querySelector("img");
+ img.addEventListener("load", () => { image_loaded = true; });
+
+ await new Promise(resolve => window.addEventListener("load", resolve));
+
+ assert_false(image_loaded,
+ "lazy-load image shouldn't be loaded yet");
+
+ const anotherWin = window.open("resources/newwindow.html");
+
+ await new Promise(resolve => anotherWin.addEventListener("load", resolve));
+
+ anotherWin.document.body.appendChild(img);
+
+ assert_false(image_loaded,
+ "lazy-load image shouldn't be loaded yet");
+
+ img.scrollIntoView();
+
+ await new Promise(resolve => img.addEventListener("load", resolve));
+ assert_true(img.complete,
+ "Now the lazy-load image should be loaded");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-into-script-disabled-iframe.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-into-script-disabled-iframe.html
new file mode 100644
index 0000000000..79bfa24378
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-into-script-disabled-iframe.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<head>
+<title>A loading='lazy' image starts loading when the element is moved into
+ an iframe where script is disabled</title>
+<link rel="help" href="https://github.com/scott-little/lazyload">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<div style="height:1000vh;"></div>
+<iframe id="iframe" src="resources/image-loading-lazy-in-viewport.html">
+</iframe>
+<iframe id="sandboxediframe" sandbox="allow-same-origin">
+</iframe>
+<script>
+promise_test(async t => {
+ await new Promise(resolve => window.addEventListener('load', resolve));
+
+ const image = iframe.contentDocument.querySelector("img");
+
+ assert_false(image.complete, "lazy-load image shouldn't be loaded");
+
+ sandboxediframe.contentDocument.body.appendChild(image);
+ await new Promise(resolve => image.addEventListener("load", resolve));
+
+ assert_true(image.complete,
+ "lazy-load image shouldn't be honored in script disabled iframe");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multicol.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multicol.html
new file mode 100644
index 0000000000..20d52d4dfa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multicol.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='lazy' load when in the viewport</title>
+ <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t = async_test("Test that images with loading='lazy' under multicol load once they enter the viewport.");
+
+ let has_in_viewport_loaded = false;
+ let has_window_loaded = false;
+
+ const in_viewport_img_onload = t.step_func(function() {
+ assert_false(has_in_viewport_loaded, "The in_viewport element should load only once.");
+ has_in_viewport_loaded = true;
+ });
+
+ window.addEventListener("load", t.step_func_done(function() {
+ assert_true(has_in_viewport_loaded, "The in_viewport element should have loaded before window.load().");
+ assert_false(has_window_loaded, "The window load event should only fire once.");
+ has_window_loaded = true;
+ }));
+
+</script>
+
+<div class=texty style="column-count: 2; height: 300px">
+ <div style="border: 1px solid black">
+ <h2 style="column-span: all"></h2>
+ <img loading="lazy" src="resources/image.png?loading-lazy-multicol-first" width="160" height="120"
+ onload="in_viewport_img_onload()">
+ </div>
+</div>
+
+ <!--
+ This async script loads very slowly in order to ensure that, if the
+ below_viewport element has started loading, it has a chance to finish
+ loading before window load event fires, so that the test will dependably fail
+ in that case instead of potentially passing depending on how long different
+ resource fetches take.
+ -->
+ <script async src="/common/slow.py"></script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multiple-times.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multiple-times.html
new file mode 100644
index 0000000000..2d67150560
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multiple-times.html
@@ -0,0 +1,60 @@
+<head>
+ <title>Images with loading='lazy' can be lazy loaded multiple times</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <!-- This is used to represent the top of the viewport, so we can scroll the
+ below-viewport image out-of-view later in the test -->
+ <div id="top_div"></div>
+ <div style="height:1000vh;"></div>
+ <img id="below_viewport" loading="lazy" src="resources/image.png?image-loading-lazy-multiple-times-first">
+
+<script>
+ const t = async_test("Images with loading='lazy' can be lazy loaded multiple times");
+ const image = document.querySelector('#below_viewport');
+ const top_div = document.querySelector('#top_div');
+
+ let has_window_load_fired = false;
+
+ // This should be triggered first.
+ window.addEventListener('load', t.step_func(() => {
+ has_window_load_fired = true;
+ // Scroll the loading=lazy below-viewport image into view, so that it loads.
+ image.scrollIntoView();
+ }));
+
+ image.onload = t.step_func(() => {
+ assert_true(has_window_load_fired,
+ "The loading=lazy below-viewport image should not block the " +
+ "window load event");
+ changeImageSourceAndScrollToTop();
+ });
+
+ function changeImageSourceAndScrollToTop() {
+ top_div.scrollIntoView();
+
+ // Allow some time for scroll back to top, since we don't
+ // want the image to still be in the viewport and trigger a
+ // load due to the scroll being slow.
+ t.step_timeout(() => {
+ // Lazily load a "different" image.
+ image.src = 'resources/image.png?image-loading-lazy-multiple-times-second';
+ image.onload =
+ t.unreached_func("The loading=lazy below-viewport image should lazily " +
+ "load its second image, and not load it eagerly when " +
+ "the `src` attribute is changed");
+
+ // In 1s, scroll the image *back* into view, and record that it loads
+ // successfully.
+ t.step_timeout(() => {
+ image.onload = t.step_func_done();
+ image.scrollIntoView();
+ }, 1000);
+ }, 500);
+ }
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-negative-margin.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-negative-margin.html
new file mode 100644
index 0000000000..875160b4ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-negative-margin.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='lazy' defers images in a hidden area as a result
+ of negative margins</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <script>
+ window.negative_margin_test =
+ async_test("A loading=lazy image that is pulled into an `overflow: hidden` " +
+ "area by a negative margin will not load because " +
+ "IntersectionObserver sees it as non-intersecting");
+
+ // If the `negative_margin` image in the DOM loads, the test should fail
+ // immediately.
+ window.negative_margin_onload =
+ negative_margin_test.step_func_done(
+ negative_margin_test.unreached_func("The image with a negative margin " +
+ "should never load"));
+ </script>
+
+ <div style="width: 200px; height: 200px; overflow: hidden;">
+ <img id="negative_margin" width="5px"; style="margin-left: -10000vw;"
+ loading="lazy" src="resources/image.png?loading-lazy-negative-margin"
+ onload="window.negative_margin_onload()">
+ </div>
+
+ <script>
+ const intersection_observer_promise = new Promise(resolve => {
+ function io_callback(entries) {
+ assert_equals(entries.length, 1);
+ resolve(entries[0].isIntersecting);
+ }
+
+ const options = {
+ root: document,
+ rootMargin: '0px',
+ threshold: 1.0,
+ }
+
+ const observer = new IntersectionObserver(io_callback, options);
+ observer.observe(document.querySelector('#negative_margin'));
+ });
+
+ const timeout_promise = new Promise(resolve => {
+ window.negative_margin_test.step_timeout(resolve, 500);
+ });
+
+ Promise.all([intersection_observer_promise, timeout_promise]).then(
+ window.negative_margin_test.step_func_done(values => {
+ assert_equals(values.length, 2);
+ assert_equals(values[0], false, "The IntersectionObserver sees that " +
+ "the image does not intersect the " +
+ "viewport");
+ }));
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-referrerpolicy-change.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-referrerpolicy-change.sub.html
new file mode 100644
index 0000000000..110c36cca7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-referrerpolicy-change.sub.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<head>
+ <title>Deferred loading=lazy images are fetched with the latest
+ `referrerpolicy` attribute</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="author" title="Raj T" href="mailto:rajendrant@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<script>
+ const below_viewport_img = new ElementLoadPromise("below_viewport_img");
+
+ async_test(function(t) {
+ // At this point the image's #updating-the-image-data algorithm has been
+ // invoked, and the image request has been deferred. The deferred request
+ // was created with the default referrer policy, which will result in a
+ // `Referer` header being sent when the request is finally made. The request
+ // is also for an image that the server will send a broken response for if
+ // the request has a `Referer` header.
+ // While the request is deferred, we'll set the `referrerpolicy` attribute
+ // to `no-referrer`, which would cause the image request to succeed. Since
+ // `referrerpolicy` mutations trigger another #updating-the-image-data
+ // invocation (replacing the first one), when we scroll the image into view,
+ // the image should be fetched with no `Referer` header, and succeed.
+ window.addEventListener("load", t.step_func(() => {
+ below_viewport_img.element().referrerPolicy = "no-referrer";
+ below_viewport_img.element().scrollIntoView();
+ }));
+
+ below_viewport_img.promise
+ .then(t.step_func_done())
+ .catch(t.unreached_func("The image request should successfully load"))
+ }, "Test that when a deferred image is loaded, it uses the latest referrerpolicy");
+</script>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <img id="below_viewport_img"
+ src="resources/referrer-checker-img.py?expected_referrer="
+ loading="lazy" referrerpolicy="unsafe-url"
+ onload="below_viewport_img.resolve();"
+ onerror="below_viewport_img.reject();">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-relevant-mutations.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-relevant-mutations.html
new file mode 100644
index 0000000000..3a2662451e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-relevant-mutations.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<head>
+ <title>Relevant mutations on deferred loading=lazy images should not trigger
+ a request</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ let below_viewport_1_loaded = false,
+ below_viewport_2_loaded = false,
+ below_viewport_3_loaded = false
+
+ // For general lazy loading behavior.
+ promise_test(() => {
+ // When the page loads, start the rest of the tests.
+ return new Promise(resolve => {
+ window.addEventListener("load", e => {
+ const kAssertion = 'image should never load';
+ assert_false(below_viewport_1_loaded, `below-viewport-1 ${kAssertion}`);
+ assert_false(below_viewport_2_loaded, `below-viewport-2 ${kAssertion}`);
+ assert_false(below_viewport_3_loaded, `below-viewport-3 ${kAssertion}`);
+ resolve();
+ });
+ });
+ }, "Images are lazyloaded");
+
+ // For `referrerPolicy` attribute mutations.
+ promise_test(t => {
+ return new Promise((resolve, reject) => {
+ const below_viewport_1 = document.querySelector('img#below-viewport-1');
+ below_viewport_1.onload = reject;
+ below_viewport_1.onerror = reject;
+ t.step_timeout(resolve, 1000);
+
+ below_viewport_1.referrerPolicy = 'no-referrer';
+ });
+ }, "Image referrerPolicy mutation does not cause deferred loading=lazy " +
+ "images to be fetched");
+
+ // For `crossOrigin` attribute mutations.
+ promise_test(t => {
+ return new Promise((resolve, reject) => {
+ const below_viewport_2 = document.querySelector('img#below-viewport-2');
+ below_viewport_2.onload = reject;
+ below_viewport_2.onerror = reject;
+ t.step_timeout(resolve, 1000);
+
+ below_viewport_2.crossOrigin = 'anonymous';
+ });
+ }, "Image crossOrigin mutation does not cause deferred loading=lazy " +
+ "images to be fetched");
+
+ // For `src` attribute mutations.
+ promise_test(t => {
+ return new Promise((resolve, reject) => {
+ const below_viewport_3 = document.querySelector('img#below-viewport-3');
+ below_viewport_3.onload = reject;
+ below_viewport_3.onerror = reject;
+ t.step_timeout(resolve, 1000);
+
+ below_viewport_3.src = "resources/image.png?relevant-mutations-change";
+ });
+ }, "Image src mutation does not cause deferred loading=lazy " +
+ "images to be fetched");
+</script>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <img id="below-viewport-1" src="resources/image.png?relevant-mutations-1" loading="lazy"
+ onload="below_viewport_1_loaded = true"
+ onerror="below_viewport_1_loaded = true">
+
+ <img id="below-viewport-2" src="resources/image.png?relevant-mutations-2" loading="lazy"
+ onload="below_viewport_2_loaded = true"
+ onerror="below_viewport_2_loaded = true">
+
+ <img id="below-viewport-3" src="resources/image.png?relevant-mutations-3" loading="lazy"
+ onload="below_viewport_3_loaded = true"
+ onerror="below_viewport_3_loaded = true">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio-ref.html
new file mode 100644
index 0000000000..6de01a9b3b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio-ref.html
@@ -0,0 +1,2 @@
+<!doctype HTML>
+<span style="display: inline-block; width: 100px; height: 50px; border: 1px solid black">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio.html
new file mode 100644
index 0000000000..662ada6909
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel="match" href="image-loading-lazy-slow-aspect-ratio-ref.html">
+ <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+ <script src="/common/reftest-wait.js"></script>
+ <img id=target loading="lazy"
+ width="200" height="100" style="width: 100px; height: auto; border: 1px solid black">
+<script>
+ let loaded = false;
+ target.onload = () => {
+ if (loaded) return;
+ loaded = true;
+ target.src = "";
+ requestAnimationFrame(() => requestAnimationFrame(() => {
+ target.src = "resources/image.png?slow-aspect-ratio&pipe=trickle(d2)";
+ takeScreenshot();
+ }));
+ };
+ target.src = "resources/image.png?slow-aspect-ratio";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-ref.html
new file mode 100644
index 0000000000..20fbb9b50e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<span style="display: inline-block; width: 330px; height: 254px; border: 1px solid black"></span>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow.html
new file mode 100644
index 0000000000..fac2c2e8f7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel="match" href="image-loading-lazy-slow-ref.html">
+ <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+
+ <script src="/common/reftest-wait.js"></script>
+ <img id=target loading="lazy"
+ width="330" height="254" style="border: 1px solid black">
+<script>
+ let loaded = false;
+ target.onload = () => {
+ if (loaded) return;
+ loaded = true;
+ target.src = "";
+ requestAnimationFrame(() => requestAnimationFrame(() => {
+ target.src = "resources/image.png?loading-lazy-slow&pipe=trickle(d2)";
+ takeScreenshot();
+ }));
+ };
+ target.src = "resources/image.png?loading-lazy-slow";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-srcset.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-srcset.html
new file mode 100644
index 0000000000..953d4af4ef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-srcset.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<head>
+<title>loading='lazy' image with srcset</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#update-the-image-data">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#will-lazy-load-image-steps">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<div style="height:1000vh;"></div>
+<img srcset="resources/image.png?loading-lazy-srcset" loading="lazy">
+<img loading="lazy" srcset="resources/image.png?loading-lazy-srcset">
+<script>
+promise_test(async t => {
+ let loaded_images = 0;
+ const imgs = document.querySelectorAll("img");
+ imgs.forEach(img => {
+ img.addEventListener("load", () => { loaded_images++; }, { once: true });
+ });
+
+ await new Promise(resolve => window.addEventListener("load", resolve));
+
+ assert_equals(loaded_images, 0,
+ "lazy-load images with srcset shouldn't be loaded yet");
+
+ const promises = [
+ new Promise(resolve => imgs[0].addEventListener("load", resolve)),
+ new Promise(resolve => imgs[1].addEventListener("load", resolve)),
+ ];
+
+ imgs[1].scrollIntoView();
+ await Promise.all(promises);
+
+ imgs.forEach(img => {
+ assert_true(img.complete,
+ "Now the lazy-load image with srcset should be loaded");
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-subframe-detached-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-subframe-detached-crash.html
new file mode 100644
index 0000000000..86a290d50d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-subframe-detached-crash.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<html class="test-wait">
+<title>Crash when detaching a frame during a lazy-load operation</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=1619858">
+<iframe srcdoc=""></iframe>
+<script>
+onload = function() {
+ let frame = document.querySelector("iframe");
+ frame.contentDocument.body.innerHTML = `
+ <div style="height: 300vh"></div>
+ <img loading="lazy" src="/images/blue96x96.png" width=96 height=96>
+ `;
+ let img = frame.contentDocument.querySelector("img");
+ new IntersectionObserver(() => {
+ frame.remove();
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ document.documentElement.className = "";
+ });
+ });
+ }).observe(img);
+ frame.contentWindow.scrollTo(0, img.getBoundingClientRect().top);
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-to-eager.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-to-eager.html
new file mode 100644
index 0000000000..6246063981
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-to-eager.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<head>
+ <title>Below-viewport images with loading='lazy' load when set to
+ loading='eager' or the `loading` attribute is removed</title>
+ <link rel="author" title="Dom Farolino" href="mailto:domfarolino@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t = async_test("Below-viewport images with loading='lazy' load when " +
+ "set to loading='eager' or the `loading` attribute is " +
+ "removed");
+
+ const img_1_onload = t.unreached_func("#img_1 should not load before the " +
+ "window load event");
+ const img_2_onload = t.unreached_func("#img_2 should not load before the " +
+ "window load event");
+
+ window.addEventListener("load", t.step_func(() => {
+ const img_1 = document.querySelector('#img_1');
+ const img_2 = document.querySelector('#img_2');
+
+ const img_1_promise = new Promise((resolve, reject) => {
+ img_1.onerror = reject;
+ img_1.onload = resolve;
+ });
+
+ const img_2_promise = new Promise((resolve, reject) => {
+ img_2.onerror = reject;
+ img_2.onload = resolve;
+ });
+
+ Promise.all([img_1_promise, img_2_promise])
+ .then(t.step_func_done())
+ .catch(t.unreached_func("The images should load successfully"));
+
+ // Kick off the requests.
+ img_1.loading = 'eager';
+ img_2.removeAttribute('loading'); // unset the attribute, putting it in
+ // the default (eager) state.
+ }));
+
+</script>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <img id="img_1"
+ src="resources/image.png?lazy-to-eager-1"
+ loading="lazy" onload="img_1_onload();">
+ <img id="img_2"
+ src="resources/image.png?lazy-to-eager-2"
+ loading="lazy" onload="img_2_onload();">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-use-list-of-available-images.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-use-list-of-available-images.html
new file mode 100644
index 0000000000..f2a8a3542f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-use-list-of-available-images.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<html>
+<title>Lazyload images can load immediately from the list of available images</title>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- A `loading=lazy` image will be placed below this div so that it is below
+ the viewport -->
+<div style="height: 1000vh;"></div>
+<div id="below-viewport-img-container"></div>
+
+<script>
+const image_path = location.origin + '/html/semantics/embedded-content/the-img-element/resources/image.png?image-loading-lazy-use-list-of-available-images-' + Math.random();
+
+promise_test(async t => {
+ const eager_image_promise = new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = resolve;
+ img.onerror = e => { reject(new Error("The img should not fail to load")) };
+ img.src = image_path;
+ });
+
+ await eager_image_promise;
+
+ // At this point, the image fetched eagerly above exists in the "list of
+ // available images". As per the spec's #updating-the-image-data algorithm [1]
+ // step 6, the "list of avalable images" is consulted before we take any
+ // lazyload-specific action. This means that lazyload images can load eagerly
+ // if they target a resource in the list of available images, which the image
+ // below is doing.
+ //
+ // Note that if https://github.com/whatwg/html/issues/7005 resolves in favor
+ // of allowing in-flight image requests to be placed in the list of available
+ // images, as opposed to just complete images, this would allow lazyload
+ // images (in addition to non-lazyload ones) to coalesce with these in-flight
+ // entries in the list of available images too. In that case we'd need to test
+ // for this here.
+ // [1]: https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data
+ const lazyload_image_promise = new Promise((resolve, reject) => {
+ const img = new Image();
+ img.loading = 'lazy';
+ img.onload = resolve;
+ img.onerror = e => { reject("The img should not fail to load") };
+
+ document.querySelector('#below-viewport-img-container').append(img);
+ img.src = image_path;
+ });
+ const timeout_promise = new Promise((resolve, reject) => {
+ t.step_timeout(() => {
+ reject(new Error("The `loading=lazy` image should load immediately from " +
+ "the list of available images, beating this timeout " +
+ "promise."));
+ }, 1000);
+ });
+
+ // The `lazyload_image_promise` should resolve first because lazyload images
+ // are able to eagerly use resources in the "list of available images".
+ await Promise.race([lazyload_image_promise, timeout_promise]);
+}, 'Lazyload images can load immediately from the list of available images');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-zero-intersection-area.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-zero-intersection-area.html
new file mode 100644
index 0000000000..9962ce7837
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-zero-intersection-area.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Image with zero intersection area is lazy-loaded</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-intersection-observer">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1785186">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div style="height: 0; overflow: hidden;">
+ <img style="display: block" id=target loading="lazy" width="100" height="100">
+</div>
+<script>
+ async_test(function(t) {
+ target.addEventListener("load", t.step_func_done(function() {}));
+ target.src = "resources/image.png?zero-intersection-area";
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy.html
new file mode 100644
index 0000000000..88f6549d96
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='lazy' load only when in the viewport</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="author" title="Scott Little" href="mailto:sclittle@chromium.org">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const in_viewport_test =
+ async_test("In-viewport loading=lazy images load immediately but do not " +
+ "block the window load event");
+ const below_viewport_test =
+ async_test("Below-viewport loading=lazy images only load when in the " +
+ "viewport and do not block the window load event");
+ const below_viewport_data_url_test =
+ async_test("Below-viewport data:url images only load when in the " +
+ "viewport and do not block the window load event");
+ const below_viewport_blob_url_test =
+ async_test("Below-viewport blob URL images only load when in the " +
+ "viewport and do not block the window load event");
+
+ document.addEventListener('DOMContentLoaded', e => {
+ const img = document.querySelector('#below_viewport_blob_url');
+
+ // Blob URL helper.
+ // Source: https://bl.ocks.org/nolanlawson/0eac306e4dac2114c752.
+ function fixBinary(bin) {
+ const length = bin.length;
+ const buf = new ArrayBuffer(length);
+ const arr = new Uint8Array(buf);
+ for (var i = 0; i < length; i++) {
+ arr[i] = bin.charCodeAt(i);
+ }
+
+ return buf;
+ }
+
+ const base64 =
+ "R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA";
+ const binary = fixBinary(atob(base64));
+ const blob = new Blob([binary], {type: 'image/png'});
+ const url = URL.createObjectURL(blob);
+ img.src = url;
+ }) // DOMContentLoaded.
+
+ let has_window_load_fired = false;
+ let has_in_viewport_loaded = false;
+
+ window.onload = e => {
+ has_window_load_fired = true;
+ }
+
+ // Helper assertion messages for the below tests.
+ const kScrollAssertion = "images only load when scrolled into view";
+ const kWindowLoadAssertion = "images do not block the load event";
+
+ const in_viewport_img_onload = in_viewport_test.step_func_done(() => {
+ has_in_viewport_loaded = true;
+ document.querySelector('#bottom').scrollIntoView();
+ assert_true(has_window_load_fired,
+ "In-viewport loading=lazy images do not block the window " +
+ "load event");
+ });
+
+ const below_viewport_img_onload = below_viewport_test.step_func_done(() => {
+ assert_true(has_in_viewport_loaded,
+ "Below-viewport loading=lazy images only load once loaded " +
+ "into the viewport");
+ assert_true(has_window_load_fired,
+ "Below-viewport loading=lazy images should not block the " +
+ "window load event");
+ });
+
+ const below_viewport_data_url_img_onload = below_viewport_data_url_test.step_func_done(() => {
+ assert_true(has_in_viewport_loaded,
+ "Below-viewport loading=lazy data: url images " +
+ kScrollAssertion);
+ assert_true(has_window_load_fired,
+ "Below-viewport loading=lazy data: url images " +
+ kWindowLoadAssertion);
+ });
+
+ const below_viewport_blob_url_img_onload = below_viewport_blob_url_test.step_func_done(() => {
+ assert_true(has_in_viewport_loaded,
+ "Below-viewport loading=lazy blob url images " +
+ kScrollAssertion);
+ assert_true(has_window_load_fired,
+ "Below-viewport loading=lazy blob url images " +
+ kWindowLoadAssertion);
+ });
+</script>
+
+<body>
+ <!-- |in_viewport| takes 2 seconds to load, so that in browsers that don't
+ support lazy loading, |below_viewport| finishes before |in_viewport|, and
+ the test will dependably fail without relying on a timeout. -->
+ <img id="in_viewport" loading="lazy" src="resources/image.png?image-loading-lazy-first&pipe=trickle(d2)"
+ onload="in_viewport_img_onload()">
+ <div style="height:1000vh;"></div>
+ <img id="below_viewport" loading="lazy" src="resources/image.png?image-loading-lazy-second"
+ onload="below_viewport_img_onload()">
+ <img id="below_viewport_data_url" loading="lazy"
+ src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA"
+ onload="below_viewport_data_url_img_onload()">
+ <!-- This image has its `src` set to a blob URL dynamically above -->
+ <img id="below_viewport_blob_url" loading="lazy"
+ onload="below_viewport_blob_url_img_onload()">
+ <div id="bottom"></div>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip-ref.html
new file mode 100644
index 0000000000..f841dba31b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<style>
+* {
+ margin: 0;
+}
+</style>
+<html class="reftest-wait" style="overflow: hidden">
+ <head>
+ <title>Images with loading='lazy' load under subpixel-offset clips</title>
+ <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/#lazy-loading-attributes">
+ </head>
+ <div style="height: 44.5px"></div>
+ <div style="position: relative; font-size: 0; background: lightblue">
+ <img id=target loading="lazy" data-sizes="auto">
+ </div>
+</html>
+<script src="/common/reftest-wait.js"></script>
+<script>
+ target.onload = takeScreenshot;
+ target.src = "resources/image.png";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip.html
new file mode 100644
index 0000000000..594d9bebe4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<style>
+* {
+ margin: 0;
+}
+</style>
+<html class="reftest-wait" style="overflow: hidden">
+ <head>
+ <title>Images with loading='lazy' load under subpixel-offset clips</title>
+ <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/#lazy-loading-attributes">
+ <link rel="match" href="image-loading-subpixel-clip-ref.html">
+ </head>
+ <div style="height: 44.5px"></div>
+ <div style="overflow: hidden">
+ <div style="position: relative; font-size: 0; background: lightblue">
+ <img id=target loading="lazy" data-sizes="auto">
+ </div>
+ </div>
+</html>
+<script src="/common/reftest-wait.js"></script>
+<script>
+ target.onload = takeScreenshot;
+ target.src = "resources/image.png";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print-ref.html
new file mode 100644
index 0000000000..160f9f50a1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print-ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<style>
+ body { margin: 0 }
+ img {
+ margin: 16px; /* to account for iframe + body margin in the test */
+ }
+</style>
+<img src="/images/green-100x50.png" alt="FAIL">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print.html
new file mode 100644
index 0000000000..4ad922ba00
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1710822">
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="match" href="image-srcdoc-relative-uri-print-ref.html">
+<iframe frameborder=0 srcdoc="<img src=/images/green-100x50.png>">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image.png b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image.png
new file mode 100644
index 0000000000..d26878c9f2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-created-in-active-document-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-created-in-active-document-crash.html
new file mode 100644
index 0000000000..852375bff3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-created-in-active-document-crash.html
@@ -0,0 +1,6 @@
+<iframe id="i"></iframe>
+<script>
+var doc = i.contentDocument;
+i.remove();
+doc.createElement("img");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-picture-ancestor.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-picture-ancestor.html
new file mode 100644
index 0000000000..3518cab54d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-picture-ancestor.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>img should only look at a parent picture element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<picture>
+ <source media="not all" srcset="data:,a">
+ <source media="all" srcset="data:,b">
+ <img src="data:,c">
+ <picture>
+ <source media="not all" srcset="data:,e">
+ <source media="all" srcset="data:,f">
+ <img src="data:,g">
+ </picture>
+</picture>
+<script>
+const picture1 = document.querySelector("picture");
+const picture2 = document.querySelector("picture > picture");
+const img1 = document.querySelector("picture > img");
+const img2 = document.querySelector("picture > picture > img");
+
+const div = document.createElement("div");
+
+const imgInsideDiv = document.createElement("img");
+imgInsideDiv.src = "data:,d";
+div.append(imgInsideDiv);
+
+test(function() {
+ assert_equals(img1.currentSrc, "data:,b");
+}, "currentSrc of img in normally parented picture is correct");
+
+test(function() {
+ assert_equals(img2.currentSrc, "data:,f");
+}, "currentSrc of img in nested picture element is correct");
+
+async_test(function(t) {
+ picture1.append(div);
+ queueMicrotask(t.step_func(function() {
+ assert_equals(imgInsideDiv.currentSrc, "data:,d");
+ t.done();
+ }));
+}, "currentSrc of img with picture ancestor but non-picture parent is correct");
+
+async_test(function(t) {
+ picture2.remove();
+ queueMicrotask(t.step_func(function() {
+ assert_equals(img2.currentSrc, "data:,f");
+ t.done();
+ }));
+}, "currentSrc of img in nested picture element remains correct when the inner picture is removed from the document");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-src-in-synthetic-document.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-src-in-synthetic-document.html
new file mode 100644
index 0000000000..24c5567fb8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-src-in-synthetic-document.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/9855">
+<link rel=help href="https://html.spec.whatwg.org/#reflecting-content-attributes-in-idl-attributes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+test(() => {
+ const doc = document.implementation.createHTMLDocument('');
+ const img = doc.createElement('img');
+ img.setAttribute('src', '/test');
+ doc.body.appendChild(img);
+ assert_equals(img.src, '/test');
+}, 'HTMLImageElement.src should return the string from the attribute in about:blank documents.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size-ref.html
new file mode 100644
index 0000000000..56176c4b71
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size-ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<style>
+img {
+ width: 200px;
+ height: 100px;
+}
+</style>
+<img src="image.png">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size.html
new file mode 100644
index 0000000000..bdeae0ac2e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Ensure images with containment and size are rendered properly</title>
+<meta charset="utf-8">
+<link rel="match" href="img-with-containment-and-size-ref.html">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+<style>
+img {
+ contain: paint;
+ width: 200px;
+ height: 100px;
+ will-change: transform;
+}
+</style>
+<script>
+onload = () => {
+ var i = new Image();
+ i.onload = function() {
+ document.body.appendChild(i);
+ document.documentElement.classList.remove("reftest-wait");
+ };
+ i.src = "image.png";
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img.complete.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img.complete.html
new file mode 100644
index 0000000000..d8d5a84eb7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img.complete.html
@@ -0,0 +1,200 @@
+<!DOCTYPE HTML>
+<title>DOM img complete Test</title>
+<meta charset=UTF-8>
+<link rel="author" title="Anselm Hannemann" href="http://anselm-hannemann.com/" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<img id="imgTestTag">
+<img src="" id="imgTestTag2">
+<img id="imgTestTag3" style="width: 80px; height:auto;">
+<img id="imgTestTag4">
+<img id="imgTestTag5">
+<div id="image-container"></div>
+
+<script>
+ var imageInstance = document.createElement('img');
+ imageInstance.style.display = 'none';
+
+ document.body.appendChild(imageInstance);
+</script>
+
+<div id="log"></div>
+<script>
+ test(function() {
+ assert_true(document.getElementById("imgTestTag").complete);
+ }, "img src and srcset omitted");
+
+ test(function() {
+ var img = document.createElement("img");
+ assert_true(img.complete);
+ }, "img src and srcset omitted on newly-created image");
+
+ test(function() {
+ var cont = document.getElementById("image-container");
+ this.add_cleanup(() => { cont.innerHTML = "" });
+ var img = document.createElement("img");
+ cont.appendChild(img);
+ assert_true(img.complete);
+ }, "img src and srcset omitted on newly-created-and-inserted image");
+
+ test(function() {
+ var cont = document.getElementById("image-container");
+ this.add_cleanup(() => { cont.innerHTML = "" });
+ cont.innerHTML = "<img>";
+ assert_true(cont.querySelector("img").complete);
+ }, "img src and srcset omitted on newly-created-via-innerHTML image");
+
+ test(function() {
+ assert_true(document.getElementById("imgTestTag2").complete);
+ }, "img src empty and srcset omitted");
+
+ test(function() {
+ var img = document.createElement("img");
+ img.setAttribute("src", "");
+ assert_true(img.complete);
+ }, "img src empty and srcset omitted on newly-created image");
+
+ test(function() {
+ var cont = document.getElementById("image-container");
+ this.add_cleanup(() => { cont.innerHTML = "" });
+ var img = document.createElement("img");
+ img.setAttribute("src", "");
+ cont.appendChild(img);
+ assert_true(img.complete);
+ }, "img src empty and srcset omitted on newly-created-and-inserted image");
+
+ test(function() {
+ var cont = document.getElementById("image-container");
+ this.add_cleanup(() => { cont.innerHTML = "" });
+ cont.innerHTML = "<img src=''>";
+ assert_true(cont.querySelector("img").complete);
+ }, "img src empty and srcset omitted on newly-created-via-innerHTML image");
+
+ test(function() {
+ var img = document.createElement("img");
+ img.src = location.href;
+ assert_false(img.complete, "Should have a load going");
+ img.removeAttribute("src");
+ assert_true(img.complete);
+ }, "img src and srcset omitted on image after it started a load");
+
+ // test if set to true after img is completely available
+ async_test(t => {
+ var loaded = false;
+ const img = document.getElementById("imgTestTag3");
+ img.onload = t.step_func_done(function(){
+ assert_false(loaded);
+ loaded = true;
+ assert_true(img.complete);
+ var currentSrc = img.currentSrc;
+ var expectedUrl = new URL("3.jpg", window.location);
+ assert_equals(new URL(currentSrc).pathname, expectedUrl.pathname);
+ }, "Only one onload, despite setting the src twice");
+
+ img.src = 'test' + Math.random();
+ //test if img.complete is set to false if src is changed
+ assert_false(img.complete, "src changed, should be set to false")
+ //change src again, should make only one request as per 'await stable state'
+ img.src = '3.jpg?nocache=' + Math.random();
+ }, "async src complete test");
+
+ async_test(t => {
+ var loaded = false;
+ const img = document.getElementById("imgTestTag5")
+ img.onload = t.step_func_done(function(){
+ assert_false(loaded);
+ loaded = true;
+ assert_true(img.complete);
+ }, "Only one onload, despite setting the srcset twice");
+ //Test if src, srcset is omitted
+ assert_true(img.complete)
+ img.srcset = "/images/green-256x256.png 1x";
+ //test if img.complete is set to false if srcset is present
+ assert_false(img.complete, "srcset present, should be set to false");
+ //change src again, should make only one request as per 'await stable state'
+ img.srcset="/images/green-256x256.png 1.6x"
+ }, "async srcset complete test");
+
+ // https://html.spec.whatwg.org/multipage/multipage/embedded-content-1.html#update-the-image-data
+ // says to "await a stable state" before fetching so we use a separate <script>
+ imageInstance.src = 'image-1.jpg?pipe=trickle(d1)&nocache=' + Math.random(); // make sure the image isn't in cache
+</script>
+<script>
+ // test: The final task that is queued by the networking task source once the resource has been fetched has been queued, but has not yet been run, and the img element is not in the broken state
+ test(function() {
+ assert_false(imageInstance.complete, "imageInstance.complete should be false");
+ var startTime = Date.now();
+ while (true) {
+ if (Date.now() - startTime > 2000) {
+ assert_false(imageInstance.complete, "imageInstance.complete should remain false");
+ break;
+ }
+ if (imageInstance.complete === true) {
+ assert_unreached(".complete should not change within a task");
+ }
+ }
+ },
+ 'IDL attribute complete cannot "randomly" change during a task');
+
+ // test if broken img does not pass
+ async_test(t => {
+ const img = document.getElementById("imgTestTag4");
+
+ img.src = 'brokenimg.jpg';
+
+ //test if img.complete is set to false if src is changed
+ assert_false(img.complete, "src changed to broken img, should be set to false");
+
+ img.onload = img.onerror = t.step_func_done(function(event){
+ assert_equals(event.type, "error");
+ assert_true(img.complete);
+ });
+ }, "async src broken test");
+
+ async_test(t => {
+ var img = document.createElement("img");
+ assert_true(img.complete);
+ img.src = `3.jpg?nocache=${Math.random()}`;
+ assert_false(img.complete);
+ img.onload = t.step_func_done(() => {
+ assert_true(img.complete);
+ img.removeAttribute("src");
+ assert_true(img.complete, "Should be complete, since we removed the src");
+ });
+ }, "async src removal test");
+
+ async_test(t => {
+ var img = document.createElement("img");
+ assert_true(img.complete);
+ img.srcset = `3.jpg?nocache=${Math.random()} 1x`;
+ assert_false(img.complete);
+ img.onload = t.step_func_done(() => {
+ assert_true(img.complete);
+ img.removeAttribute("srcset");
+ assert_true(img.complete, "Should be complete, since we removed the srcset");
+ });
+ }, "async srcset removal test");
+
+ async_test(t => {
+ var preload = document.createElement("img");
+ var url = `3.jpg?nocache=${Math.random()}`;
+ preload.src = url;
+ preload.onload = t.step_func_done(function() {
+ var img = document.createElement("img");
+ assert_true(img.complete);
+ img.src = url;
+ assert_true(img.complete, "Should be complete because we should hit the available image cache");
+ });
+ }, "async src available image lookup test");
+
+ async_test(t => {
+ var img = document.createElement("img");
+ img.src = `3.jpg?nocache=${Math.random()}`;
+ img.onload = t.step_func_done(function() {
+ assert_true(img.complete);
+ img.src = `3.jpg?nocache=${Math.random()}`;
+ assert_false(img.complete, "Should not be complete because we have started a new load");
+ });
+ }, "async pending request test");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invalid-src.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invalid-src.html
new file mode 100644
index 0000000000..37ea8ce754
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invalid-src.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Loading a non-parsing URL as an image should silently fail; triggering appropriate events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<img id=brokenurl />
+<img id=emptysrc />
+<script>
+async_test(function(t) {
+ var img = document.getElementById("brokenurl");
+ img.src = "http://[";
+
+ // The errors should be queued in the event loop, so they should only trigger
+ // after this block of code finishes, not during the img.src setter itself
+ img.addEventListener('error', t.step_func(function() {
+ t.step_timeout(t.step_func_done(), 0);
+ }));
+}, 'src="http://["');
+
+async_test(function(t) {
+ var img = document.getElementById("emptysrc");
+ img.src = "";
+
+ // Setting src to empty string triggers only error event.
+ // The errors should be queued in the event loop, so they should only trigger
+ // after this block of code finishes, not during the img.src setter itself
+ img.addEventListener('error', t.step_func(function() {
+ t.step_timeout(t.step_func_done(), 0);
+ }));
+}, 'src=""');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invisible-image.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invisible-image.html
new file mode 100644
index 0000000000..35ebbaa11a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invisible-image.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<head>
+ <title>Test that below-viewport invisible images that are not marked
+ loading=lazy still load, and block the window load event</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="common.js"></script>
+</head>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <img id="visibility_hidden" style="visibility:hidden;" src='resources/image.png?1'>
+ <img id="visibility_hidden_explicit_eager" style="visibility:hidden;" src='resources/image.png?2'
+ loading="eager">
+
+ <img id="display_none" style="display:none;" src='resources/image.png?3'>
+ <img id="display_none_explicit_eager" style="display:none;" src='resources/image.png?4'
+ loading="eager">
+
+ <img id="attribute_hidden" hidden src='resources/image.png?5'>
+ <img id="attribute_hidden_explicit_eager" hidden src='resources/image.png?6'
+ loading="eager">
+
+ <img id="js_display_none" src='resources/image.png?7'>
+ <img id="js_display_none_explicit_eager" src='resources/image.png?8'
+ loading="eager">
+ <script>
+ document.getElementById("js_display_none").style = 'display:none;';
+
+ const visibility_hidden_element = document.getElementById("visibility_hidden");
+ const visibility_hidden_element_explicit_eager =
+ document.getElementById("visibility_hidden_explicit_eager");
+
+ const display_none_element = document.getElementById("display_none");
+ const display_none_element_explicit_eager =
+ document.getElementById("display_none_explicit_eager");
+
+ const attribute_hidden_element = document.getElementById("attribute_hidden");
+ const attribute_hidden_element_explicit_eager =
+ document.getElementById("attribute_hidden_explicit_eager");
+
+ const js_display_none_element = document.getElementById("js_display_none");
+ const js_display_none_element_explicit_eager =
+ document.getElementById("js_display_none_explicit_eager");
+
+ let have_images_loaded = false;
+
+ async_test(t => {
+ let image_fully_loaded_promise = (element) => {
+ return new Promise(resolve => {
+ element.addEventListener("load", t.step_func(resolve));
+ });
+ }
+
+ Promise.all([
+ image_fully_loaded_promise(visibility_hidden_element),
+ image_fully_loaded_promise(visibility_hidden_element_explicit_eager),
+ image_fully_loaded_promise(display_none_element),
+ image_fully_loaded_promise(display_none_element_explicit_eager),
+ image_fully_loaded_promise(attribute_hidden_element),
+ image_fully_loaded_promise(attribute_hidden_element_explicit_eager),
+ image_fully_loaded_promise(js_display_none_element),
+ image_fully_loaded_promise(js_display_none_element_explicit_eager)
+ ]).then(t.step_func(() => {
+ have_images_loaded = true;
+ })).catch(t.unreached_func("All images should load correctly"));
+
+ window.addEventListener("load", t.step_func_done(() => {
+ assert_true(have_images_loaded,
+ "The images should block the window load event.");
+ }));
+
+ }, "Test that below-viewport invisible images that are not marked " +
+ "loading=lazy still load, and block the window load event");
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-after.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-after.html
new file mode 100644
index 0000000000..bb4c5991c9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-after.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html>
+ <head>
+ <title>img ismap attribute coordinate origin</title>
+ <style>
+ #bg { background-color: lightgray; position: relative; }
+ #target { position: absolute; width: 48px; height: 48px; border: 2px dashed green; pointer-events: none; }
+ .after { top: 246px; left: 246px; }
+ img { margin: 50px; border: 50px solid white; padding: 50px; }
+ </style>
+ </head>
+ <body>
+ <div id="bg">
+ <div id="target" class="after"></div>
+ <a href="/somewhere/">
+ <img src="/images/blue96x96.png" ismap>
+ </a>
+ </div>
+ <h1>Click inside the dashed rectangle</h1>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-before.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-before.html
new file mode 100644
index 0000000000..8349b62783
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-before.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html>
+ <head>
+ <title>img ismap attribute coordinate origin</title>
+ <style>
+ #bg { background-color: lightgray; position: relative; }
+ #target { position: absolute; width: 96px; height: 96px; border: 2px dashed green; pointer-events: none; }
+ .before { top: 50px; left: 50px; }
+ img { margin: 50px; border: 50px solid white; padding: 50px; }
+ </style>
+ </head>
+ <body>
+ <div id="bg">
+ <div id="target" class="before"></div>
+ <a href="/somewhere/">
+ <img src="/images/blue96x96.png" ismap>
+ </a>
+ </div>
+ <h1>Click inside the dashed rectangle</h1>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-inside.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-inside.html
new file mode 100644
index 0000000000..fdecee9ace
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-inside.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html>
+ <head>
+ <title>img ismap attribute coordinate origin</title>
+ <style>
+ #bg { background-color: lightgray; position: relative; }
+ #target { position: absolute; width: 96px; height: 96px; border: 2px dashed green; pointer-events: none; }
+ .in { top: 148px; left: 148px; }
+ img { margin: 50px; border: 50px solid white; padding: 50px; }
+ </style>
+ </head>
+ <body>
+ <div id="bg">
+ <div id="target" class="in"></div>
+ <a href="/common/blank.html">
+ <img src="/images/blue96x96.png" ismap>
+ </a>
+ </div>
+ <h1>Click inside the dashed rectangle</h1>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-manual.html
new file mode 100644
index 0000000000..4d77e677e4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-manual.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+ <head>
+ <title>img ismap attribute coordinate origin</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style>
+ iframe { width: 500px; height: 500px; }
+ </style>
+ </head>
+ <body>
+ <iframe></iframe>
+ <div id="log"></div>
+ <script type="text/javascript">
+ tests = [
+ {
+ file: "img-ismap-coordinates-iframe-inside.html",
+ },
+ {
+ test: async_test("Coordinates within the content box of an image map have origin of the context box"),
+ resultMinXY: 0,
+ resultMaxXY: 96,
+ },
+ {
+ file: "img-ismap-coordinates-iframe-before.html",
+ },
+ {
+ test: async_test("Coordinates within the margin/padding (top-left) of the image map are clamped to zero"),
+ resultMinXY: 0,
+ resultMaxXY: 0,
+ },
+ {
+ file: "img-ismap-coordinates-iframe-after.html",
+ },
+ {
+ test: async_test("Coordinates within the margin/padding (bottom-right) of the image map have origin in the content box"),
+ resultMinXY: 97,
+ resultMaxXY: 146,
+ }
+ ];
+ testIndex = 0;
+
+ var iframe = document.querySelector('iframe');
+ iframe.onload = function testInit() {
+ if (testIndex % 2 == 0) {
+ testIndex++;
+ return;
+ }
+ // User clicked on a results...
+ var url = iframe.contentWindow.location.toString();
+ var test = tests[testIndex].test;
+ var minXY = tests[testIndex].resultMinXY;
+ var maxXY = tests[testIndex].resultMaxXY;
+ testIndex++;
+ if (testIndex < tests.length)
+ iframe.src = tests[testIndex].file; // Advance the test...
+ // Validate the last test's results...
+ test.step(function () {
+ var i = url.indexOf("?");
+ assert_not_equals(i, -1);
+ var coordsStr = url.substr(i+1);
+ var i = coordsStr.indexOf(',');
+ assert_not_equals(i, -1);
+ var x = parseFloat(coordsStr.substring(0, i));
+ var y = parseFloat(coordsStr.substring(i+1));
+ assert_greater_than_equal(x, minXY);
+ assert_less_than_equal(x, maxXY);
+ assert_greater_than_equal(y, minXY);
+ assert_less_than_equal(y, maxXY);
+ test.done();
+ });
+ if (testIndex >= tests.length)
+ iframe.style.display = "none";
+ }
+ iframe.src = tests[0].file;
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-does-not-coalesce-in-flight-requests.sub.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-does-not-coalesce-in-flight-requests.sub.tentative.html
new file mode 100644
index 0000000000..52e91bc087
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-does-not-coalesce-in-flight-requests.sub.tentative.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<html>
+<title>List of available images does not coalesce in-flight requests</title>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-list-of-available-images">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+const uuid = "{{uuid()}}";
+const path = location.origin + '/html/semantics/embedded-content/the-img-element/resources/image-and-stash.py';
+
+promise_test(async t => {
+ let first_image_promise = new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = resolve;
+ img.onerror = e => { reject(new Error("The img should not fail to load")) };
+ img.src = path + `?increment=${uuid}&pipe=trickle(d1)`;
+ });
+
+ // As of right now, the spec's #updating-the-image-data step 6 [1] does not
+ // place images into the "list of available images" until they are completely
+ // downloaded and the `load` event is fired. The spec also explicitly states
+ // that coalescing in-flight image requests is not what the list of available
+ // images is for. This test asserts this behavior, though since no browsers
+ // follow this behavior (they all allow coalescing in-flight requests for the
+ // same image resource) we've started discussion about this in
+ // https://github.com/whatwg/html/issues/7005. If that issue resolves in
+ // letting in-flight requests into the list of available images, then we
+ // should changes the expectations of this test.
+ //
+ // [1]: https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data
+ let second_image_promise = new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = resolve;
+ img.onerror = e => { reject("The img should not fail to load") };
+ img.src = path + `?increment=${uuid}&pipe=trickle(d1)`;
+ });
+
+ await Promise.all([first_image_promise, second_image_promise]);
+ const response = await fetch(path + `?read=${uuid}`);
+ const request_count = await response.text();
+
+ assert_equals(request_count, "2", "The server should have seen exactly two " +
+ "requests, since the second image request " +
+ "above did not coalesce with the first " +
+ "in-flight one");
+}, 'list of available images does not coalesce in-flight requests');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-matching.https.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-matching.https.html
new file mode 100644
index 0000000000..4843d21915
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-matching.https.html
@@ -0,0 +1,68 @@
+<!doctype html>
+<html>
+<title>List of available images tuple-matching logic</title>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-list-of-available-images">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<script>
+const path = location.origin + '/html/semantics/embedded-content/the-img-element/';
+const image_url = path + 'image-1.jpg';
+
+function syncWait(ms) {
+ const start = Date.now();
+ while (Date.now() - start < ms);
+}
+
+let sawNoCorsRequest = false;
+
+navigator.serviceWorker.onmessage = ({data}) => {
+ if (data.url === image_url && data.mode === 'no-cors') {
+ sawNoCorsRequest = true;
+ }
+};
+
+promise_test(t => {
+ return service_worker_unregister_and_register(t, 'resources/sw.js', path)
+ .then(r => {
+ return wait_for_state(t, r.installing, 'activated');
+ });
+}, 'registering service worker');
+
+promise_test(async t => {
+ const img = new Image();
+
+ function load_img_promise() {
+ return new Promise((resolve, reject) => {
+ img.onload = resolve;
+ img.onerror = e => { reject("The img should not fail to load") };
+
+ img.src = image_url;
+ // If there is not a matching image in the list of available images, the
+ // actual fetch operation is queued as a microtask, so we will see a
+ // request with mode 'cors' due to setting the crossorigin attribute
+ // below.
+ syncWait(500);
+ img.crossOrigin = 'anonymous';
+ });
+ };
+
+ await load_img_promise();
+ assert_false(sawNoCorsRequest, "The image is not fetched with mode: no-cors");
+ await new Promise(resolve => {
+ img.onload = img.onerror = resolve;
+ img.src = '';
+ img.crossOrigin = null;
+ });
+ await load_img_promise();
+ assert_false(sawNoCorsRequest, "The image is not fetched with mode: no-cors");
+
+}, 'list of available images tuple-matching logic');
+
+promise_test(t => {
+ return service_worker_unregister(t, path);
+}, 'unregistering service worker');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/move-element-and-scroll.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/move-element-and-scroll.html
new file mode 100644
index 0000000000..3c95fae5bf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/move-element-and-scroll.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='lazy' load being moved to another document
+ and then scrolled to</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="common.js"></script>
+</head>
+
+<body>
+ <div id="tall_div" style="height:1000vh"></div>
+ <div id="below_viewport_div"></div>
+ <img id="below_viewport" src='resources/image.png?below_viewport' loading="lazy">
+
+ <script>
+ const tall_div = document.getElementById("tall_div");
+ const below_viewport_element = document.getElementById("below_viewport");
+ const below_viewport_div = document.getElementById("below_viewport_div");
+
+ async_test(function(t) {
+ below_viewport_element.onload =
+ t.unreached_func("The below viewport image should not load");
+ t.step_timeout(t.step_func_done(), 1000);
+ const iframe = document.createElement('iframe');
+ iframe.setAttribute("style", "display:none");
+ iframe.srcdoc = "<body></body>";
+ iframe.onload = () => {
+ const adopted_img = iframe.contentDocument.adoptNode(below_viewport_element);
+ iframe.contentDocument.body.appendChild(adopted_img);
+ below_viewport_div.scrollIntoView();
+ };
+ document.body.insertBefore(iframe, tall_div);
+ }, "Test that <img> below viewport is not loaded when moved to another " +
+ "document and then scrolled to");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/natural-size-orientation.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/natural-size-orientation.html
new file mode 100644
index 0000000000..662dc0804f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/natural-size-orientation.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>naturalWidth and naturalHeight on HTMLImageElement reflect orientation metadata</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+.ignore-orientation { image-orientation: none; }
+</style>
+<body>
+<script>
+async_test(function(t) {
+ let img = document.createElement("img");
+ img.src = "/images/green-100x50.png";
+ img.onload = t.step_func_done(function() {
+ assert_equals(img.naturalWidth, 100);
+ assert_equals(img.naturalHeight, 50);
+ img.remove();
+ });
+ document.body.append(img);
+}, "naturalWidth and naturalHeight return correct values for an image without orientation metadata");
+
+async_test(function(t) {
+ let img = document.createElement("img");
+ img.src = "/images/arrow-oriented-upright.jpg";
+ img.onload = t.step_func_done(function() {
+ assert_equals(img.naturalWidth, 144);
+ assert_equals(img.naturalHeight, 240);
+ img.remove();
+ });
+ document.body.append(img);
+}, "naturalWidth and naturalHeight return re-oriented values for an image with orientation metadata");
+
+async_test(function(t) {
+ let img = document.createElement("img");
+ img.src = "/images/arrow-oriented-upright.jpg";
+ img.className = "ignore-orientation";
+ img.onload = t.step_func_done(function() {
+ assert_equals(img.naturalWidth, 144);
+ assert_equals(img.naturalHeight, 240);
+ img.remove();
+ });
+ document.body.append(img);
+}, "naturalWidth and naturalHeight return re-oriented values for an image with orientation metadata even with image-orientation:none");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/non-active-document.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/non-active-document.html
new file mode 100644
index 0000000000..6072138cb3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/non-active-document.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>img in non-active document should not perform loads</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+
+<!-- Per load the image so that any loads in this test would be cached. -->
+<img src=/images/green-1x1.png>
+
+<!-- tests -->
+<template>
+<img>
+</template>
+
+<script>
+
+onload = function() {
+ async_test(function(t) {
+ var p = new DOMParser();
+ var d = p.parseFromString('<img>', 'text/html');
+ var i = d.querySelector('img');
+ i.onerror = t.unreached_func('got unexpected error event');
+ i.onload = t.unreached_func('got unexpected load event');
+ i.src = '/images/green-1x1.png';
+ // delay to ensure there is no load/error event fired.
+ t.step_timeout(t.step_func_done(), 0);
+ }, "DOMParser");
+
+ async_test(function(t) {
+ var d = document.implementation.createHTMLDocument('');
+ d.body.innerHTML = '<img>';
+ var i = d.querySelector('img');
+ i.onerror = this.unreached_func('got unexpected error event');
+ i.onload = this.unreached_func('got unexpected load event');
+ i.src = '/images/green-1x1.png';
+ // delay to ensure there is no load/error event fired.
+ t.step_timeout(t.step_func_done(), 0);
+ }, "createHTMLDocument");
+
+ async_test(function(t) {
+ var template = document.querySelector('template');
+ var i = template.content.querySelector('img');
+ i.onerror = this.unreached_func('got unexpected error event');
+ i.onload = this.unreached_func('got unexpected load event');
+ i.src = '/images/green-1x1.png';
+ // delay to ensure there is no load/error event fired.
+ t.step_timeout(t.step_func_done(), 0);
+ }, "<template>");
+};
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/nonexistent-image.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/nonexistent-image.html
new file mode 100644
index 0000000000..f58569ede0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/nonexistent-image.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Loading an nonexisting image should fail; triggering appropriate events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<img>
+
+<script>
+ async_test(function(t) {
+ var img = document.querySelector("img");
+ img.onload = this.step_func_done(function() {
+ assert_unreached("image.onload() was not supposed to be called");
+ });
+ img.onerror = this.step_func_done(function(e) {
+ assert_equals(e.type, "error", "image.onerror() called");
+ t.done();
+ });
+ img.src = "404";
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-below-viewport-image-loading-lazy.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-below-viewport-image-loading-lazy.html
new file mode 100644
index 0000000000..401771565a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-below-viewport-image-loading-lazy.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<head>
+ <title>Below-viewport loading=lazy not-rendered images should never load,
+ even when scrolled into view</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<body>
+ <!-- These images must not attempt to load when scrolled into the
+ viewport -->
+ <img id="display_none" style="display:none;" src="resources/image.png?not-rendered-2" loading="lazy"
+ onload="display_none_img.resolve();" onerror="display_none_img.reject();">
+ <img id="attribute_hidden" hidden src="resources/image.png?not-rendered-3" loading="lazy"
+ onload="attribute_hidden_img.resolve();" onerror="attribute_hidden_img.reject();">
+ <img id="js_display_none" src="resources/image.png?not-rendered-4" loading="lazy"
+ onload="js_display_none_img.resolve();" onerror="js_display_none_img.reject();">
+ <script>
+ document.getElementById("js_display_none").style = 'display:none;';
+ </script>
+
+ <!-- Later in the test we'll scroll to this div, instead of the above images,
+ since due to them not being rendered, they cannot be scrolled to -->
+ <div id="rendered_div"></div>
+</body>
+
+<script>
+ const display_none_img = new ElementLoadPromise("display_none");
+ const attribute_hidden_img = new ElementLoadPromise("attribute_hidden");
+ const js_display_none_img = new ElementLoadPromise("js_display_none");
+ const rendered_div_element = document.querySelector('#rendered_div');
+
+ async_test(t => {
+ window.addEventListener("load", t.step_func(() => {
+ rendered_div.scrollIntoView();
+ }));
+
+ const unreached_not_rendered_img_func =
+ t.unreached_func("The not-rendered below-viewport loading=lazy images " +
+ "should not attempt to load.");
+
+ display_none_img.promise
+ .then(unreached_not_rendered_img_func)
+ .catch(unreached_not_rendered_img_func);
+
+ attribute_hidden_img.promise
+ .then(unreached_not_rendered_img_func)
+ .catch(unreached_not_rendered_img_func);
+
+ js_display_none_img.promise
+ .then(unreached_not_rendered_img_func)
+ .catch(unreached_not_rendered_img_func);
+
+ // If none of the above images load after being scrolled to within the below
+ // timeout, the test passes.
+ t.step_timeout(t.done, 2000);
+ }, "Below-viewport loading=lazy not-rendered images should never load, " +
+ "even when scrolled into view");
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-dimension-getter.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-dimension-getter.html
new file mode 100644
index 0000000000..4d929fd8b1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-dimension-getter.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Image intrinsic dimensions are returned if the image isn't rendered</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-img-width">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="container" style="display: none">
+</div>
+<script>
+async_test(function(t) {
+ var img = document.createElement('img');
+ img.onload = t.step_func_done(function() {
+ assert_equals(img.width, 389, "intrinsic width should've been returned")
+ assert_equals(img.height, 590, "intrinsic height should've been returned")
+ document.getElementById('container').appendChild(img);
+ assert_equals(img.width, 389, "intrinsic width should've been returned");
+ assert_equals(img.height, 590, "intrinsic height should've been returned");
+ });
+ img.src = "image-1.jpg";
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-image-loading-lazy.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-image-loading-lazy.html
new file mode 100644
index 0000000000..25aaedb2b2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-image-loading-lazy.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<head>
+ <title>In-viewport loading=lazy not-rendered images should never load</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<body>
+ <!-- These images must not attempt to load -->
+ <img id="display_none" style="display:none;" src="resources/image.png?not-rendered-image-loading-lazy-2" loading="lazy"
+ onload="display_none_img.resolve();" onerror="display_none_img.reject();">
+ <img id="attribute_hidden" hidden src="resources/image.png?not-rendered-image-loading-lazy-3" loading="lazy"
+ onload="attribute_hidden_img.resolve();" onerror="attribute_hidden_img.reject();">
+ <img id="js_display_none" src="resources/image.png?not-rendered-image-loading-lazy-4" loading="lazy"
+ onload="js_display_none_img.resolve();" onerror="js_display_none_img.reject();">
+ <script>
+ document.getElementById("js_display_none").style = 'display:none;';
+ </script>
+</body>
+
+<script>
+ const display_none_img = new ElementLoadPromise("display_none");
+ const attribute_hidden_img = new ElementLoadPromise("attribute_hidden");
+ const js_display_none_img = new ElementLoadPromise("js_display_none");
+
+ async_test(t => {
+ const unreached_not_rendered_img_func =
+ t.unreached_func("The not-rendered in-viewport loading=lazy images " +
+ "should not attempt to load.");
+
+ display_none_img.promise
+ .then(unreached_not_rendered_img_func)
+ .catch(unreached_not_rendered_img_func);
+
+ attribute_hidden_img.promise
+ .then(unreached_not_rendered_img_func)
+ .catch(unreached_not_rendered_img_func);
+
+ js_display_none_img.promise
+ .then(unreached_not_rendered_img_func)
+ .catch(unreached_not_rendered_img_func);
+
+ t.step_timeout(t.done, 2000);
+ }, "In-viewport loading=lazy not-rendered images should never load");
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/null-image-source.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/null-image-source.html
new file mode 100644
index 0000000000..8999276503
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/null-image-source.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Null image source check for src, srcset and picture parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<img id="src_id" src="">
+<img id="srcset_id" srcset="">
+<picture><img id="parent_picture_id"></picture>
+<script>
+async_test(function(t) {
+ img = document.getElementById('src_id');
+ img.onerror = t.step_func(function(e) {
+ assert_equals(e.type, "error", "null image source check failed");
+ t.done();
+ });
+}, "img with empty src");
+
+async_test(function(t) {
+ img = document.getElementById('srcset_id');
+ img.onerror = t.unreached_func("empty srcset fires an error");
+ t.step_timeout(function() { t.done(); }, 2000);
+}, "img with empty srcset");
+
+async_test(function(t) {
+ img = document.getElementById('parent_picture_id');
+ img.onerror = t.unreached_func("null img with picture parent fires an error");
+ t.step_timeout(function() { t.done(); }, 2000);
+}, "img with picture parent");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/picture-loading-lazy.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/picture-loading-lazy.html
new file mode 100644
index 0000000000..08c01616bd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/picture-loading-lazy.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='lazy' in picture elements load when near the viewport</title>
+ <link rel="author" title="Raj T" href="mailto:rajendrant@chromium.org">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<script>
+const in_viewport_img = new ElementLoadPromise("in_viewport_img");
+const lazy_attribute_img = new ElementLoadPromise("lazy_attribute_img");
+const eager_attribute_img = new ElementLoadPromise("eager_attribute_img");
+
+const document_load_promise = new Promise(resolve => {
+ window.addEventListener("load", resolve);
+});
+
+async_test(function(t) {
+ document_load_promise.then(t.step_func_done(function() {
+ assert_false(lazy_attribute_img.element().complete);
+ lazy_attribute_img.element().scrollIntoView();
+ }));
+}, "Test that the loading=lazy <picture> element below viewport was deferred, on document load.");
+
+async_test(function(t) {
+ in_viewport_img.promise.then(t.step_func_done());
+}, "Test that in viewport <picture> element was loaded");
+
+async_test(function(t) {
+ eager_attribute_img.promise.then(t.step_func_done());
+}, "Test that eager <picture> element was loaded");
+
+async_test(function(t) {
+ lazy_attribute_img.promise.then(t.step_func_done());
+}, "Test that deferred <picture> element was loaded-in as well, after scrolled down");
+
+</script>
+
+<body>
+<picture>
+ <source sizes='50vw' srcset='resources/image.png?in_viewport_img'>
+ <img id='in_viewport_img' src='img-not-loaded.png' loading="lazy" onload="in_viewport_img.resolve();">
+</picture>
+<div style="height:10000px;"></div>
+<picture>
+ <source sizes='50vw' srcset='resources/image.png?lazy_attribute_img'>
+ <img id='lazy_attribute_img' src='img-not-loaded.png' loading="lazy" onload="lazy_attribute_img.resolve();">
+</picture>
+<picture>
+ <source sizes='50vw' srcset='resources/image.png?eager_attribute_img'>
+ <img id='eager_attribute_img' src='img-not-loaded.png' loading="eager" onload="eager_attribute_img.resolve();">
+</picture>
+
+<!--
+ This async script loads very slowly in order to ensure that, if the
+ below_viewport image has started loading, it has a chance to finish
+ loading before window.load() happens, so that the test will dependably fail
+ in that case instead of potentially passing depending on how long different
+ resource fetches take.
+-->
+<script async src="/common/slow.py"></script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations-lazy.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations-lazy.html
new file mode 100644
index 0000000000..d3784671b8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations-lazy.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<title>img relevant mutations, lazy-loaded</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/relevant-mutations.js"></script>
+<div id=log></div>
+
+<img src="/images/green-2x2.png"> <!-- block the window load event -->
+
+<!-- should invoke update the image data, but omit events for layout changes -->
+<!-- but also see https://github.com/whatwg/html/issues/8492 -->
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" width="2" loading="lazy" data-desc="width attribute changes">
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" style="width: 2px" loading="lazy" data-desc="width property changes">
+
+<div style="width: 2px">
+ <img srcset="/images/green-2x2.png 2w" sizes="auto" style="width: 100%" loading="lazy" data-desc="percentage width, CB width changes">
+</div>
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" style="height: 2px; aspect-ratio: 2 / 2" loading="lazy" data-desc="height property changes (with aspect-ratio)">
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" style="width: 2px" loading="lazy" data-desc="being rendered changes">
+
+<!-- should not invoke update the image data -->
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" style="width: 2px" loading="lazy" data-desc="loading attribute state changes">
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" style="width: 2px" loading="lazy" data-desc="display property changes to inline-block">
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" style="width: 2px" loading="lazy" data-desc="loading attribute changes to LAZY">
+
+<script>
+const rAF = () => new Promise(resolve => requestAnimationFrame(resolve));
+
+onload = async function() {
+
+ await rAF();
+ await rAF();
+
+ // lazy-loaded images should have fired their first 'load' event at this point.
+
+ t('width attribute changes', function(img) {
+ img.width = '4';
+ }, 'load');
+
+ t('width property changes', function(img) {
+ img.style.width = '4px';
+ }, 'timeout');
+
+ t('percentage width, CB width changes', function(img) {
+ img.parentNode.style.width = '4px';
+ }, 'timeout');
+
+ t('height property changes (with aspect-ratio)', function(img) {
+ img.style.height = '4px';
+ }, 'timeout');
+
+ t('loading attribute state changes', function(img) {
+ img.loading = 'eager';
+ }, 'timeout');
+
+ t('being rendered changes', function(img) {
+ img.style.display = 'none';
+ }, 'timeout');
+
+ t('display property changes to inline-block', function(img) {
+ img.style.display = 'inline-block';
+ }, 'timeout');
+
+ t('loading attribute changes to LAZY', function(img) {
+ img.setAttribute('loading', 'LAZY');
+ }, 'timeout');
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations.html
new file mode 100644
index 0000000000..24e15543cd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations.html
@@ -0,0 +1,628 @@
+<!doctype html>
+<title>img relevant mutations</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/relevant-mutations.js"></script>
+<div id=log></div>
+
+<!-- should invoke update the image data -->
+
+<img data-desc="src set">
+<img src="/images/green-2x2.png" data-desc="src changed">
+<img src="/images/green-2x2.png" data-desc="src removed">
+
+<img data-desc="srcset set">
+<img srcset="/images/green-2x2.png" data-desc="srcset changed">
+<img srcset="/images/green-2x2.png" data-desc="srcset removed">
+
+<img data-desc="sizes set">
+<img sizes="" data-desc="sizes changed">
+<img sizes="" data-desc="sizes removed">
+
+<img src="/images/green-2x2.png" data-desc="src set to same value">
+
+<img data-desc="crossorigin absent to empty, src absent">
+<img data-desc="crossorigin absent to anonymous, src absent">
+<img data-desc="crossorigin absent to use-credentials, src absent">
+<img crossorigin data-desc="crossorigin empty to absent, src absent">
+<img crossorigin data-desc="crossorigin empty to use-credentials, src absent">
+<img crossorigin=anonymous data-desc="crossorigin anonymous to absent, src absent">
+<img crossorigin=anonymous data-desc="crossorigin anonymous to use-credentials, src absent">
+<img crossorigin=use-credentials data-desc="crossorigin use-credentials to absent, src absent">
+<img crossorigin=use-credentials data-desc="crossorigin use-credentials to empty, src absent">
+<img crossorigin=use-credentials data-desc="crossorigin use-credentials to anonymous, src absent">
+<img crossorigin=use-credentials data-desc="crossorigin use-credentials to invalid, src absent">
+
+<img src="/images/green-2x2.png" data-desc="crossorigin absent to empty, src already set">
+<img src="/images/green-2x2.png" data-desc="crossorigin absent to anonymous, src already set">
+<img src="/images/green-2x2.png" data-desc="crossorigin absent to use-credentials, src already set">
+<img src="/images/green-2x2.png" crossorigin data-desc="crossorigin empty to absent, src already set">
+<img src="/images/green-2x2.png" crossorigin data-desc="crossorigin empty to use-credentials, src already set">
+<img src="/images/green-2x2.png" crossorigin=anonymous data-desc="crossorigin anonymous to absent, src already set">
+<img src="/images/green-2x2.png" crossorigin=anonymous data-desc="crossorigin anonymous to use-credentials, src already set">
+<img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin use-credentials to absent, src already set">
+<img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin use-credentials to empty, src already set">
+<img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin use-credentials to anonymous, src already set">
+<img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin use-credentials to invalid, src already set">
+
+<img data-desc="referrerpolicy absent to no-referrer-when-downgrade, src absent">
+<img data-desc="referrerpolicy absent to no-referrer, src absent">
+<img referrerpolicy data-desc="referrerpolicy empty to no-referrer-when-downgrade, src absent">
+<img referrerpolicy data-desc="referrerpolicy empty to no-referrer, src absent">
+<img referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to absent, src absent">
+<img referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to empty, src absent">
+<img referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to no-referrer, src absent">
+<img referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to invalid, src absent">
+<img referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to absent, src absent">
+<img referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to empty, src absent">
+<img referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to no-referrer-when-downgrade, src absent">
+<img referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to invalid, src absent">
+
+<img src="/images/green-2x2.png" data-desc="referrerpolicy absent to no-referrer-when-downgrade, src already set">
+<img src="/images/green-2x2.png" data-desc="referrerpolicy absent to no-referrer, src already set">
+<img src="/images/green-2x2.png" referrerpolicy data-desc="referrerpolicy empty to no-referrer-when-downgrade, src already set">
+<img src="/images/green-2x2.png" referrerpolicy data-desc="referrerpolicy empty to no-referrer, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to absent, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to empty, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to no-referrer, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to invalid, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to absent, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to empty, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to no-referrer-when-downgrade, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to invalid, src already set">
+
+<img src="/images/green-2x2.png" data-desc="inserted into picture"><picture></picture>
+
+<picture><img src="/images/green-2x2.png" data-desc="removed from picture"></picture>
+
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, previous source inserted"></picture>
+
+<picture><source><img src="/images/green-2x2.png" data-desc="parent is picture, previous source removed"></picture>
+
+<picture><source><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has srcset set"></picture>
+<picture><source srcset=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has srcset changed"></picture>
+<picture><source srcset=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has srcset removed"></picture>
+
+<picture><source><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has sizes set"></picture>
+<picture><source sizes=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has sizes changed"></picture>
+<picture><source sizes=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has sizes removed"></picture>
+
+<picture><source><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has media set"></picture>
+<picture><source media=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has media changed"></picture>
+<picture><source media=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has media removed"></picture>
+
+<picture><source><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has type set"></picture>
+<picture><source type=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has type changed"></picture>
+<picture><source type=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has type removed"></picture>
+
+<img srcset="/images/green-2x2.png" data-desc="srcset is set to same value">
+<img srcset="/images/green-2x2.png" sizes data-desc="sizes is set to same value">
+
+<img src="/images/green-2x2.png" data-desc="crossorigin state not changed: absent, removeAttribute">
+<img src="/images/green-2x2.png" crossorigin data-desc="crossorigin state not changed: empty to anonymous">
+<img src="/images/green-2x2.png" crossorigin=anonymous data-desc="crossorigin state not changed: anonymous to foobar">
+<img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin state not changed: use-credentials to USE-CREDENTIALS">
+
+<img src="/images/green-2x2.png" data-desc="referrerpolicy state not changed: absent, removeAttribute">
+<img src="/images/green-2x2.png" referrerpolicy data-desc="referrerpolicy state not changed: empty to empty">
+<img src="/images/green-2x2.png" referrerpolicy data-desc="referrerpolicy state not changed: empty to invalid">
+<img src="/images/green-2x2.png" data-desc="referrerpolicy state not changed: absent to invalid">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer data-desc="referrerpolicy state not changed: no-referrer to NO-REFERRER">
+<img src="/images/green-2x2.png" referrerpolicy=foobar data-desc="referrerpolicy state not changed: invalid to other-invalid">
+
+<img src="/images/green-2x2.png" data-desc="inserted into picture ancestor"><picture><span></span></picture>
+<picture><span><img src="/images/green-2x2.png" data-desc="removed from picture ancestor"></span></picture>
+
+<picture><span><img src="/images/green-2x2.png" data-desc="ancestor picture has a source inserted"></span></picture>
+<picture><source><span><img src="/images/green-2x2.png" data-desc="ancestor picture has a source removed"></span></picture>
+
+<picture><span><img src="/images/green-2x2.png" data-desc="ancestor picture; previous sibling source inserted"></span></picture>
+<picture><span><source><img src="/images/green-2x2.png" data-desc="ancestor picture; previous sibling source removed"></span></picture>
+
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following sibling source inserted"></picture>
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following sibling source removed"><source></picture>
+
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following sibling source has srcset set"><source></picture>
+
+<img src="/images/green-2x2.png" data-desc="media on img set">
+<img src="/images/green-2x2.png" data-desc="type on img set">
+<img src="/images/green-2x2.png" data-desc="class on img set">
+<img src="/images/green-2x2.png" data-desc="alt on img set">
+
+<picture><source><img src="/images/green-2x2.png" data-desc="src on previous sibling source set"></picture>
+<picture><source><img src="/images/green-2x2.png" data-desc="class on previous sibling source set"></picture>
+
+<img src="/images/green-2x2.png" data-desc="inserted/removed children of img">
+
+<picture><img src="/images/green-2x2.png" data-desc="picture is inserted; img has src"></picture><span></span>
+<picture><img srcset="/images/green-2x2.png" data-desc="picture is inserted; img has srcset"></picture><span></span>
+<picture><source srcset="/images/green-2x2.png"><img src="/images/green-2x2.png" data-desc="picture is inserted; img has previous sibling source"></picture><span></span>
+<picture><img src="/images/green-2x2.png" data-desc="picture is inserted; img has following sibling source"><source srcset="/images/green-2x2.png"></picture><span></span>
+
+<picture><img src="/images/green-2x2.png" data-desc="picture is removed; img has src"></picture>
+<picture><img srcset="/images/green-2x2.png" data-desc="picture is removed; img has srcset"></picture>
+<picture><source srcset="/images/green-2x2.png"><img src="/images/green-2x2.png" data-desc="picture is removed; img has previous sibling source"></picture>
+<picture><img src="/images/green-2x2.png" data-desc="picture is removed; img has following sibling source"><source srcset="/images/green-2x2.png"></picture>
+
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following img inserted"></picture>
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following img removed"><img></picture>
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following img has src set"><img></picture>
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following img has srcset set"><img></picture>
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following img has sizes set"><img></picture>
+
+
+<script>
+onload = function() {
+
+ t('src set', function(img) {
+ img.src = '/images/green-2x2.png';
+ }, 'load');
+
+ t('src changed', function(img) {
+ img.src = '/images/green-2x2.png ';
+ }, 'load');
+
+ t('src removed', function(img) {
+ img.removeAttribute('src');
+ }, 'timeout');
+
+ t('srcset set', function(img) {
+ img.srcset = '/images/green-2x2.png';
+ }, 'load');
+
+ t('srcset changed', function(img) {
+ img.srcset = '/images/green-2x2.png ';
+ }, 'load');
+
+ t('srcset removed', function(img) {
+ img.removeAttribute('srcset');
+ }, 'timeout');
+
+ t('sizes set', function(img) {
+ img.sizes = '';
+ }, 'timeout');
+
+ t('sizes changed', function(img) {
+ img.sizes = ' ';
+ }, 'timeout');
+
+ t('sizes removed', function(img) {
+ img.removeAttribute('sizes');
+ }, 'timeout');
+
+ t('src set to same value', function(img) {
+ img.src = '/images/green-2x2.png';
+ }, 'load');
+
+ // When src is absent, changing the crossorigin attribute state MUST NOT
+ // generate events.
+
+ t('crossorigin absent to empty, src absent', function(img) {
+ img.crossOrigin = '';
+ }, 'timeout');
+
+ t('crossorigin absent to anonymous, src absent', function(img) {
+ img.crossOrigin = 'anonymous';
+ }, 'timeout');
+
+ t('crossorigin absent to use-credentials, src absent', function(img) {
+ img.crossOrigin = 'use-credentials';
+ }, 'timeout');
+
+ t('crossorigin empty to absent, src absent', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'timeout');
+
+ t('crossorigin empty to use-credentials, src absent', function(img) {
+ img.crossOrigin = 'use-credentials';
+ }, 'timeout');
+
+ t('crossorigin anonymous to absent, src absent', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'timeout');
+
+ t('crossorigin anonymous to use-credentials, src absent', function(img) {
+ img.crossOrigin = 'use-credentials';
+ }, 'timeout');
+
+ t('crossorigin use-credentials to absent, src absent', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'timeout');
+
+ t('crossorigin use-credentials to empty, src absent', function(img) {
+ img.crossOrigin = '';
+ }, 'timeout');
+
+ t('crossorigin use-credentials to anonymous, src absent', function(img) {
+ img.crossOrigin = 'anonymous';
+ }, 'timeout');
+
+ t('crossorigin use-credentials to invalid, src absent', function(img) {
+ img.crossOrigin = 'foobar';
+ }, 'timeout');
+
+ // When src is set, changing the crossorigin attribute state MUST generate
+ // events.
+
+ t('crossorigin absent to empty, src already set', function(img) {
+ img.crossOrigin = '';
+ }, 'load');
+
+ t('crossorigin absent to anonymous, src already set', function(img) {
+ img.crossOrigin = 'anonymous';
+ }, 'load');
+
+ t('crossorigin absent to use-credentials, src already set', function(img) {
+ img.crossOrigin = 'use-credentials';
+ }, 'load');
+
+ t('crossorigin empty to absent, src already set', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'load');
+
+ t('crossorigin empty to use-credentials, src already set', function(img) {
+ img.crossOrigin = 'use-credentials';
+ }, 'load');
+
+ t('crossorigin anonymous to absent, src already set', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'load');
+
+ t('crossorigin anonymous to use-credentials, src already set', function(img) {
+ img.crossOrigin = 'use-credentials';
+ }, 'load');
+
+ t('crossorigin use-credentials to absent, src already set', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'load');
+
+ t('crossorigin use-credentials to empty, src already set', function(img) {
+ img.crossOrigin = '';
+ }, 'load');
+
+ t('crossorigin use-credentials to anonymous, src already set', function(img) {
+ img.crossOrigin = 'anonymous';
+ }, 'load');
+
+ t('crossorigin use-credentials to invalid, src already set', function(img) {
+ img.crossOrigin = 'foobar';
+ }, 'load');
+
+ // When src is absent, changing the referrerpolicy attribute state MUST NOT
+ // generate events.
+
+ t('referrerpolicy absent to no-referrer-when-downgrade, src absent', function(img) {
+ img.referrerPolicy = 'no-referrer-when-downgrade';
+ }, 'timeout');
+
+ t('referrerpolicy absent to no-referrer, src absent', function(img) {
+ img.referrerPolicy = 'no-referrer';
+ }, 'timeout');
+
+ t('referrerpolicy empty to no-referrer-when-downgrade, src absent', function(img) {
+ img.referrerPolicy = 'no-referrer-when-downgrade';
+ }, 'timeout');
+
+ t('referrerpolicy empty to no-referrer, src absent', function(img) {
+ img.referrerPolicy = 'no-referrer';
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer-when-downgrade to absent, src absent', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer-when-downgrade to empty, src absent', function(img) {
+ img.referrerPolicy = '';
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer-when-downgrade to no-referrer, src absent', function(img) {
+ img.referrerPolicy = 'no-referrer';
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer-when-downgrade to invalid, src absent', function(img) {
+ img.referrerPolicy = 'foobar';
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer to absent, src absent', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer to empty, src absent', function(img) {
+ img.referrerPolicy = '';
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer to no-referrer-when-downgrade, src absent', function(img) {
+ img.referrerPolicy = 'no-referrer-when-downgrade';
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer to invalid, src absent', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'timeout');
+
+ // When src is set, changing the referrerpolicy attribute state MUST generate
+ // events.
+
+ t('referrerpolicy absent to no-referrer-when-downgrade, src already set', function(img) {
+ img.referrerPolicy = 'no-referrer-when-downgrade';
+ }, 'load');
+
+ t('referrerpolicy absent to no-referrer, src already set', function(img) {
+ img.referrerPolicy = 'no-referrer';
+ }, 'load');
+
+ t('referrerpolicy empty to no-referrer-when-downgrade, src already set', function(img) {
+ img.referrerPolicy = 'no-referrer-when-downgrade';
+ }, 'load');
+
+ t('referrerpolicy empty to no-referrer, src already set', function(img) {
+ img.referrerPolicy = 'no-referrer';
+ }, 'load');
+
+ t('referrerpolicy no-referrer-when-downgrade to absent, src already set', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'load');
+
+ t('referrerpolicy no-referrer-when-downgrade to empty, src already set', function(img) {
+ img.referrerPolicy = '';
+ }, 'load');
+
+ t('referrerpolicy no-referrer-when-downgrade to no-referrer, src already set', function(img) {
+ img.referrerPolicy = 'no-referrer';
+ }, 'load');
+
+ t('referrerpolicy no-referrer-when-downgrade to invalid, src already set', function(img) {
+ img.referrerPolicy = 'foobar';
+ }, 'load');
+
+ t('referrerpolicy no-referrer to absent, src already set', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'load');
+
+ t('referrerpolicy no-referrer to empty, src already set', function(img) {
+ img.referrerPolicy = '';
+ }, 'load');
+
+ t('referrerpolicy no-referrer to no-referrer-when-downgrade, src already set', function(img) {
+ img.referrerPolicy = 'no-referrer-when-downgrade';
+ }, 'load');
+
+ t('referrerpolicy no-referrer to invalid, src already set', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'load');
+
+
+ t('inserted into picture', function(img) {
+ img.nextSibling.appendChild(img);
+ }, 'load');
+
+ t('removed from picture', function(img) {
+ img.parentNode.removeChild(img);
+ }, 'load');
+
+ t('parent is picture, previous source inserted', function(img) {
+ img.parentNode.insertBefore(document.createElement('source'), img);
+ }, 'load');
+
+ t('parent is picture, previous source removed', function(img) {
+ img.parentNode.removeChild(img.previousSibling);
+ }, 'load');
+
+ t('parent is picture, previous source has srcset set', function(img) {
+ img.previousSibling.srcset = '';
+ }, 'load');
+
+ t('parent is picture, previous source has srcset changed', function(img) {
+ img.previousSibling.srcset = ' ';
+ }, 'load');
+
+ t('parent is picture, previous source has srcset removed', function(img) {
+ img.previousSibling.removeAttribute('srcset');
+ }, 'load');
+
+ t('parent is picture, previous source has sizes set', function(img) {
+ img.previousSibling.sizes = '';
+ }, 'load');
+
+ t('parent is picture, previous source has sizes changed', function(img) {
+ img.previousSibling.sizes = ' ';
+ }, 'load');
+
+ t('parent is picture, previous source has sizes removed', function(img) {
+ img.previousSibling.removeAttribute('sizes');
+ }, 'load');
+
+ t('parent is picture, previous source has media set', function(img) {
+ img.previousSibling.media = '';
+ }, 'load');
+
+ t('parent is picture, previous source has media changed', function(img) {
+ img.previousSibling.media = ' ';
+ }, 'load');
+
+ t('parent is picture, previous source has media removed', function(img) {
+ img.previousSibling.removeAttribute('media');
+ }, 'load');
+
+ t('parent is picture, previous source has type set', function(img) {
+ img.previousSibling.type = '';
+ }, 'load');
+
+ t('parent is picture, previous source has type changed', function(img) {
+ img.previousSibling.type = ' ';
+ }, 'load');
+
+ t('parent is picture, previous source has type removed', function(img) {
+ img.previousSibling.removeAttribute('type');
+ }, 'load');
+
+ t('srcset is set to same value', function(img) {
+ img.srcset = "/images/green-2x2.png";
+ }, 'load');
+
+ t('sizes is set to same value', function(img) {
+ img.sizes = '';
+ }, 'load');
+
+ t('crossorigin state not changed: absent, removeAttribute', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'timeout');
+
+ t('crossorigin state not changed: empty to anonymous', function(img) {
+ img.crossOrigin = 'anonymous';
+ }, 'timeout');
+
+ t('crossorigin state not changed: anonymous to foobar', function(img) {
+ img.crossOrigin = 'foobar';
+ }, 'timeout');
+
+ t('crossorigin state not changed: use-credentials to USE-CREDENTIALS', function(img) {
+ img.crossOrigin = 'USE-CREDENTIALS';
+ }, 'timeout');
+
+ t('referrerpolicy state not changed: absent, removeAttribute', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'timeout');
+
+ t('referrerpolicy state not changed: empty to empty', function(img) {
+ img.referrerPolicy = '';
+ }, 'timeout');
+
+ t('referrerpolicy state not changed: empty to invalid', function(img) {
+ img.referrerPolicy = 'foobar';
+ }, 'timeout');
+
+ t('referrerpolicy state not changed: absent to invalid', function(img) {
+ img.referrerPolicy = 'foobar';
+ }, 'timeout');
+
+ t('referrerpolicy state not changed: no-referrer to NO-REFERRER', function(img) {
+ img.referrerPolicy = 'NO-REFERRER';
+ }, 'timeout');
+
+ t('referrerpolicy state not changed: invalid to other-invalid', function(img) {
+ img.referrerPolicy = 'foobar2';
+ }, 'timeout');
+
+ t('inserted into picture ancestor', function(img) {
+ img.nextSibling.firstChild.appendChild(img);
+ }, 'timeout');
+
+ t('removed from picture ancestor', function(img) {
+ img.parentNode.removeChild(img);
+ }, 'timeout');
+
+ t('ancestor picture has a source inserted', function(img) {
+ img.parentNode.parentNode.insertBefore(document.createElement('source'), img.parentNode);
+ }, 'timeout');
+
+ t('ancestor picture has a source removed', function(img) {
+ img.parentNode.parentNode.removeChild(img.parentNode.previousSibling);
+ }, 'timeout');
+
+ t('ancestor picture; previous sibling source inserted', function(img) {
+ img.parentNode.insertBefore(document.createElement('source'), img);
+ }, 'timeout');
+
+ t('ancestor picture; previous sibling source removed', function(img) {
+ img.parentNode.removeChild(img.previousSibling);
+ }, 'timeout');
+
+ t('parent is picture, following sibling source inserted', function(img) {
+ img.parentNode.appendChild(document.createElement('source'));
+ }, 'timeout');
+
+ t('parent is picture, following sibling source removed', function(img) {
+ img.parentNode.removeChild(img.nextSibling);
+ }, 'timeout');
+
+ t('parent is picture, following sibling source has srcset set', function(img) {
+ img.nextSibling.srcset = '';
+ }, 'timeout');
+
+ t('media on img set', function(img) {
+ img.setAttribute('media', '');
+ }, 'timeout');
+
+ t('type on img set', function(img) {
+ img.setAttribute('type', '');
+ }, 'timeout');
+
+ t('class on img set', function(img) {
+ img.className = '';
+ }, 'timeout');
+
+ t('alt on img set', function(img) {
+ img.alt = '';
+ }, 'timeout');
+
+ t('src on previous sibling source set', function(img) {
+ img.previousSibling.setAttribute('src', '');
+ }, 'timeout');
+
+ t('class on previous sibling source set', function(img) {
+ img.previousSibling.className = '';
+ }, 'timeout');
+
+ t('inserted/removed children of img', function(img) {
+ img.appendChild(document.createElement('source'));
+ setTimeout(this.step_func(function() {
+ img.removeChild(img.firstChild);
+ }), 0);
+ }, 'timeout');
+
+ t('picture is inserted; img has src', function(img) {
+ img.parentNode.nextSibling.appendChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is inserted; img has srcset', function(img) {
+ img.parentNode.nextSibling.appendChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is inserted; img has previous sibling source', function(img) {
+ img.parentNode.nextSibling.appendChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is inserted; img has following sibling source', function(img) {
+ img.parentNode.nextSibling.appendChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is removed; img has src', function(img) {
+ img.parentNode.parentNode.removeChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is removed; img has srcset', function(img) {
+ img.parentNode.parentNode.removeChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is removed; img has previous sibling source', function(img) {
+ img.parentNode.parentNode.removeChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is removed; img has following sibling source', function(img) {
+ img.parentNode.parentNode.removeChild(img.parentNode);
+ }, 'timeout');
+
+ t('parent is picture, following img inserted', function(img) {
+ img.parentNode.appendChild(document.createElement('img'));
+ }, 'timeout');
+
+ t('parent is picture, following img removed', function(img) {
+ img.parentNode.removeChild(img.nextSibling);
+ }, 'timeout');
+
+ t('parent is picture, following img has src set', function(img) {
+ img.nextSibling.src = '';
+ }, 'timeout');
+
+ t('parent is picture, following img has srcset set', function(img) {
+ img.nextSibling.srcset = '';
+ }, 'timeout');
+
+ t('parent is picture, following img has sizes set', function(img) {
+ img.nextSibling.sizes = '';
+ }, 'timeout');
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/remove-element-and-scroll.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/remove-element-and-scroll.html
new file mode 100644
index 0000000000..8e7fa1bfbf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/remove-element-and-scroll.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='lazy' load being removed and then scrolled to</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="common.js"></script>
+</head>
+
+<body>
+ <img id="in_viewport" src='resources/image.png?in_viewport&pipe=trickle(d1)'>
+ <div style="height:1000vh"></div>
+ <div id="below_viewport_div"></div>
+ <img id="below_viewport" src='resources/image.png?below_viewport' loading="lazy">
+
+ <script>
+ const in_viewport_element = document.getElementById("in_viewport");
+ const below_viewport_element = document.getElementById("below_viewport");
+ const below_viewport_div = document.getElementById("below_viewport_div");
+
+ async_test(t => {
+ below_viewport_element.onload = t.unreached_func("Removed loading=lazy image " +
+ "should not load when its old position is scrolled to.");
+ below_viewport_element.remove();
+
+ in_viewport_element.onload = () => {
+ below_viewport_div.scrollIntoView();
+ t.step_timeout(t.step_func_done(), 2000);
+ };
+ }, "Test that <img> below viewport is not loaded when removed from the " +
+ "document and then scrolled to");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/blue-10.png b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/blue-10.png
new file mode 100644
index 0000000000..62949e08d8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/blue-10.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/cat.jpg b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/cat.jpg
new file mode 100644
index 0000000000..a4f14f54d6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/cat.jpg
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/green.png b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/green.png
new file mode 100644
index 0000000000..25b76c3c6f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/green.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-and-stash.py b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-and-stash.py
new file mode 100644
index 0000000000..d16a3e591d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-and-stash.py
@@ -0,0 +1,44 @@
+# This is a simple implementation of a server-side stash that supports two
+# operations:
+# - increment:
+# Increments a value in the stash keyed by a given uuid, and returns an
+# image file
+# - read:
+# Returns the value in the stash keyed by a given uuid or 0 otherwise.
+# This is a read-only operation, and does not remove the value from the
+# stash as-is the default WPT stash behavior:
+# https://web-platform-tests.org/tools/wptserve/docs/stash.html.
+
+import os
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ if b"increment" in request.GET:
+ uuid = request.GET[b"increment"]
+
+ # First, increment the stash value keyed by `uuid`, and write it back to the
+ # stash. Writing it back to the stash is necessary since `take()` actually
+ # removes the value whereas we want to increment it.
+ stash_value = request.server.stash.take(uuid)
+ if stash_value is None:
+ stash_value = 0
+ request.server.stash.put(uuid, stash_value + 1)
+
+ # Return a basic image.
+ response_headers = [(b"Content-Type", b"image/png")]
+ image_path = os.path.join(os.path.dirname(isomorphic_decode(__file__)), u"image.png")
+ return (200, response_headers, open(image_path, mode='rb').read())
+
+ elif b"read" in request.GET:
+ uuid = request.GET[b"read"]
+ stash_value = request.server.stash.take(uuid)
+
+ if stash_value is None:
+ stash_value = 0
+ # Write the stash value keyed by `uuid` back to the stash. This is necessary
+ # because `take()` actually removes it, but we want a read-only read.
+ request.server.stash.put(uuid, stash_value);
+ return (200, [], str(stash_value))
+
+ return (404 , [], "Not found")
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-below-viewport.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-below-viewport.html
new file mode 100644
index 0000000000..f25bd6f4d0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-below-viewport.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<div style="height:1000vh;"></div>
+
+<img id="img" loading="lazy" src="image.png?lazy-below-viewport">
+
+<script>
+ const img = document.querySelector('#img');
+
+ img.addEventListener("load", () => {
+ parent.postMessage("image_loaded", "*");
+ });
+
+ window.addEventListener("load", () => {
+ parent.postMessage("window_loaded", "*");
+ });
+
+ window.addEventListener("message", event => {
+ if (event.data == "scroll") {
+ img.scrollIntoView();
+ }
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-in-viewport.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-in-viewport.html
new file mode 100644
index 0000000000..bafdacc883
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-in-viewport.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+
+<!-- This frame is used by image-loading-lazy-in-cross-origin-iframe-002.sub.html -->
+
+<img id="img" loading="lazy">
+
+<script>
+ const img = document.querySelector('#img');
+
+ // Prevent the list of available images check from loading the image non-lazily.
+ img.src = "image.png?image-loading-lazy-in-viewport-" + Math.random() + "-" + Date.now();
+
+ img.addEventListener("load", () => {
+ parent.postMessage("image_loaded", "*");
+ });
+
+ window.addEventListener("load", () => {
+ parent.postMessage("window_loaded", "*");
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image.png b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image.png
new file mode 100644
index 0000000000..b712825093
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/newwindow.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/newwindow.html
new file mode 100644
index 0000000000..735b8f6213
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/newwindow.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<div style="height:1000vh;"></div>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/red.png b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/red.png
new file mode 100644
index 0000000000..57bf3ddc52
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/red.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/referrer-checker-img.py b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/referrer-checker-img.py
new file mode 100644
index 0000000000..bb2071cb97
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/referrer-checker-img.py
@@ -0,0 +1,14 @@
+import os
+
+from wptserve.utils import isomorphic_decode
+
+# Returns a valid image response when request's |referrer| matches
+# |expected_referrer|.
+def main(request, response):
+ referrer = request.headers.get(b"referer", b"")
+ expected_referrer = request.GET.first(b"expected_referrer", b"")
+ response_headers = [(b"Content-Type", b"image/png")]
+ if referrer == expected_referrer:
+ image_path = os.path.join(os.path.dirname(isomorphic_decode(__file__)), u"image.png")
+ return (200, response_headers, open(image_path, mode='rb').read())
+ return (404, response_headers, u"Not found")
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js
new file mode 100644
index 0000000000..8bd079f790
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js
@@ -0,0 +1,20 @@
+addEventListener('install', (event) => {
+ skipWaiting();
+});
+
+addEventListener('activate', (event) => {
+ event.waitUntil(clients.claim());
+});
+
+async function broadcast(msg) {
+ const allClients = await clients.matchAll();
+ for (const client of allClients) {
+ client.postMessage(msg);
+ }
+}
+
+addEventListener('fetch', (event) => {
+ event.waitUntil(
+ broadcast({ url: event.request.url, mode: event.request.mode })
+ )
+});
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js.headers b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js.headers
new file mode 100644
index 0000000000..3c534471c8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js.headers
@@ -0,0 +1 @@
+Service-Worker-Allowed: /html/semantics/embedded-content/the-img-element/
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print-ref.html
new file mode 100644
index 0000000000..7189a57642
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+ <div style="width: 200px; height: 200px; background-color: green;"></div>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print.html
new file mode 100644
index 0000000000..60b061f14a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Test print result of responsive image</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#srcset-attribute">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1803094">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="match" href="responsive-image-select-print-ref.html">
+<body>
+ <picture>
+ <source width="200" srcset="./resources/red.png 1w, ./resources/green.png 200w">
+ <img>
+ </picture>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/scrolling-below-viewport-image-lazy-loading-in-iframe.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/scrolling-below-viewport-image-lazy-loading-in-iframe.html
new file mode 100644
index 0000000000..3c26323426
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/scrolling-below-viewport-image-lazy-loading-in-iframe.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Scrolling a lazy loaded image into view in an iframe</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<iframe onload="async_test(this.contentWindow.run)" srcdoc="
+<!DOCTYPE html>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='../resources/common.js'></script>
+
+<h1>Scroll down...</h1>
+<p>Scroll down...</p>
+<p>Scroll down...</p>
+<img id='below_iframe_viewport_img' src='resources/image.png' loading='lazy'
+ onload='below_iframe_viewport_img.resolve();' onerror='below_iframe_viewport_img.reject();'>
+
+<script>
+ const scroll_trigger_img = new ElementLoadPromise('visible');
+ const below_iframe_viewport_img = new ElementLoadPromise('below_iframe_viewport_img');
+
+ function run(t) {
+ below_iframe_viewport_img.element().scrollIntoView();
+ below_iframe_viewport_img.promise
+ .then(t.step_func(() => {
+ assert_not_equals(below_iframe_viewport_img.element().width, 0, 'width should be greater than zero after scrolling');
+ assert_not_equals(below_iframe_viewport_img.element().height, 0, 'height should be greater than zero after scrolling');
+ t.done();
+ }))
+ .catch(t.unreached_func('The below_iframe_viewport image should load'));
+ };
+</script>
+"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/implicit-sizes-ignores-width.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/implicit-sizes-ignores-width.html
new file mode 100644
index 0000000000..db61db351e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/implicit-sizes-ignores-width.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>Implicit sizes ignores width</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+img {width: auto;}
+</style>
+<img srcset="../srcset/resources/image.png 100w" sizes="400px" id="sizes">
+<img srcset="../srcset/resources/image.png 100w" width="400" id="width">
+<script>
+setup({explicit_done:true});
+onload = () => {
+ test(() => {
+ assert_equals(document.getElementById("sizes").width, 400);
+ assert_equals(document.getElementById("width").width, window.innerWidth);
+ done();
+ });
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-display-none.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-display-none.html
new file mode 100644
index 0000000000..6aa77ebf85
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-display-none.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>img parse a sizes attribute (display:none)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<iframe data-desc="display:none" style="width:1000px; height:1000px" src="support/sizes-iframed.sub.html?doctype=doctype%20html&style=display:none"></iframe>
+<script src="support/parse-a-sizes-attribute.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-quirks-mode.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-quirks-mode.html
new file mode 100644
index 0000000000..2150192d29
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-quirks-mode.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>img parse a sizes attribute (quirks mode)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<iframe data-desc="quirks mode" style="width:1000px; height:1000px" src="support/sizes-iframed.sub.html?doctype=----&style="></iframe>
+<script src="support/parse-a-sizes-attribute.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-standards-mode.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-standards-mode.html
new file mode 100644
index 0000000000..6e70c88396
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-standards-mode.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>img parse a sizes attribute (standards mode)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<iframe data-desc="standards mode" style="width:1000px; height:1000px" src="support/sizes-iframed.sub.html?doctype=doctype%20html&style="></iframe>
+<script src="support/parse-a-sizes-attribute.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-width-1000px.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-width-1000px.html
new file mode 100644
index 0000000000..ab3f69e058
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-width-1000px.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>img parse a sizes attribute (width:1000px)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<iframe data-desc="width:1000px" style="width:1000px; height:1000px" src="support/sizes-iframed.sub.html?doctype=doctype%20html&style=width:1000px%3B%20height:16px"></iframe>
+<script src="support/parse-a-sizes-attribute.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/reference/sizes-auto-rendering-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/reference/sizes-auto-rendering-ref.html
new file mode 100644
index 0000000000..221930fddb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/reference/sizes-auto-rendering-ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>Auto sizes rendering</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#sizes-attributes">
+<img
+ src="/images/green.png"
+ width="33"
+ height="13"
+>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-2.html
new file mode 100644
index 0000000000..75d1884e34
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-2.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Auto sizes rendering</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#sizes-attributes">
+<link rel="match" href="reference/sizes-auto-rendering-ref.html">
+<script src="/common/rendering-utils.js"></script>
+<script src="/common/reftest-wait.js"></script>
+<img
+ id="testImg"
+ loading="lazy"
+ sizes="auto"
+ width="33"
+ height="13"
+>
+<script>
+ function imageLoaded() {
+ waitForAtLeastOneFrame().then(takeScreenshot);
+ }
+
+ testImg.addEventListener('load', imageLoaded);
+ testImg.setAttribute('srcset', `
+ /images/red.png 10w,
+ /images/green.png 100w
+ `);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-3.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-3.html
new file mode 100644
index 0000000000..71ed90ebbe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-3.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Auto sizes rendering</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#sizes-attributes">
+<link rel="match" href="reference/sizes-auto-rendering-ref.html">
+<script src="/common/rendering-utils.js"></script>
+<script src="/common/reftest-wait.js"></script>
+<img
+ id="testImg"
+ loading="lazy"
+ sizes="auto"
+ width="33"
+ height="13"
+>
+<script>
+ function imageLoaded() {
+ waitForAtLeastOneFrame().then(takeScreenshot);
+ }
+
+ testImg.addEventListener('load', imageLoaded);
+ testImg.setAttribute('srcset', `
+ /images/green.png 100w,
+ /images/red.png 1000w
+ `);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-dynamic.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-dynamic.html
new file mode 100644
index 0000000000..901c6f2e09
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-dynamic.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Auto sizes dynamic rendering</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#sizes-attributes">
+<link rel="match" href="reference/sizes-auto-rendering-ref.html">
+<script src="/common/rendering-utils.js"></script>
+<script src="/common/reftest-wait.js"></script>
+<img
+ id="testImg"
+ loading="lazy"
+ sizes="auto"
+ width="1"
+ height="13"
+>
+<script>
+ function secondImageLoaded() {
+ waitForAtLeastOneFrame().then(takeScreenshot);
+ }
+
+ function firstImageLoaded() {
+ const expected = 'red.png';
+ if (!testImg.currentSrc.endsWith('red.png')) {
+ const fail_msg = `FAIL: currentSrc is ${testImg.currentSrc}, expected ${expected}.`;
+ document.body.textContent = fail_msg;
+ takeScreenshot();
+ }
+
+ testImg.addEventListener('load', secondImageLoaded);
+ testImg.style.width = '33px';
+ }
+
+ testImg.addEventListener('load', firstImageLoaded, {once: true});
+ testImg.setAttribute('srcset', `
+ /images/fail.gif 1000w,
+ /images/green.png 100w,
+ /images/red.png 10w
+ `);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering.html
new file mode 100644
index 0000000000..e972f69c20
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Auto sizes rendering</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#sizes-attributes">
+<link rel="match" href="reference/sizes-auto-rendering-ref.html">
+<script src="/common/rendering-utils.js"></script>
+<script src="/common/reftest-wait.js"></script>
+<img
+ id="testImg"
+ loading="lazy"
+ sizes="auto"
+ width="33"
+ height="13"
+>
+<script>
+ function imageLoaded() {
+ waitForAtLeastOneFrame().then(takeScreenshot);
+ }
+
+ testImg.addEventListener('load', imageLoaded);
+ testImg.setAttribute('srcset', `
+ /images/fail.gif 10w,
+ /images/green.png 100w,
+ /images/red.png 1000w
+ `);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto.html
new file mode 100644
index 0000000000..6c1a741f2b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto.html
@@ -0,0 +1,153 @@
+<!doctype html>
+<title>img parse a sizes attribute: sizes=auto</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#parse-a-sizes-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+img { height: 10px; } /* Keep all images in the viewport, so lazy images load */
+#narrow-div { width: 10px; }
+#wide-div { width: 500px; }
+</style>
+<div id=log></div>
+<script src="support/parse-a-sizes-attribute.js"></script>
+<img srcset='/images/green-1x1.png?ref1 50w, /images/green-16x16.png?ref1 51w' sizes='1px' id=ref1>
+<img srcset='/images/green-1x1.png?ref2 50w, /images/green-16x16.png?ref2 51w' sizes='100vw' id=ref2>
+<div id='narrow-div'></div>
+<div id='wide-div'></div>
+<script>
+"use strict";
+
+// https://github.com/web-platform-tests/rfcs/pull/75
+let async_promise_test = (promise, description) => {
+ async_test(test => {
+ promise(test)
+ .then(() => {test.done();})
+ .catch(test.step_func(error => { throw error; }));
+ }, description);
+};
+
+function check(imgOrPicture) {
+ let img = imgOrPicture;
+ let source;
+ if (imgOrPicture.localName === 'picture') {
+ source = imgOrPicture.firstChild;
+ img = imgOrPicture.lastChild;
+ }
+ const ref = document.getElementById(img.dataset.ref);
+ async_promise_test(async (t) => {
+ let expect = ref.currentSrc;
+ if (expect) {
+ expect = expect.split('?')[0];
+ }
+ if (expect === '' || expect === null || expect === undefined) {
+ assert_unreached('ref currentSrc was ' + format_value(expect));
+ }
+ await new Promise((resolve, reject) => {
+ img.onload = resolve;
+ img.onerror = reject;
+ });
+ t.step(() => {
+ let got = img.currentSrc;
+ assert_greater_than(got.indexOf('?'), -1, 'expected a "?" in currentSrc');
+ got = got.split('?')[0];
+ assert_equals(got, expect);
+ })
+ }, imgOrPicture.outerHTML);
+}
+
+const tests = [
+ // Smoke tests
+ {sizes: '1px', 'data-ref': 'ref1'},
+ {sizes: '', 'data-ref': 'ref2'},
+ // Actual tests
+ {loading: 'lazy', sizes: 'auto', width: '10', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'AUTO', width: '10', 'data-ref': 'ref1'},
+ {loading: 'lazy', width: '10', 'data-ref': 'ref2'}, // no `sizes: 'auto'` -> 100vw
+ {loading: 'lazy', style: 'width: 10px', 'data-ref': 'ref2'}, // no `sizes: 'auto'` -> 100vw
+ {loading: 'lazy', style: 'max-width: 10px', 'data-ref': 'ref2'}, // no `sizes: 'auto'` -> 100vw
+ {loading: 'lazy', style: 'width: 100%; max-width: 10px', 'data-ref': 'ref2'}, // no `sizes: 'auto'` -> 100vw
+ {loading: 'lazy', sizes: 'auto', width: '500', 'data-ref': 'ref2'},
+ {loading: 'lazy', sizes: 'auto', width: '10', style: 'visibility: hidden', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', width: '10', style: 'display: inline', hidden: '', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', width: '0', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'width: 0px', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', 'data-ref': 'ref2'}, // no width -> UA default of 300px
+ {loading: 'lazy', sizes: 'auto, 100vw', width: '10', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: '100vw, auto', width: '10', 'data-ref': 'ref2'},
+ {loading: 'eager', sizes: 'auto', width: '10', 'data-ref': 'ref2'},
+ {loading: 'lazy', sizes: 'auto', width: '100%', parent: 'narrow-div', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', width: '100%', parent: 'wide-div', 'data-ref': 'ref2'},
+ {loading: 'lazy', sizes: 'auto', style: 'height: 10px; aspect-ratio: 10 / 10', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'height: 10px; aspect-ratio: 500 / 10', 'data-ref': 'ref2'},
+ {loading: 'lazy', sizes: 'auto', style: 'min-height: 10px; aspect-ratio: 10 / 10', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'min-height: 10px; aspect-ratio: 500 / 10', 'data-ref': 'ref2'},
+ {loading: 'lazy', sizes: 'auto', style: 'inline-size: 10px', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'min-inline-size: 10px', 'data-ref': 'ref2'}, // no width -> UA default of 300px
+ {loading: 'lazy', sizes: 'auto', style: 'block-size: 10px; aspect-ratio: 10 / 10', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'min-block-size: 10px; aspect-ratio: 10 / 10', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'block-size: 10px; writing-mode: vertical-rl', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'min-block-size: 10px; writing-mode: vertical-rl', 'data-ref': 'ref2'}, // no width -> UA default of 300px
+ {loading: 'lazy', sizes: 'auto', style: 'inline-size: 10px; aspect-ratio: 10/10; writing-mode: vertical-rl', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'min-inline-size: 10px; aspect-ratio: 10/10; writing-mode: vertical-rl', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: '--my-width: 10px; width: var(--my-width)', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'width: calc(5px + 5px)', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'position: absolute; left: 50%; right: 49%', 'data-ref': 'ref2'}, // replaced elements don't get the width resolved from 'left'/'right' per https://drafts.csswg.org/css2/#abs-replaced-width
+];
+
+function test_img(obj, i) {
+ const img = document.createElement('img');
+ let parent = document.body;
+ for (const attr in obj) {
+ if (attr === 'parent') {
+ parent = document.getElementById(obj.parent);
+ } else {
+ img.setAttribute(attr, obj[attr]);
+ }
+ }
+ img.srcset = `/images/green-1x1.png?img${i} 50w, /images/green-16x16.png?img${i} 51w`
+ parent.appendChild(img);
+ check(img);
+}
+
+function test_picture(obj, i) {
+ const picture = document.createElement('picture');
+ const source = document.createElement('source');
+ const img = document.createElement('img');
+ let parent = document.body;
+ for (const attr in obj) {
+ switch (attr) {
+ case 'parent':
+ parent = document.getElementById(obj.parent);
+ break;
+ case 'sizes':
+ // Authors have to specify sizes="auto" on the img to use auto-sizes.
+ if(obj[attr].toLowerCase().startsWith("auto")) {
+ img.setAttribute(attr, obj[attr]);
+ break;
+ }
+ case 'type':
+ case 'media':
+ source.setAttribute(attr, obj[attr]);
+ break;
+ default:
+ img.setAttribute(attr, obj[attr]);
+ break;
+ }
+ }
+ source.srcset = `/images/green-1x1.png?picture${i} 50w, /images/green-16x16.png?picture${i} 51w`;
+ picture.appendChild(source);
+ picture.appendChild(img);
+ parent.appendChild(picture);
+ check(picture);
+}
+
+onload = () => {
+ let i = 0;
+ for (const obj of tests) {
+ i++;
+ test_img(obj, i);
+ test_picture(obj, i);
+ }
+ done();
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001-ref.html
new file mode 100644
index 0000000000..68466ae94d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001-ref.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<title>Test reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<iframe width="500" height="500" srcdoc='<!doctype html><img alt="FAIL" srcset="/images/green-256x256.png 100w" style="max-width: 100%" sizes="10px">'></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001.html
new file mode 100644
index 0000000000..51f8145bf9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Image intrinsic size specified via sizes attribute reacts properly to media changes</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="match" href="sizes-dynamic-001-ref.html">
+<link rel="help" href="https://html.spec.whatwg.org/#sizes-attributes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1149357">
+<script>
+function frameLoaded(frame) {
+ frame.width = "500";
+ let img = frame.contentDocument.querySelector('img');
+
+ // Trigger the viewport resize, which will trigger the image load task.
+ img.offsetWidth;
+
+ // Wait for the image load task to run.
+ setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+}
+</script>
+<iframe onload="frameLoaded(this)" width="200" height="500" srcdoc='<!doctype html><img srcset="/images/green-256x256.png 100w" style="max-width: 100%" sizes="(min-width: 400px) 10px, 20px">'></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-002.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-002.html
new file mode 100644
index 0000000000..6c64b3da39
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-002.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Image intrinsic size specified via sizes attribute reacts properly to media changes in Shadow DOM</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="match" href="sizes-dynamic-001-ref.html">
+<link rel="help" href="https://html.spec.whatwg.org/#sizes-attributes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1149357">
+<script>
+function frameLoaded(frame) {
+ let doc = frame.contentDocument;
+ let shadow = doc.getElementById("host").attachShadow({ mode: "open" });
+
+ let img = doc.createElement("img");
+ img.srcset = "/images/green-256x256.png 100w";
+ img.style.maxWidth = "100%";
+ img.setAttribute("sizes", "(min-width: 400px) 10px, 20px");
+
+ img.onload = function() {
+ img.offsetWidth; // Flush layout.
+
+ frame.width = "500";
+
+ // Trigger the viewport resize, which will trigger the image load task.
+ img.offsetWidth;
+
+ // Wait for the image load task to run.
+ setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+ };
+
+ shadow.appendChild(img);
+}
+</script>
+<iframe onload="frameLoaded(this)" width="200" height="500" srcdoc='<!doctype html><div id="host"></div>'></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/parse-a-sizes-attribute.js b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/parse-a-sizes-attribute.js
new file mode 100644
index 0000000000..62ad00a468
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/parse-a-sizes-attribute.js
@@ -0,0 +1,29 @@
+setup({explicit_done:true});
+
+function check(p, iframe) {
+ var current = p.firstElementChild;
+ var ref_sizes = current.getAttribute('sizes');
+ var expect = current.currentSrc;
+ if (expect) {
+ expect = expect.split('?')[0];
+ }
+ while (current = current.nextElementSibling) {
+ test(function() {
+ if (expect === '' || expect === null || expect === undefined) {
+ assert_unreached('ref currentSrc was ' + format_value(expect));
+ }
+ var got = current.currentSrc;
+ assert_greater_than(got.indexOf('?'), -1, 'expected a "?" in currentSrc');
+ got = got.split('?')[0];
+ assert_equals(got, expect);
+ }, current.outerHTML + ' ref sizes=' + format_value(ref_sizes) + ' (' + iframe.getAttribute('data-desc') + ')');
+ }
+}
+
+onload = function() {
+ var iframe = document.querySelector('iframe');
+ [].forEach.call(iframe.contentDocument.querySelectorAll('p'), function(p) {
+ check(p, iframe);
+ });
+ done();
+}
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/sizes-iframed.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/sizes-iframed.sub.html
new file mode 100644
index 0000000000..1f80ad2f91
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/sizes-iframed.sub.html
@@ -0,0 +1,186 @@
+<!{{GET[doctype]}}>
+<style> img { {{GET[style]}} } </style>
+<!-- First <img> in a <p> is the reference. The following <img>s should be equivalent -->
+<!-- default is 100vw, not 300px -->
+<p>
+<img srcset='/images/green-1x1.png?a1 300w, /images/green-16x16.png?a1 301w' sizes='100vw'>
+<img srcset='/images/green-1x1.png?a2 300w, /images/green-16x16.png?a2 301w'>
+<p>
+<img srcset='/images/green-1x1.png?b1 450w, /images/green-16x16.png?b1 451w' sizes='100vw'>
+<img srcset='/images/green-1x1.png?b2 450w, /images/green-16x16.png?b2 451w'>
+<p>
+<img srcset='/images/green-1x1.png?c1 600w, /images/green-16x16.png?c1 601w' sizes='100vw'>
+<img srcset='/images/green-1x1.png?c2 600w, /images/green-16x16.png?c2 601w'>
+<p>
+<img srcset='/images/green-1x1.png?d1 900w, /images/green-16x16.png?d1 901w' sizes='100vw'>
+<img srcset='/images/green-1x1.png?d2 900w, /images/green-16x16.png?d2 901w'>
+
+<p>
+<img srcset='/images/green-1x1.png?e1 50w, /images/green-16x16.png?e1 51w' sizes='1px'>
+<img srcset='/images/green-1x1.png?e2 50w, /images/green-16x16.png?e2 51w' sizes='0'>
+<img srcset='/images/green-1x1.png?e3 50w, /images/green-16x16.png?e3 51w' sizes='-0'>
+<img srcset='/images/green-1x1.png?e4 50w, /images/green-16x16.png?e4 51w' sizes='+0'>
+<img srcset='/images/green-1x1.png?e5 50w, /images/green-16x16.png?e5 51w' sizes='+1px'>
+<img srcset='/images/green-1x1.png?e6 50w, /images/green-16x16.png?e6 51w' sizes='.1px'>
+<img srcset='/images/green-1x1.png?e7 50w, /images/green-16x16.png?e7 51w' sizes='0.1em'>
+<img srcset='/images/green-1x1.png?e8 50w, /images/green-16x16.png?e8 51w' sizes='0.1ex'>
+<img srcset='/images/green-1x1.png?e9 50w, /images/green-16x16.png?e9 51w' sizes='0.1ch'>
+<img srcset='/images/green-1x1.png?e10 50w, /images/green-16x16.png?e10 51w' sizes='0.1rem'>
+<img srcset='/images/green-1x1.png?e11 50w, /images/green-16x16.png?e11 51w' sizes='0.1vw'>
+<img srcset='/images/green-1x1.png?e12 50w, /images/green-16x16.png?e12 51w' sizes='0.1vh'>
+<img srcset='/images/green-1x1.png?e13 50w, /images/green-16x16.png?e13 51w' sizes='0.1vmin'>
+<img srcset='/images/green-1x1.png?e14 50w, /images/green-16x16.png?e14 51w' sizes='0.1vmax'>
+<img srcset='/images/green-1x1.png?e15 50w, /images/green-16x16.png?e15 51w' sizes='0.1cm'>
+<img srcset='/images/green-1x1.png?e16 50w, /images/green-16x16.png?e16 51w' sizes='1mm'>
+<img srcset='/images/green-1x1.png?e17 50w, /images/green-16x16.png?e17 51w' sizes='1q'>
+<img srcset='/images/green-1x1.png?e18 50w, /images/green-16x16.png?e18 51w' sizes='0.01in'>
+<img srcset='/images/green-1x1.png?e19 50w, /images/green-16x16.png?e19 51w' sizes='0.1pc'>
+<img srcset='/images/green-1x1.png?e20 50w, /images/green-16x16.png?e20 51w' sizes='0.1pt'>
+<img srcset='/images/green-1x1.png?e21 50w, /images/green-16x16.png?e21 51w' sizes='/* */1px/* */'>
+<img srcset='/images/green-1x1.png?e22 50w, /images/green-16x16.png?e22 51w' sizes=' /**/ /**/ 1px /**/ /**/ '>
+<img srcset='/images/green-1x1.png?e23 50w, /images/green-16x16.png?e23 51w' sizes='(),1px'>
+<img srcset='/images/green-1x1.png?e24 50w, /images/green-16x16.png?e24 51w' sizes='x(),1px'>
+<img srcset='/images/green-1x1.png?e25 50w, /images/green-16x16.png?e25 51w' sizes='{},1px'>
+<img srcset='/images/green-1x1.png?e26 50w, /images/green-16x16.png?e26 51w' sizes='[],1px'>
+<img srcset='/images/green-1x1.png?e27 50w, /images/green-16x16.png?e27 51w' sizes='1px,('>
+<img srcset='/images/green-1x1.png?e28 50w, /images/green-16x16.png?e28 51w' sizes='1px,x('>
+<img srcset='/images/green-1x1.png?e29 50w, /images/green-16x16.png?e29 51w' sizes='1px,{'>
+<img srcset='/images/green-1x1.png?e30 50w, /images/green-16x16.png?e30 51w' sizes='1px,['>
+<img srcset='/images/green-1x1.png?e31 50w, /images/green-16x16.png?e31 51w' sizes='\(,1px'>
+<img srcset='/images/green-1x1.png?e32 50w, /images/green-16x16.png?e32 51w' sizes='x\(,1px'>
+<img srcset='/images/green-1x1.png?e33 50w, /images/green-16x16.png?e33 51w' sizes='\{,1px'>
+<img srcset='/images/green-1x1.png?e34 50w, /images/green-16x16.png?e34 51w' sizes='\[,1px'>
+<img srcset='/images/green-1x1.png?e35 50w, /images/green-16x16.png?e35 51w' sizes='1\p\x'>
+<img srcset='/images/green-1x1.png?e36 50w, /images/green-16x16.png?e36 51w' sizes='calc(1px)'>
+<img srcset='/images/green-1x1.png?e36a 50w, /images/green-16x16.png?e36a 51w' sizes='min(1px, 100px)'>
+<img srcset='/images/green-1x1.png?e36b 50w, /images/green-16x16.png?e36b 51w' sizes='min(-100px, 1px)'>
+<img srcset='/images/green-1x1.png?e37 50w, /images/green-16x16.png?e37 51w' sizes='(min-width:0) calc(1px)'>
+<img srcset='/images/green-1x1.png?e37a 50w, /images/green-16x16.png?e37a 51w' sizes='(min-width:0) min(1px, 100px)'>
+<img srcset='/images/green-1x1.png?e37b 50w, /images/green-16x16.png?e37b 51w' sizes='(min-width:0) max(-100px, 1px)'>
+<img srcset='/images/green-1x1.png?e38 50w, /images/green-16x16.png?e38 51w' sizes='(min-width:calc(0)) 1px'>
+<img srcset='/images/green-1x1.png?e39 50w, /images/green-16x16.png?e39 51w' sizes='(min-width:0) 1px, 100vw'>
+<img srcset='/images/green-1x1.png?e40 50w, /images/green-16x16.png?e40 51w' sizes='(min-width:0) 1px, (min-width:0) 100vw, 100vw'>
+<img srcset='/images/green-1x1.png?e41 50w, /images/green-16x16.png?e41 51w' sizes='(min-width:0) 1px'>
+<img srcset='/images/green-1x1.png?e42 50w, /images/green-16x16.png?e42 51w' sizes='not (min-width:0) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e43 50w, /images/green-16x16.png?e43 51w' sizes='(min-width:unknown-mf-value) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e44 50w, /images/green-16x16.png?e44 51w' sizes='not (min-width:unknown-mf-value) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e45 50w, /images/green-16x16.png?e45 51w' sizes='(min-width:-1px) 1px, 100vw'>
+<img srcset='/images/green-1x1.png?e46 50w, /images/green-16x16.png?e46 51w' sizes='not (min-width:-1px) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e47 50w, /images/green-16x16.png?e47 51w' sizes='(unknown-mf-name) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e48 50w, /images/green-16x16.png?e48 51w' sizes='not (unknown-mf-name) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e49 50w, /images/green-16x16.png?e49 51w' sizes='(unknown "general-enclosed") 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e50 50w, /images/green-16x16.png?e50 51w' sizes='not (unknown "general-enclosed") 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e51 50w, /images/green-16x16.png?e51 51w' sizes='unknown-general-enclosed(foo) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e52 50w, /images/green-16x16.png?e52 51w' sizes='not unknown-general-enclosed(foo) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e53 50w, /images/green-16x16.png?e53 51w' sizes='print 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e54 50w, /images/green-16x16.png?e54 51w' sizes='not print 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e55 50w, /images/green-16x16.png?e55 51w' sizes='unknown-media-type 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e56 50w, /images/green-16x16.png?e56 51w' sizes='not unknown-media-type 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e57 50w, /images/green-16x16.png?e57 51w' sizes='(min-width:0) or (min-width:0) 1px'>
+<img srcset='/images/green-1x1.png?e58 50w, /images/green-16x16.png?e58 51w' sizes='(min-width:0) or (unknown-mf-name) 1px'>
+<img srcset='/images/green-1x1.png?e59 50w, /images/green-16x16.png?e59 51w' sizes='(min-width:0) or (min-width:unknown-mf-value) 1px'>
+<img srcset='/images/green-1x1.png?e60 50w, /images/green-16x16.png?e60 51w' sizes='(min-width:0) or (min-width:-1px) 1px'>
+<img srcset='/images/green-1x1.png?e61 50w, /images/green-16x16.png?e61 51w' sizes='(min-width:0) or (unknown "general-enclosed") 1px'>
+<img srcset='/images/green-1x1.png?e62 50w, /images/green-16x16.png?e62 51w' sizes='(min-width:0) or unknown-general-enclosed(foo) 1px'>
+<img srcset='/images/green-1x1.png?e63 50w, /images/green-16x16.png?e63 51w' sizes='(min-width:0) or (]) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e64 50w, /images/green-16x16.png?e64 51w' sizes='(min-width:0) or unknown-media-type 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e65 50w, /images/green-16x16.png?e65 51w' sizes='(123) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e66 50w, /images/green-16x16.png?e66 51w' sizes='not (123) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e67 50w, /images/green-16x16.png?e67 51w' sizes='(!) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e68 50w, /images/green-16x16.png?e68 51w' sizes='not (!) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e69 50w, /images/green-16x16.png?e69 51w' sizes='! 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e70 50w, /images/green-16x16.png?e70 51w' sizes='not ! 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e71 50w, /images/green-16x16.png?e71 51w' sizes='(]) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e72 50w, /images/green-16x16.png?e72 51w' sizes='not (]) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e73 50w, /images/green-16x16.png?e73 51w' sizes='] 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e74 50w, /images/green-16x16.png?e74 51w' sizes='not ] 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e75 50w, /images/green-16x16.png?e75 51w' sizes='(}) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e76 50w, /images/green-16x16.png?e76 51w' sizes='not (}) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e77 50w, /images/green-16x16.png?e77 51w' sizes='} 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e78 50w, /images/green-16x16.png?e78 51w' sizes='not } 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e79 50w, /images/green-16x16.png?e79 51w' sizes=') 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e80 50w, /images/green-16x16.png?e80 51w' sizes='not ) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e81 50w, /images/green-16x16.png?e81 51w' sizes='(;) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e82 50w, /images/green-16x16.png?e82 51w' sizes='not (;) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e83 50w, /images/green-16x16.png?e83 51w' sizes='(.) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e84 50w, /images/green-16x16.png?e84 51w' sizes='not (.) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e85 50w, /images/green-16x16.png?e85 51w' sizes='; 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e86 50w, /images/green-16x16.png?e86 51w' sizes='not ; 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e87 50w, /images/green-16x16.png?e87 51w' sizes=', 1px'>
+<img srcset='/images/green-1x1.png?e88 50w, /images/green-16x16.png?e88 51w' sizes='1px,'>
+<img srcset='/images/green-1x1.png?e89 50w, /images/green-16x16.png?e89 51w' sizes='(min-width:0) 1px,'>
+<img srcset='/images/green-1x1.png?e90 50w, /images/green-16x16.png?e90 51w' sizes='-0e-0px'>
+<img srcset='/images/green-1x1.png?e91 50w, /images/green-16x16.png?e91 51w' sizes='+0.11e+01px'>
+<img srcset='/images/green-1x1.png?e92 50w, /images/green-16x16.png?e92 51w' sizes='0.2e1px'>
+<img srcset='/images/green-1x1.png?e93 50w, /images/green-16x16.png?e93 51w' sizes='0.3E1px'>
+<img srcset='/images/green-1x1.png?e94 50w, /images/green-16x16.png?e94 51w' sizes='.4E1px'>
+<img srcset='/images/green-1x1.png?e95 50w, /images/green-16x16.png?e95 51w' sizes='all 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e96 50w, /images/green-16x16.png?e96 51w' sizes='all and (min-width:0) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e97 50w, /images/green-16x16.png?e97 51w' sizes='min-width:0 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e98 50w, /images/green-16x16.png?e98 51w' sizes='1px, 100vw'>
+<img srcset='/images/green-1x1.png?e99 50w, /images/green-16x16.png?e99 51w' sizes='1px, (min-width:0) 100vw'>
+<img srcset='/images/green-1x1.png?e100 50w, /images/green-16x16.png?e100 51w' sizes='1px, foo bar'>
+<img srcset='/images/green-1x1.png?e101 50w, /images/green-16x16.png?e101 51w' sizes='(min-width:0) 1px, foo bar'>
+<img srcset='/images/green-1x1.png?e102 50w, /images/green-16x16.png?e102 51w' sizes='("grammar does not match") 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e103 50w, /images/green-16x16.png?e103 51w' sizes='not ("grammar does not match") 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e104 50w, /images/green-16x16.png?e104 51w' sizes='(unknown-general-enclosed !) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e105 50w, /images/green-16x16.png?e105 51w' sizes='not (unknown-general-enclosed !) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e106 50w, /images/green-16x16.png?e106 51w' sizes='(min-width:0) or (unknown-general-enclosed !) 1px'>
+<img srcset='/images/green-1x1.png?e107 50w, /images/green-16x16.png?e107 51w' sizes='not ((min-width:0) or (unknown "general-enclosed")) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e108 50w, /images/green-16x16.png?e108 51w' sizes='(max-width:0) or (unknown-general-enclosed !) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e109 50w, /images/green-16x16.png?e109 51w' sizes='not ((max-width:0) or (unknown "general-enclosed")) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?f48 50w, /images/green-16x16.png?f48 51w' sizes='calc(1px'>
+<img srcset='/images/green-1x1.png?f48a 50w, /images/green-16x16.png?f48a 51w' sizes='min(1px, 200vw'>
+<img srcset='/images/green-1x1.png?f48b 50w, /images/green-16x16.png?f48b 51w' sizes='max(-200vw, 1px'>
+<img srcset='/images/green-1x1.png?f49 50w, /images/green-16x16.png?f49 51w' sizes='(min-width:0) calc(1px'>
+<img srcset='/images/green-1x1.png?f49a 50w, /images/green-16x16.png?f49a 51w' sizes='(min-width:0) min(1px, 200vw'>
+<img srcset='/images/green-1x1.png?f49b 50w, /images/green-16x16.png?f49b 51w' sizes='(min-width:0) max(-200vw, 1px'>
+
+<p>
+<img srcset='/images/green-1x1.png?f1 50w, /images/green-16x16.png?f1 51w' sizes='100vw'>
+<img srcset='/images/green-1x1.png?f2 50w, /images/green-16x16.png?f2 51w' sizes=''>
+<img srcset='/images/green-1x1.png?f3 50w, /images/green-16x16.png?f3 51w' sizes=','>
+<img srcset='/images/green-1x1.png?f4 50w, /images/green-16x16.png?f4 51w' sizes='-1px'>
+<img srcset='/images/green-1x1.png?f5 50w, /images/green-16x16.png?f5 51w' sizes='1'>
+<img srcset='/images/green-1x1.png?f6 50w, /images/green-16x16.png?f6 51w' sizes='0.1%'>
+<img srcset='/images/green-1x1.png?f7 50w, /images/green-16x16.png?f7 51w' sizes='0.1deg'>
+<img srcset='/images/green-1x1.png?f8 50w, /images/green-16x16.png?f8 51w' sizes='0.1grad'>
+<img srcset='/images/green-1x1.png?f9 50w, /images/green-16x16.png?f9 51w' sizes='0.1rad'>
+<img srcset='/images/green-1x1.png?f10 50w, /images/green-16x16.png?f10 51w' sizes='0.1turn'>
+<img srcset='/images/green-1x1.png?f11 50w, /images/green-16x16.png?f11 51w' sizes='0.1s'>
+<img srcset='/images/green-1x1.png?f12 50w, /images/green-16x16.png?f12 51w' sizes='0.1ms'>
+<img srcset='/images/green-1x1.png?f13 50w, /images/green-16x16.png?f13 51w' sizes='0.1Hz'>
+<img srcset='/images/green-1x1.png?f14 50w, /images/green-16x16.png?f14 51w' sizes='0.1kHz'>
+<img srcset='/images/green-1x1.png?f15 50w, /images/green-16x16.png?f15 51w' sizes='0.1dpi'>
+<img srcset='/images/green-1x1.png?f16 50w, /images/green-16x16.png?f16 51w' sizes='0.1dpcm'>
+<img srcset='/images/green-1x1.png?f17 50w, /images/green-16x16.png?f17 51w' sizes='0.1dppx'>
+<img srcset='/images/green-1x1.png?f17a 50w, /images/green-16x16.png?f17a 51w' sizes='0.1x'>
+<img srcset='/images/green-1x1.png?f18 50w, /images/green-16x16.png?f18 51w' data-foo='1px' sizes='attr(data-foo, length, 1px)'>
+<img srcset='/images/green-1x1.png?f19 50w, /images/green-16x16.png?f19 51w' data-foo='1' sizes='attr(data-foo, px, 1px)'>
+<img srcset='/images/green-1x1.png?f20 50w, /images/green-16x16.png?f20 51w' sizes='toggle(1px)'>
+<img srcset='/images/green-1x1.png?f21 50w, /images/green-16x16.png?f21 51w' sizes='inherit'>
+<img srcset='/images/green-1x1.png?f23 50w, /images/green-16x16.png?f23 51w' sizes='initial'>
+<img srcset='/images/green-1x1.png?f24 50w, /images/green-16x16.png?f24 51w' sizes='unset'>
+<img srcset='/images/green-1x1.png?f25 50w, /images/green-16x16.png?f25 51w' sizes='default'>
+<img srcset='/images/green-1x1.png?f26 50w, /images/green-16x16.png?f26 51w' sizes='1/* */px'>
+<img srcset='/images/green-1x1.png?f27 50w, /images/green-16x16.png?f27 51w' sizes='1p/* */x'>
+<img srcset='/images/green-1x1.png?f28 50w, /images/green-16x16.png?f28 51w' sizes='-/**/0'>
+<img srcset='/images/green-1x1.png?f29 50w, /images/green-16x16.png?f29 51w' sizes='((),1px'>
+<img srcset='/images/green-1x1.png?f30 50w, /images/green-16x16.png?f30 51w' sizes='x(x(),1px'>
+<img srcset='/images/green-1x1.png?f31 50w, /images/green-16x16.png?f31 51w' sizes='{{},1px'>
+<img srcset='/images/green-1x1.png?f32 50w, /images/green-16x16.png?f32 51w' sizes='[[],1px'>
+<img srcset='/images/green-1x1.png?f33 50w, /images/green-16x16.png?f33 51w' sizes='1px !important'>
+<img srcset='/images/green-1x1.png?f34 50w, /images/green-16x16.png?f34 51w' sizes='\1px'>
+<img srcset='/images/green-1x1.png?f35 50w, /images/green-16x16.png?f35 51w' sizes='all 1px'>
+<img srcset='/images/green-1x1.png?f36 50w, /images/green-16x16.png?f36 51w' sizes='all and (min-width:0) 1px'>
+<img srcset='/images/green-1x1.png?f37 50w, /images/green-16x16.png?f37 51w' sizes='min-width:0 1px'>
+<img srcset='/images/green-1x1.png?f38 50w, /images/green-16x16.png?f38 51w' sizes='100vw, 1px'>
+<img srcset='/images/green-1x1.png?f39 50w, /images/green-16x16.png?f39 51w' sizes='100vw, (min-width:0) 1px'>
+<img srcset='/images/green-1x1.png?f40 50w, /images/green-16x16.png?f40 51w' sizes='foo bar'>
+<img srcset='/images/green-1x1.png?f41 50w, /images/green-16x16.png?f41 51w' sizes='foo-bar'>
+<img srcset='/images/green-1x1.png?f42 50w, /images/green-16x16.png?f42 51w' sizes='(min-width:0) 1px foo bar'>
+<img srcset='/images/green-1x1.png?f43 50w, /images/green-16x16.png?f43 51w' sizes='(min-width:0) 0.1%'>
+<img srcset='/images/green-1x1.png?f44 50w, /images/green-16x16.png?f44 51w' sizes='(min-width:0) 1'>
+<img srcset='/images/green-1x1.png?f45 50w, /images/green-16x16.png?f45 51w' sizes='-1e0px'>
+<img srcset='/images/green-1x1.png?f46 50w, /images/green-16x16.png?f46 51w' sizes='1e1.5px'>
+<img srcset='/images/green-1x1.png?f47 50w, /images/green-16x16.png?f47 51w' style='--foo: 1px' sizes='var(--foo)'>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/source-media-outside-doc.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/source-media-outside-doc.html
new file mode 100644
index 0000000000..5997e14e4b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/source-media-outside-doc.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>Image source selection using media queries is performed for img elements outside the document</title>
+<link rel="help" href="https://html.spec.whatwg.org/#reacting-to-environment-changes">
+<link rel="help" href="https://html.spec.whatwg.org/#reacting-to-dom-mutations">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe width="350" height="100" onload="async_test(this.contentWindow.run)" srcdoc="
+<!DOCTYPE html>
+<script>
+const { assert_equals } = parent;
+const iframe = parent.document.querySelector('iframe');
+
+function run(t) {
+ const picture = document.createElement('picture');
+
+ const source1 = document.createElement('source');
+ source1.setAttribute('media', '(min-width: 300px)');
+ source1.setAttribute('srcset', 'data:,a');
+ picture.append(source1);
+
+ const source2 = document.createElement('source');
+ source2.setAttribute('media', '(min-width: 200px)');
+ source2.setAttribute('srcset', 'data:,b');
+ picture.append(source2);
+
+ const img = document.createElement('img');
+ img.src = 'data:,c';
+ picture.append(img);
+
+ queueMicrotask(t.step_func(function() {
+ assert_equals(img.currentSrc, 'data:,a', 'Initial currentSrc value');
+ matchMedia(source1.media).addEventListener(
+ 'change',
+ function() {
+ queueMicrotask(t.step_func(function() {
+ assert_equals(img.currentSrc, 'data:,b', 'After MQ change');
+ img.remove();
+ queueMicrotask(t.step_func(function() {
+ assert_equals(img.currentSrc, 'data:,c', 'After removing img');
+ t.done();
+ }));
+ }));
+ },
+ { once: true }
+ );
+ iframe.width = 250;
+ }));
+}
+</script>
+"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/avoid-reload-on-resize.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/avoid-reload-on-resize.html
new file mode 100644
index 0000000000..52366dcaa7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/avoid-reload-on-resize.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>Avoid srcset image reloads when viewport resizes</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({single_test:true});
+const image_was_loaded = () => {
+ const iframe = document.getElementById("iframe");
+ // Resize the iframe
+ iframe.width="400";
+ // Wait 500 ms
+ step_timeout(() => {
+ // Check that the iframe only loaded a single resource
+ const entries = iframe.contentWindow.performance.getEntriesByType("resource");
+ assert_equals(entries.length, 1);
+ done();
+ }, 500);
+}
+</script>
+<iframe id=iframe width="401" src="resources/resized.html" onload="image_was_loaded()"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/common.js b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/common.js
new file mode 100644
index 0000000000..d4d2c7534c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/common.js
@@ -0,0 +1,25 @@
+setup({explicit_done:true});
+
+function check(img) {
+ var name = format_value(img.getAttribute('srcset'));
+ if (img.hasAttribute('sizes')) {
+ name += ' sizes=' + format_value(img.getAttribute('sizes'));
+ }
+ if (img.hasAttribute('data-desc')) {
+ name += ' (' + img.getAttribute('data-desc') + ')';
+ }
+ test(function() {
+ var expect = img.dataset.expect;
+ if ('resolve' in img.dataset) {
+ var a = document.createElement('a');
+ a.href = expect;
+ expect = a.href;
+ }
+ assert_equals(img.currentSrc, expect);
+ }, name);
+}
+
+onload = function() {
+ [].forEach.call(document.images, check);
+ done();
+};
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/parse-a-srcset-attribute.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/parse-a-srcset-attribute.html
new file mode 100644
index 0000000000..ce1e4cebe5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/parse-a-srcset-attribute.html
@@ -0,0 +1,245 @@
+<!doctype html>
+<title>img parse a srcset attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=common.js></script>
+<div id=log></div>
+<!-- splitting loop -->
+<img srcset='' data-expect=''>
+<img srcset=',' data-expect=''>
+<img srcset=',,,' data-expect=''>
+<img srcset=' data:,a 1x ' data-expect='data:,a'>
+<img srcset='&#x9;&#x9;data:,a&#x9;&#x9;1x&#x9;&#x9;' data-expect='data:,a'>
+<img srcset='&#xA;&#xA;data:,a&#xA;&#xA;1x&#xA;&#xA;' data-expect='data:,a'>
+<img srcset='&#xB;&#xB;data:,a&#xB;&#xB;1x&#xB;&#xB;' data-expect='&#xB;&#xB;data:,a&#xB;&#xB;1x&#xB;&#xB;' data-resolve>
+<img srcset='&#xC;&#xC;data:,a&#xC;&#xC;1x&#xC;&#xC;' data-expect='data:,a'>
+<img srcset='&#xD;&#xD;data:,a&#xD;&#xD;1x&#xD;&#xD;' data-expect='data:,a'>
+<img srcset='&#xE;&#xE;data:,a&#xE;&#xE;1x&#xE;&#xE;' data-expect='&#xE;&#xE;data:,a&#xE;&#xE;1x&#xE;&#xE;' data-resolve>
+<img srcset='&#xF;&#xF;data:,a&#xF;&#xF;1x&#xF;&#xF;' data-expect='&#xF;&#xF;data:,a&#xF;&#xF;1x&#xF;&#xF;' data-resolve>
+<img srcset='&#x10;&#x10;data:,a&#x10;&#x10;1x&#x10;&#x10;' data-expect='&#x10;&#x10;data:,a&#x10;&#x10;1x&#x10;&#x10;' data-resolve>
+<img srcset='data:,a' data-expect='data:,a'>
+<img srcset='data:,a ' data-expect='data:,a'>
+<img srcset='data:,a ,' data-expect='data:,a'>
+<img srcset='data:,a,' data-expect='data:,a'>
+<img srcset='data:,a, ' data-expect='data:,a'>
+<img srcset='data:,a,,,' data-expect='data:,a'>
+<img srcset='data:,a,, , ' data-expect='data:,a'>
+<img srcset=' data:,a' data-expect='data:,a'>
+<img srcset=',,,data:,a' data-expect='data:,a'>
+<img srcset=' , ,,data:,a' data-expect='data:,a'>
+<img srcset='&nbsp;data:,a' data-expect='&nbsp;data:,a' data-resolve>
+<img srcset='data:,a&nbsp;' data-expect='data:,a&nbsp;' data-resolve>
+<!-- descriptor tokenizer -->
+<img srcset='data:,a 1x' data-expect='data:,a'>
+<img srcset='data:,a 1x ' data-expect='data:,a'>
+<img srcset='data:,a 1x,' data-expect='data:,a'>
+<img srcset='data:,a ( , data:,b 1x, ), data:,c' data-expect='data:,c'>
+<img srcset='data:,a ((( , data:,b 1x, ), data:,c' data-expect='data:,c'>
+<img srcset='data:,a [ , data:,b 1x, ], data:,c' data-expect='data:,b'>
+<img srcset='data:,a { , data:,b 1x, }, data:,c' data-expect='data:,b'>
+<img srcset='data:,a " , data:,b 1x, ", data:,c' data-expect='data:,b'>
+<img srcset='data:,a \,data:;\,b, data:,c' data-expect='data:;\,b'>
+<img srcset='data:,a, data:,b (' data-expect='data:,a'>
+<img srcset='data:,a, data:,b ( ' data-expect='data:,a'>
+<img srcset='data:,a, data:,b (,' data-expect='data:,a'>
+<img srcset='data:,a, data:,b (x' data-expect='data:,a'>
+<img srcset='data:,a, data:,b ()' data-expect='data:,a'>
+<img srcset='data:,a (, data:,b' data-expect=''>
+<img srcset='data:,a /*, data:,b, data:,c */' data-expect='data:,b'>
+<img srcset='data:,a //, data:,b' data-expect='data:,b'>
+<!-- descriptor parser -->
+<img srcset='data:,a foo' data-expect=''>
+<img srcset='data:,a foo foo' data-expect=''>
+<img srcset='data:,a foo 1x' data-expect=''>
+<img srcset='data:,a foo 1x foo' data-expect=''>
+<img srcset='data:,a foo 1w' data-expect=''>
+<img srcset='data:,a foo 1w foo' data-expect=''>
+<img srcset='data:,a 1x 1x' data-expect=''>
+<img srcset='data:,a 1w 1w' data-expect=''>
+<img srcset='data:,a 1w 1x' data-expect=''>
+<img srcset='data:,a 1x 1w' data-expect=''>
+<img srcset='data:,a 1w 1h' data-expect='data:,a'><!-- should fail for x-only impl -->
+<img srcset='data:,a 1h 1w' data-expect='data:,a'><!-- should fail for x-only impl -->
+<img srcset='data:,a 1h 1h' data-expect=''>
+<img srcset='data:,a 1h 1x' data-expect=''>
+<img srcset='data:,a 1h 1w 1x' data-expect=''>
+<img srcset='data:,a 1x 1w 1h' data-expect=''>
+<img srcset='data:,a 1w' data-expect='data:,a'><!-- should fail for x-only impl -->
+<img srcset='data:,a 1h' data-expect=''>
+<img srcset='data:,a 1h foo' data-expect=''>
+<img srcset='data:,a foo 1h' data-expect=''>
+<img srcset='data:,a 0w' data-expect=''>
+<img srcset='data:,a -1w' data-expect=''>
+<img srcset='data:,a 1w -1w' data-expect=''>
+<img srcset='data:,a 1.0w' data-expect=''>
+<img srcset='data:,a 1w 1.0w' data-expect=''>
+<img srcset='data:,a 1e0w' data-expect=''>
+<img srcset='data:,a 1w 1e0w' data-expect=''>
+<img srcset='data:,a 1www' data-expect=''>
+<img srcset='data:,a 1w 1www' data-expect=''>
+<img srcset='data:,a +1w' data-expect=''>
+<img srcset='data:,a 1w +1w' data-expect=''>
+<img srcset='data:,a 1W' data-expect=''>
+<img srcset='data:,a 1w 1W' data-expect=''>
+<img srcset='data:,a Infinityw' data-expect=''>
+<img srcset='data:,a 1w Infinityw' data-expect=''>
+<img srcset='data:,a NaNw' data-expect=''>
+<img srcset='data:,a 1w NaNw' data-expect=''>
+<img srcset='data:,a 0x1w' data-expect=''>
+<img srcset='data:,a 0X1w' data-expect=''>
+<img srcset='data:,a 1&#x1;w' data-expect='' data-desc='trailing U+0001'>
+<img srcset='data:,a 1&nbsp;w' data-expect='' data-desc='trailing U+00A0'>
+<img srcset='data:,a 1&#x1680;w' data-expect='' data-desc='trailing U+1680'>
+<img srcset='data:,a 1&#x2000;w' data-expect='' data-desc='trailing U+2000'>
+<img srcset='data:,a 1&#x2001;w' data-expect='' data-desc='trailing U+2001'>
+<img srcset='data:,a 1&#x2002;w' data-expect='' data-desc='trailing U+2002'>
+<img srcset='data:,a 1&#x2003;w' data-expect='' data-desc='trailing U+2003'>
+<img srcset='data:,a 1&#x2004;w' data-expect='' data-desc='trailing U+2004'>
+<img srcset='data:,a 1&#x2005;w' data-expect='' data-desc='trailing U+2005'>
+<img srcset='data:,a 1&#x2006;w' data-expect='' data-desc='trailing U+2006'>
+<img srcset='data:,a 1&#x2007;w' data-expect='' data-desc='trailing U+2007'>
+<img srcset='data:,a 1&#x2008;w' data-expect='' data-desc='trailing U+2008'>
+<img srcset='data:,a 1&#x2009;w' data-expect='' data-desc='trailing U+2009'>
+<img srcset='data:,a 1&#x200A;w' data-expect='' data-desc='trailing U+200A'>
+<img srcset='data:,a 1&#x200C;w' data-expect='' data-desc='trailing U+200C'>
+<img srcset='data:,a 1&#x200D;w' data-expect='' data-desc='trailing U+200D'>
+<img srcset='data:,a 1&#x202F;w' data-expect='' data-desc='trailing U+202F'>
+<img srcset='data:,a 1&#x205F;w' data-expect='' data-desc='trailing U+205F'>
+<img srcset='data:,a 1&#x3000;w' data-expect='' data-desc='trailing U+3000'>
+<img srcset='data:,a 1&#xFEFF;w' data-expect='' data-desc='trailing U+FEFF'>
+<img srcset='data:,a &#x1;1w' data-expect='' data-desc='leading U+0001'>
+<img srcset='data:,a &nbsp;1w' data-expect='' data-desc='leading U+00A0'>
+<img srcset='data:,a &#x1680;1w' data-expect='' data-desc='leading U+1680'>
+<img srcset='data:,a &#x2000;1w' data-expect='' data-desc='leading U+2000'>
+<img srcset='data:,a &#x2001;1w' data-expect='' data-desc='leading U+2001'>
+<img srcset='data:,a &#x2002;1w' data-expect='' data-desc='leading U+2002'>
+<img srcset='data:,a &#x2003;1w' data-expect='' data-desc='leading U+2003'>
+<img srcset='data:,a &#x2004;1w' data-expect='' data-desc='leading U+2004'>
+<img srcset='data:,a &#x2005;1w' data-expect='' data-desc='leading U+2005'>
+<img srcset='data:,a &#x2006;1w' data-expect='' data-desc='leading U+2006'>
+<img srcset='data:,a &#x2007;1w' data-expect='' data-desc='leading U+2007'>
+<img srcset='data:,a &#x2008;1w' data-expect='' data-desc='leading U+2008'>
+<img srcset='data:,a &#x2009;1w' data-expect='' data-desc='leading U+2009'>
+<img srcset='data:,a &#x200A;1w' data-expect='' data-desc='leading U+200A'>
+<img srcset='data:,a &#x200C;1w' data-expect='' data-desc='leading U+200C'>
+<img srcset='data:,a &#x200D;1w' data-expect='' data-desc='leading U+200D'>
+<img srcset='data:,a &#x202F;1w' data-expect='' data-desc='leading U+202F'>
+<img srcset='data:,a &#x205F;1w' data-expect='' data-desc='leading U+205F'>
+<img srcset='data:,a &#x3000;1w' data-expect='' data-desc='leading U+3000'>
+<img srcset='data:,a &#xFEFF;1w' data-expect='' data-desc='leading U+FEFF'>
+<img srcset='data:,a 0x' data-expect='data:,a'>
+<img srcset='data:,a -0x' data-expect='data:,a'>
+<img srcset='data:,a 1x -0x' data-expect=''>
+<img srcset='data:,a -1x' data-expect=''>
+<img srcset='data:,a 1x -1x' data-expect=''>
+<img srcset='data:,a 1e0x' data-expect='data:,a'>
+<img srcset='data:,a 1E0x' data-expect='data:,a'>
+<img srcset='data:,a 1e-1x' data-expect='data:,a'>
+<img srcset='data:,a 1.5e1x' data-expect='data:,a'>
+<img srcset='data:,a -x' data-expect=''>
+<img srcset='data:,a .x' data-expect=''>
+<img srcset='data:,a -.x' data-expect=''>
+<img srcset='data:,a 1.x' data-expect=''>
+<img srcset='data:,a .5x' data-expect='data:,a'>
+<img srcset='data:,a .5e1x' data-expect='data:,a'>
+<img srcset='data:,a 1x 1.5e1x' data-expect=''>
+<img srcset='data:,a 1x 1e1.5x' data-expect=''>
+<img srcset='data:,a 1.0x' data-expect='data:,a'>
+<img srcset='data:,a 1x 1.0x' data-expect=''>
+<img srcset='data:,a +1x' data-expect=''>
+<img srcset='data:,a 1X' data-expect=''>
+<img srcset='data:,a Infinityx' data-expect=''>
+<img srcset='data:,a NaNx' data-expect=''>
+<img srcset='data:,a 0x1x' data-expect=''>
+<img srcset='data:,a 0X1x' data-expect=''>
+<img srcset='data:,a 1&#x1;x' data-expect='' data-desc='trailing U+0001'>
+<img srcset='data:,a 1&nbsp;x' data-expect='' data-desc='trailing U+00A0'>
+<img srcset='data:,a 1&#x1680;x' data-expect='' data-desc='trailing U+1680'>
+<img srcset='data:,a 1&#x2000;x' data-expect='' data-desc='trailing U+2000'>
+<img srcset='data:,a 1&#x2001;x' data-expect='' data-desc='trailing U+2001'>
+<img srcset='data:,a 1&#x2002;x' data-expect='' data-desc='trailing U+2002'>
+<img srcset='data:,a 1&#x2003;x' data-expect='' data-desc='trailing U+2003'>
+<img srcset='data:,a 1&#x2004;x' data-expect='' data-desc='trailing U+2004'>
+<img srcset='data:,a 1&#x2005;x' data-expect='' data-desc='trailing U+2005'>
+<img srcset='data:,a 1&#x2006;x' data-expect='' data-desc='trailing U+2006'>
+<img srcset='data:,a 1&#x2007;x' data-expect='' data-desc='trailing U+2007'>
+<img srcset='data:,a 1&#x2008;x' data-expect='' data-desc='trailing U+2008'>
+<img srcset='data:,a 1&#x2009;x' data-expect='' data-desc='trailing U+2009'>
+<img srcset='data:,a 1&#x200A;x' data-expect='' data-desc='trailing U+200A'>
+<img srcset='data:,a 1&#x200C;x' data-expect='' data-desc='trailing U+200C'>
+<img srcset='data:,a 1&#x200D;x' data-expect='' data-desc='trailing U+200D'>
+<img srcset='data:,a 1&#x202F;x' data-expect='' data-desc='trailing U+202F'>
+<img srcset='data:,a 1&#x205F;x' data-expect='' data-desc='trailing U+205F'>
+<img srcset='data:,a 1&#x3000;x' data-expect='' data-desc='trailing U+3000'>
+<img srcset='data:,a 1&#xFEFF;x' data-expect='' data-desc='trailing U+FEFF'>
+<img srcset='data:,a &#x1;1x' data-expect='' data-desc='leading U+0001'>
+<img srcset='data:,a &nbsp;1x' data-expect='' data-desc='leading U+00A0'>
+<img srcset='data:,a &#x1680;1x' data-expect='' data-desc='leading U+1680'>
+<img srcset='data:,a &#x2000;1x' data-expect='' data-desc='leading U+2000'>
+<img srcset='data:,a &#x2001;1x' data-expect='' data-desc='leading U+2001'>
+<img srcset='data:,a &#x2002;1x' data-expect='' data-desc='leading U+2002'>
+<img srcset='data:,a &#x2003;1x' data-expect='' data-desc='leading U+2003'>
+<img srcset='data:,a &#x2004;1x' data-expect='' data-desc='leading U+2004'>
+<img srcset='data:,a &#x2005;1x' data-expect='' data-desc='leading U+2005'>
+<img srcset='data:,a &#x2006;1x' data-expect='' data-desc='leading U+2006'>
+<img srcset='data:,a &#x2007;1x' data-expect='' data-desc='leading U+2007'>
+<img srcset='data:,a &#x2008;1x' data-expect='' data-desc='leading U+2008'>
+<img srcset='data:,a &#x2009;1x' data-expect='' data-desc='leading U+2009'>
+<img srcset='data:,a &#x200A;1x' data-expect='' data-desc='leading U+200A'>
+<img srcset='data:,a &#x200C;1x' data-expect='' data-desc='leading U+200C'>
+<img srcset='data:,a &#x200D;1x' data-expect='' data-desc='leading U+200D'>
+<img srcset='data:,a &#x202F;1x' data-expect='' data-desc='leading U+202F'>
+<img srcset='data:,a &#x205F;1x' data-expect='' data-desc='leading U+205F'>
+<img srcset='data:,a &#x3000;1x' data-expect='' data-desc='leading U+3000'>
+<img srcset='data:,a &#xFEFF;1x' data-expect='' data-desc='leading U+FEFF'>
+<img srcset='data:,a 1w 0h' data-expect=''>
+<img srcset='data:,a 1w -1h' data-expect=''>
+<img srcset='data:,a 1w 1.0h' data-expect=''>
+<img srcset='data:,a 1w 1e0h' data-expect=''>
+<img srcset='data:,a 1w 1hhh' data-expect=''>
+<img srcset='data:,a 1w +1h' data-expect=''>
+<img srcset='data:,a 1w 1H' data-expect=''>
+<img srcset='data:,a 1w Infinityh' data-expect=''>
+<img srcset='data:,a 1w NaNh' data-expect=''>
+<img srcset='data:,a 0x1h' data-expect=''>
+<img srcset='data:,a 0X1h' data-expect=''>
+<img srcset='data:,a 1w 1&#x1;h' data-expect='' data-desc='trailing U+0001'>
+<img srcset='data:,a 1w 1&nbsp;h' data-expect='' data-desc='trailing U+00A0'>
+<img srcset='data:,a 1w 1&#x1680;h' data-expect='' data-desc='trailing U+1680'>
+<img srcset='data:,a 1w 1&#x2000;h' data-expect='' data-desc='trailing U+2000'>
+<img srcset='data:,a 1w 1&#x2001;h' data-expect='' data-desc='trailing U+2001'>
+<img srcset='data:,a 1w 1&#x2002;h' data-expect='' data-desc='trailing U+2002'>
+<img srcset='data:,a 1w 1&#x2003;h' data-expect='' data-desc='trailing U+2003'>
+<img srcset='data:,a 1w 1&#x2004;h' data-expect='' data-desc='trailing U+2004'>
+<img srcset='data:,a 1w 1&#x2005;h' data-expect='' data-desc='trailing U+2005'>
+<img srcset='data:,a 1w 1&#x2006;h' data-expect='' data-desc='trailing U+2006'>
+<img srcset='data:,a 1w 1&#x2007;h' data-expect='' data-desc='trailing U+2007'>
+<img srcset='data:,a 1w 1&#x2008;h' data-expect='' data-desc='trailing U+2008'>
+<img srcset='data:,a 1w 1&#x2009;h' data-expect='' data-desc='trailing U+2009'>
+<img srcset='data:,a 1w 1&#x200A;h' data-expect='' data-desc='trailing U+200A'>
+<img srcset='data:,a 1w 1&#x200C;h' data-expect='' data-desc='trailing U+200C'>
+<img srcset='data:,a 1w 1&#x200D;h' data-expect='' data-desc='trailing U+200D'>
+<img srcset='data:,a 1w 1&#x202F;h' data-expect='' data-desc='trailing U+202F'>
+<img srcset='data:,a 1w 1&#x205F;h' data-expect='' data-desc='trailing U+205F'>
+<img srcset='data:,a 1w 1&#x3000;h' data-expect='' data-desc='trailing U+3000'>
+<img srcset='data:,a 1w 1&#xFEFF;h' data-expect='' data-desc='trailing U+FEFF'>
+<img srcset='data:,a 1w &#x1;1h' data-expect='' data-desc='leading U+0001'>
+<img srcset='data:,a 1w &nbsp;1h' data-expect='' data-desc='leading U+00A0'>
+<img srcset='data:,a 1w &#x1680;1h' data-expect='' data-desc='leading U+1680'>
+<img srcset='data:,a 1w &#x2000;1h' data-expect='' data-desc='leading U+2000'>
+<img srcset='data:,a 1w &#x2001;1h' data-expect='' data-desc='leading U+2001'>
+<img srcset='data:,a 1w &#x2002;1h' data-expect='' data-desc='leading U+2002'>
+<img srcset='data:,a 1w &#x2003;1h' data-expect='' data-desc='leading U+2003'>
+<img srcset='data:,a 1w &#x2004;1h' data-expect='' data-desc='leading U+2004'>
+<img srcset='data:,a 1w &#x2005;1h' data-expect='' data-desc='leading U+2005'>
+<img srcset='data:,a 1w &#x2006;1h' data-expect='' data-desc='leading U+2006'>
+<img srcset='data:,a 1w &#x2007;1h' data-expect='' data-desc='leading U+2007'>
+<img srcset='data:,a 1w &#x2008;1h' data-expect='' data-desc='leading U+2008'>
+<img srcset='data:,a 1w &#x2009;1h' data-expect='' data-desc='leading U+2009'>
+<img srcset='data:,a 1w &#x200A;1h' data-expect='' data-desc='leading U+200A'>
+<img srcset='data:,a 1w &#x200C;1h' data-expect='' data-desc='leading U+200C'>
+<img srcset='data:,a 1w &#x200D;1h' data-expect='' data-desc='leading U+200D'>
+<img srcset='data:,a 1w &#x202F;1h' data-expect='' data-desc='leading U+202F'>
+<img srcset='data:,a 1w &#x205F;1h' data-expect='' data-desc='leading U+205F'>
+<img srcset='data:,a 1w &#x3000;1h' data-expect='' data-desc='leading U+3000'>
+<img srcset='data:,a 1w &#xFEFF;1h' data-expect='' data-desc='leading U+FEFF'>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png
new file mode 100644
index 0000000000..d26878c9f2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png.headers b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png.headers
new file mode 100644
index 0000000000..edaec7ad15
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png.headers
@@ -0,0 +1,3 @@
+Cache-Control: no-store
+
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/resized.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/resized.html
new file mode 100644
index 0000000000..6fb6847a66
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/resized.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<img srcset="image.png?400 400w, image.png?800 800w, image.png?1600 1600w" sizes="50vw">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/select-an-image-source.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/select-an-image-source.html
new file mode 100644
index 0000000000..292395d3ae
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/select-an-image-source.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>img select an image source</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=common.js></script>
+<div id=log></div>
+<!-- dup entries -->
+<img srcset='data:,a 1x, data:,b 1x' data-expect='data:,a'>
+<img srcset='data:,a , data:,b 1x' data-expect='data:,a'>
+<img srcset='data:,a 1x, data:,b' data-expect='data:,a'>
+<img srcset='data:,a 1w, data:,b 1w' data-expect='data:,a'>
+<img srcset='data:,a 1w 1h, data:,b 1w' data-expect='data:,a'>
+<img srcset='data:,a 1w, data:,b 1w 1h' data-expect='data:,a'>
+<img srcset='data:,a 1w 1h, data:,b 1w 2h' data-expect='data:,a'>
+<img srcset='data:,a 1w 2h, data:,b 1w 1h' data-expect='data:,a'>
+<img srcset='data:,a , data:,b' data-expect='data:,a'>
+<img srcset='data:,a 1w, data:,b 1x' sizes='1px' data-expect='data:,a'>
+<img srcset='data:,a 1x, data:,b 1w' sizes='1px' data-expect='data:,a'>
+<img srcset='data:,a 1w, data:,b 2x' sizes='0.5px' data-expect='data:,a'>
+<img srcset='data:,a 2x, data:,b 1w' sizes='0.5px' data-expect='data:,a'>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/srcset-media-dynamic.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/srcset-media-dynamic.html
new file mode 100644
index 0000000000..2cc74e2b8f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/srcset-media-dynamic.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>source element in picture handles dynamic media change correctly.</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=1523627">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<picture id="pic">
+ <source srcset="data:,a">
+</picture>
+<script>
+let t = async_test("Dynamic media change is handled correctly");
+
+let pic = document.getElementById("pic");
+// Something that will never match.
+pic.querySelector("source").setAttribute("media", "not all");
+
+let img = document.createElement("img");
+img.src = "data:,b";
+pic.appendChild(img);
+
+onload = t.step_func_done(function() {
+ assert_equals(img.currentSrc, "data:,b");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/external-sheet.svg b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/external-sheet.svg
new file mode 100644
index 0000000000..fd2eda7164
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/external-sheet.svg
@@ -0,0 +1,4 @@
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <style xmlns="http://www.w3.org/1999/xhtml">:root { background-color: green }</style>
+ <link xmlns="http://www.w3.org/1999/xhtml" rel="stylesheet" type="text/css" href="red-bg.css" />
+</svg>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/red-bg.css b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/red-bg.css
new file mode 100644
index 0000000000..da9af10628
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/red-bg.css
@@ -0,0 +1,2 @@
+:root { background: red }
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/relevant-mutations.js b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/relevant-mutations.js
new file mode 100644
index 0000000000..7105b03708
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/relevant-mutations.js
@@ -0,0 +1,15 @@
+setup({explicit_done:true});
+
+function t(desc, func, expect) {
+ async_test(function() {
+ var img = document.querySelector('[data-desc="' + desc + '"]');
+ img.onload = img.onerror = this.unreached_func('update the image data was run');
+ if (expect == 'timeout') {
+ setTimeout(this.step_func_done(), 1000);
+ } else {
+ img['on' + expect] = this.step_func_done();
+ setTimeout(this.unreached_func('update the image data didn\'t run'), 1000);
+ }
+ func.call(this, img);
+ }, desc);
+}
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet-ref.html
new file mode 100644
index 0000000000..fdab582933
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet-ref.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<title>Test reference</title>
+<p>You should see a green square below.</p>
+<div style="background:green;width:100px;height:100px"></div>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet.html
new file mode 100644
index 0000000000..a09dd7cc54
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<title>An img element with an svg src should not load external resources from the svg file.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/embedded-content.html#the-img-element">
+<link rel="match" href="svg-img-with-external-stylesheet-ref.html">
+<p>You should see a green square below.</p>
+<img width="100" height="100" src="support/external-sheet.svg">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-media.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-media.html
new file mode 100644
index 0000000000..dd679ef571
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-media.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>img update media</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ var t = async_test('set media after src updates selected image');
+
+ var img;
+
+ onload = t.step_func(function() {
+ img = document.querySelector('img');
+ img.addEventListener('load', t.step_func_done(onImgLoad));
+
+ var source = document.querySelector('source[data-media]');
+ source.setAttribute('media', source.getAttribute('data-media'));
+ });
+
+ function onImgLoad() {
+ img.removeEventListener('load', onImgLoad);
+
+ assert_true(img.currentSrc.indexOf(img.getAttribute('data-expect')) > -1);
+ }
+
+</script>
+
+<div id="log"></div>
+<picture>
+ <source srcset="/images/fail.gif" data-media="(max-width: 1px)" />
+ <source srcset="/images/smiley.png" />
+ <img data-expect="/images/smiley.png">
+</picture> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-src-complete.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-src-complete.html
new file mode 100644
index 0000000000..de3926a296
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-src-complete.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Changing the img src should retain the 'complete' property</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<p id="display"><img src="image.png"></p>
+<script>
+ setup({ single_test: true });
+
+ function check() {
+ var img = document.querySelector("img");
+ assert_true(img.complete, "By onload, image should have loaded");
+ img.src = `image.png?${Math.random()}`;
+ assert_false(img.complete, "Now that we're loading we should no longer be complete");
+ img.onload = function () {
+ assert_true(img.complete, "The new thing should have loaded.");
+ done();
+ }
+ }
+
+ onload = function () {
+ check();
+ };
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/current-request-microtask.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/current-request-microtask.html
new file mode 100644
index 0000000000..125b37eadb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/current-request-microtask.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>An img's current request should be updated in a microtask after selecting an image source</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+
+<script>
+async_test(function(t) {
+ const picture = document.createElement("picture");
+
+ const nonMatchingSource = document.createElement("source");
+ nonMatchingSource.media = "not all";
+ nonMatchingSource.srcset = "data:,a";
+ picture.append(nonMatchingSource);
+
+ const matchingSource = document.createElement("source");
+ matchingSource.media = "all";
+ matchingSource.srcset = "data:,b";
+ picture.append(matchingSource);
+
+ const img = document.createElement("img");
+ img.src = "data:,c";
+
+ assert_equals(img.currentSrc, "", "after assigning to img.src but before the corresponding microtask is run");
+
+ queueMicrotask(t.step_func(function() {
+ assert_equals(img.currentSrc, "data:,c", "after assigning to img.src and after corresponding microtask is run");
+
+ picture.append(img);
+ assert_equals(img.currentSrc, "data:,c", "after appending img to picture but before the corresponding microtask is run");
+
+ queueMicrotask(t.step_func(function() {
+ assert_equals(img.currentSrc, "data:,b", "after appending img to picture and after the corresponding microtask is run");
+ t.done();
+ }));
+ }));
+}, "currentSrc is updated only after the microtask that updates the current request is run");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/fail-to-resolve.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/fail-to-resolve.html
new file mode 100644
index 0000000000..959ceaa979
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/fail-to-resolve.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>img update the image data: fail to resolve URL</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+
+<img src="//[">
+<img srcset="//[">
+<img srcset="//[" src="/images/red.png">
+<img srcset="//[, /images/red.png">
+
+<script>
+setup({explicit_done: true});
+
+var expected = '//[';
+
+onload = function() {
+ [].forEach.call(document.images, function(img) {
+ test(function() {
+ assert_equals(img.currentSrc, expected);
+ }, img.outerHTML);
+ });
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-source-set.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-source-set.html
new file mode 100644
index 0000000000..063667baa9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-source-set.html
@@ -0,0 +1,140 @@
+<!doctype html>
+<title>img update the source set</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({explicit_done:true});
+
+function check(p) {
+ var img = p.querySelector('[data-expect]');
+ test(function() {
+ var expect = img.dataset.expect;
+ if ('resolve' in img.dataset) {
+ var a = document.createElement('a');
+ a.href = expect;
+ expect = a.href;
+ }
+ assert_equals(img.currentSrc, expect);
+ }, p.innerHTML);
+}
+
+onload = function() {
+ [].forEach.call(document.querySelectorAll('div:not([id])'), check);
+ done();
+};
+
+</script>
+<div id=log></div>
+<div><img data-expect=''></div>
+<div><img src data-expect=''></div>
+<div><img src='data:,a' data-expect='data:,a'></div>
+<div><img srcset src='data:,a' data-expect='data:,a'></div>
+<div><img srcset='data:,b' src='data:,a' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b' data-expect='data:,b'><!-- srcset after src --></div>
+<div><img src='data:,a' srcset='data:,b 1x' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 1.0x' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 1e0x' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 10000w' sizes='1px' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 10000w, data:,c 10000x' sizes='1px' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 10000x, data:,c 10000w' sizes='1px' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 1w' sizes='10000px' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 1w, data:,c 0.0001x' sizes='10000px' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 0.0001x, data:,c 1w' sizes='10000px' data-expect='data:,b'></div>
+<div><img srcset='data:,a' data-expect='data:,a'></div>
+
+<!-- child is not a <source> -->
+
+<div><picture>foo<img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><!--foo--><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><br><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><p></p><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><video><source srcset='data:,b'></video><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><span><source srcset='data:,b'></span><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><svg><source srcset='data:,b'/></svg><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><svg/><source srcset='data:,b'/><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><svg><font/><source srcset='data:,b'/></svg><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><svg><!--<font face> tag breaks out of svg--><font face></font><source srcset='data:,b'/></svg><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><img src='data:,a'><img src='data:,b' data-expect='data:,b'></picture></div>
+
+<!-- <source> has no srcset -->
+
+<div><picture><source><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source src='data:,b'><img src='data:,a' data-expect='data:,a'></picture></div>
+
+<!-- <source srcset> has zero candidates -->
+
+<div><picture><source srcset><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset=', ,'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b 1x 1x'><img src='data:,a' data-expect='data:,a'></picture></div>
+
+<!-- <source media> -->
+
+<div><picture><source srcset='data:,b' media><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' media='all'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' media='all and (min-width:0)'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' media='all and !'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media='all and (!)'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media='not all'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media='not all and (min-width:0)'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media='not all and (max-width:0)'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' media='not all and !'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media='not all and (!)'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media='all, !'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' media=','><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media=', all'><img src='data:,a' data-expect='data:,b'></picture></div>
+
+<!-- <source type> assume support for gif, png, jpg, svg, ico -->
+
+<div><picture><source srcset='data:,b' type><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type=' '><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type=' image/gif'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif '><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif;'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif;encodings'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif;encodings='><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif;encodings=foobar'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/png'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/jpeg'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/svg+xml'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/x-icon'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='text/xml'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='text/html'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='text/plain'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='text/css'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='video/mp4'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='video/ogg'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='video/webm'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='unknown/unknown'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='application/octet-stream'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='application/x-shockwave-flash'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='image\gif'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='gif'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='.gif'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='*'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='*/*'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='image/*'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type=','><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif, image/png'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif image/png'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='image/foobarbaz'><img src='data:,a' data-expect='data:,a'></picture></div>
+
+<!-- trailing garbage -->
+
+<div><picture><img src='data:,a' data-expect='data:,a'>foo</picture></div>
+<div><picture><img src='data:,a' data-expect='data:,a'><br></picture></div>
+<div><picture><img src='data:,a' data-expect='data:,a'><!--foo--></picture></div>
+<div><picture><img src='data:,a' data-expect='data:,a'><img src='data:,b'></picture></div>
+<div><picture><img data-expect=''><img src='data:,b'></picture></div>
+<div><picture><img src='data:,a' data-expect='data:,a'><source srcset='data:,b'></picture></div>
+<div><picture><img data-expect=''><source srcset='data:,b'></picture></div>
+
+<!-- parent not picture -->
+
+<div><picture><span><source srcset='data:,b'><img data-expect=''></span></picture></div>
+<div><picture><span><source srcset='data:,b'><img src='data:,a' data-expect='data:,a'></span></picture></div>
+<div><picture><source srcset='data:,b'><span><img src='data:,a' data-expect='data:,a'></span></picture></div>
+
+<!-- no src -->
+
+<div><picture><source srcset='data:,b'><img data-expect='data:,b'></picture></div>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/usemap-casing.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/usemap-casing.html
new file mode 100644
index 0000000000..c28f667ff3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/usemap-casing.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>img usemap case-sensitive</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-a-hash-name-reference">
+<!-- See also: https://github.com/whatwg/html/issues/1666 -->
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<img src="/images/threecolors.png" usemap="#sanityCheck" width="100" height="100">
+<map name="sanityCheck"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#sImPlE" width="100" height="100">
+<map name="simple"><area shape="rect" coords="0,0,100,100"></map>
+<map name="SIMPLE"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#paSSfield-killroyß" width="100" height="100">
+<map name="passfield-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="PASSFIELD-KILLROYß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="paſſfield-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="passfield-&#x212a;illroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="paßfield-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="paẞfield-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="passfield-killroyẞ"><area shape="rect" coords="0,0,100,100"></map>
+<map name="passfield-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="passfıeld-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="passfİeld-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#глупый" width="100" height="100">
+<map name="глупы&#x438;&#x306;"><area shape="rect" coords="0,0,100,100"></map>
+<map name="ГЛУПЫЙ"><area shape="rect" coords="0,0,100,100"></map>
+<map name="ГЛУПЫ&#x418;&#x306;"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#åωk" width="100" height="100">
+<map name="ÅΩK"><area shape="rect" coords="0,0,100,100"></map>
+<map name="&#x212b;ωk"><area shape="rect" coords="0,0,100,100"></map>
+<map name="å&#x2126;k"><area shape="rect" coords="0,0,100,100"></map>
+<map name="åω&#x212a;"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#blah1" width="100" height="100">
+<map name="blah&#x2460;"><area shape="rect" coords="0,0,100,100"></map>
+<map name="bl&#x24b6;h1"><area shape="rect" coords="0,0,100,100"></map>
+<map name="bl&#x24d0;h1"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#t&Eacute;dz5アパートFi" width="100" height="100">
+<map name="T&Eacute;DZ5アパートFi"><area shape="rect" coords="0,0,100,100"></map>
+<map name="T&eacute;&#x01F1;&#x2075;アパートFi"><area shape="rect" coords="0,0,100,100"></map>
+<map name="t&Eacute;dz5&#x3100;Fi"><area shape="rect" coords="0,0,100,100"></map>
+<map name="t&Eacute;dz5&#x30A2;&#x30CF;&#x309A;&#x30FC;&#x30C8;Fi"><area shape="rect" coords="0,0,100,100"></map>
+<map name="T&Eacute;DZ⁵アパートFi"><area shape="rect" coords="0,0,100,100"></map>
+<map name="T&Eacute;DZ5アパートfi"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#ΣΣ" width="100" height="100">
+<map name="σς"><area shape="rect" coords="0,0,100,100"></map>
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ explicit_done: true });
+
+onload = () => {
+ test(() => {
+ const image = document.querySelector(`img[usemap="#sanityCheck"]`);
+ const imageRect = image.getBoundingClientRect();
+ const x = imageRect.left + imageRect.width / 2;
+ const y = imageRect.top + imageRect.height / 2;
+ const element = document.elementFromPoint(x, y);
+ const area = document.querySelector(`map[name="sanityCheck"] > area`);
+
+ assert_equals(element, area);
+ }, `Image with usemap of #sanityCheck should match the area with map named sanityCheck`);
+
+ const images = Array.from(document.querySelectorAll(`img:not([usemap="#sanityCheck"])`));
+
+ for (let image of images) {
+ test(() => {
+ const imageRect = image.getBoundingClientRect();
+ const x = imageRect.left + imageRect.width / 2;
+ const y = imageRect.top + imageRect.height / 2;
+ const element = document.elementFromPoint(x, y);
+
+ const name = element.parentElement.getAttribute("name");
+ const messageSuffix = name ? `; used <map> with name "${name}"` : "";
+
+ assert_equals(element, image, "The element retrieved must be the image, not an area" + messageSuffix);
+ }, `Image with usemap of ${image.useMap} should not match any of the areas`);
+ }
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/block-object-with-ruby-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/block-object-with-ruby-crash.html
new file mode 100644
index 0000000000..481a7408e4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/block-object-with-ruby-crash.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=966363">
+<object style="display:block;">
+ <ruby></ruby>
+</object>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ test(()=> {}, "no crash");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/document-getters-return-null-for-cross-origin.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/document-getters-return-null-for-cross-origin.html
new file mode 100644
index 0000000000..3d1077538e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/document-getters-return-null-for-cross-origin.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test that contentDocument/getSVGDocument() return null for a cross-origin document.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<object data='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect height="100" width="100"/></svg>'></object>
+<script>
+const object = document.querySelector('object');
+var t1 = async_test('HTMLObjectElement.contentDocument for cross-origin document');
+window.addEventListener(
+ 'load', t1.step_func_done(() => { assert_equals(object.contentDocument, null); }));
+var t2 = async_test('HTMLObjectElement.getSVGDocument() for cross-origin document');
+window.addEventListener(
+ 'load', t2.step_func_done(() => { assert_equals(object.getSVGDocument(), null); }));
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/historical.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/historical.html
new file mode 100644
index 0000000000..c7a577a9d4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/historical.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>Historical object element features should not be supported</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<object id=object></object>
+<script>
+test(function() {
+ var elm = document.getElementById('object');
+ assert_equals(typeof elm, 'object', 'typeof');
+ assert_throws_js(TypeError, function() {
+ elm();
+ });
+}, 'object legacycaller should not be supported');
+
+test(() => {
+ const obj = document.createElement("object");
+ assert_false("typeMustMatch" in obj);
+}, "object's typeMustMatch IDL attribute should not be supported");
+
+async_test(t => {
+ const obj = document.createElement("object");
+ t.add_cleanup(() => obj.remove());
+ obj.setAttribute("data", "/common/blank.html");
+ obj.setAttribute("type", "text/plain");
+ obj.setAttribute("typemustmatch", "");
+ obj.onload = t.step_func_done(() => {
+ assert_not_equals(obj.contentDocument, null, "/common/blank.html should be loaded");
+ });
+ obj.onerror = t.unreached_func();
+ document.body.appendChild(obj);
+}, "object's typemustmatch content attribute should not be supported");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-attributes.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-attributes.html
new file mode 100644
index 0000000000..c630d8055c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-attributes.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: object - attributes</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body onload="on_load()">
+<div id="log"></div>
+<form>
+ <object id="obj1" data="/common/blank.html" name="o" height="50" width="100"></object>
+ <object id="obj2" name="p" type="image/png"></object>
+ <object id="obj3" data="missing.html" name="o3" height="50" width="100"></object>
+</form>
+<script>
+ var obj1;
+ var obj2;
+ var obj3;
+ var t1 = async_test("object.contentWindow");
+ var t2 = async_test("object.contentWindow.name");
+ var t3 = async_test("object.width");
+ var t4 = async_test("object.height");
+
+ setup(function() {
+ obj1 = document.getElementById("obj1");
+ obj2 = document.getElementById("obj2");
+ obj3 = document.getElementById("obj3");
+ });
+
+ function on_load () {
+ t1.step(function() {
+ assert_not_equals(obj1.contentWindow, null, "The contentWindow of the object element should not be null.");
+ assert_equals(obj2.contentWindow, null, "The contentWindow of the object element should be null when it type attribute starts with 'image/'.");
+ assert_equals(obj3.contentWindow, null, "The contentWindow of the object element should be null as it is showing fallback content.");
+ });
+ t1.done()
+
+ t2.step(function() {
+ assert_equals(obj1.contentWindow.name, "o", "The contentWindow's name of the object element should be 'o'.");
+ obj1.setAttribute("name", "o1");
+ assert_equals(obj1.name, "o1", "The name of the object element should be 'o1'.");
+ assert_equals(obj1.contentWindow.name, "o", "The contentWindow's name of the object element should still be 'o'.");
+ obj1.removeAttribute("name");
+ assert_equals(obj1.name, "", "The name of the object element should be empty string.");
+ assert_equals(obj1.contentWindow.name, "o", "The contentWindow's name of the object element should still be 'o'.");
+ });
+ t2.done()
+
+ t3.step(function() {
+ assert_equals(getComputedStyle(obj1, null)["width"], "100px", "The width should be 100px.");
+ });
+ t3.done();
+
+ t4.step(function() {
+ assert_equals(getComputedStyle(obj1, null)["height"], "50px", "The height should be 50px.");
+ });
+ t4.done();
+ }
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-construct-in-document-with-null-browsing-context-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-construct-in-document-with-null-browsing-context-crash.html
new file mode 100644
index 0000000000..7248368656
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-construct-in-document-with-null-browsing-context-crash.html
@@ -0,0 +1,11 @@
+<title>HTMLObjectElement: construct in a document with a null browsing context</title>
+<link rel="author" title="Nate Chapin" href="mailto:japhet@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/C/#the-object-element">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1083437">
+<meta name="assert" content="Constructing an HTMLObjectElement in a document with a null browsing context should not crash"/>
+<iframe id="i"></iframe>
+<script>
+var doc = i.contentDocument;
+i.remove();
+doc.createElement('object');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-events.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-events.html
new file mode 100644
index 0000000000..38f92c3d35
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-events.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: object-events</title>
+<meta name="timeout" content="long">
+<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>
+<script>
+
+async_test(function(t) {
+ var obj = document.createElement("object");
+ obj.onerror = t.step_func_done(function(e){
+ assert_equals(Object.getPrototypeOf(e).constructor, Event, "The error event should use the Event interface.");
+ assert_true(e.isTrusted, "The error event should be a trusted event.");
+ assert_false(e.cancelable, "The error event should not be a cancelable event.");
+ assert_false(e.bubbles, "The error event should not be a bubble event.");
+ assert_equals(e.target, obj, "The error event target should be the corresponding object element.");
+ });
+
+ obj.onload = t.step_func_done(function(e){
+ assert_unreached("The load event should not be fired.");
+ });
+
+ obj.data = "file:\\http://nonexistent.html";
+ document.body.appendChild(obj);
+}, "error event (using 'file:' protocol)");
+
+async_test(function(t) {
+ var obj = document.createElement("object");
+ obj.onerror = t.step_func_done(function(e){
+ assert_equals(e.target, obj,
+ "The error event should be fired on our element");
+ });
+ obj.onload = t.step_func_done(function(e){
+ assert_unreached("The load event should not be fired.");
+ });
+
+ obj.data = "http://test:test";
+ document.body.appendChild(obj);
+}, "error event (using 'http:' protocol)");
+
+
+async_test(function(t) {
+ var obj = document.createElement("object");
+ obj.onload = t.step_func_done(function(e){
+ assert_equals(Object.getPrototypeOf(e).constructor, Event, "The load event should use the Event interface.");
+ assert_true(e.isTrusted, "The load event should be a trusted event.");
+ assert_false(e.cancelable, "The load event should not be a cancelable event.");
+ assert_false(e.bubbles, "The load event should not be a bubble event.");
+ assert_equals(e.target, obj, "The load event target should be the corresponding object element.");
+ });
+
+ obj.onerror = t.step_func_done(function(e){
+ assert_unreached("The error event should not be fired.");
+ });
+
+ obj.data = "/images/blue.png";
+ document.body.appendChild(obj);
+}, "load event");
+
+async_test(function(t) {
+ var obj = document.createElement("object");
+ obj.onload = t.step_func_done(function(e){
+ assert_true(obj.contentWindow instanceof obj.contentWindow.Window, "The object element should represent a nested browsing context.")
+ assert_equals(Object.getPrototypeOf(e).constructor, Event, "The load event should use the Event interface.");
+ assert_true(e.isTrusted, "The load event should be a trusted event.");
+ assert_false(e.cancelable, "The load event should not be a cancelable event.");
+ assert_false(e.bubbles, "The load event should not be a bubble event.");
+ assert_equals(e.target, obj, "The load event target should be the corresponding object element.");
+ });
+
+ obj.onerror = t.step_func_done(function(e){
+ assert_unreached("The error event should not be fired.");
+ });
+
+ obj.data = "about:blank";
+ document.body.appendChild(obj);
+}, "load event of about:blank");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-fallback-failed-cross-origin-navigation.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-fallback-failed-cross-origin-navigation.sub.html
new file mode 100644
index 0000000000..09061e0349
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-fallback-failed-cross-origin-navigation.sub.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test that &lt;object&gt; renders its own fallback.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+ const URIS = [
+ // The host exists but the resource is unavailable.
+ "http://{{hosts[alt][www]}}:{{ports[http][0]}}/foo.html",
+ // The destination does not even exist and the navigation fails.
+ "http://{{hosts[alt][nonexistent]}}:{{ports[http][0]}}/foo.html",
+ ];
+
+ // Create an <object> with some fallback content.
+ function create_object_with_fallback(url, t) {
+ var object = document.createElement("object");
+ var fallback = document.createElement("button");
+ fallback.textContent = "FALLBACK CONTENT";
+ object.appendChild(fallback);
+ object.data = url;
+ object.type = "text/html";
+ let promise = new Promise(resolve => {
+ object.addEventListener("load", t.unreached_func("Should never reach the load event"), {once: true});
+ object.addEventListener("error", () => resolve(object), {once: true});
+ });
+ document.body.appendChild(object);
+ t.add_cleanup(() => object.remove());
+ return promise;
+ }
+
+ function area(el) {
+ let bounds = el.getBoundingClientRect();
+ return bounds.width * bounds.height;
+ }
+
+ for (let uri of URIS) {
+ promise_test(async(t) => {
+ let object = await create_object_with_fallback(uri, t);
+
+ // XXX In Chrome this is needed, fallback doesn't seem to be ready after
+ // the error event, which seems weird/odd.
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ assert_true(area(object.firstChild) > 0, "Should be showing fallback");
+
+ // Per https://html.spec.whatwg.org/#the-object-element:
+ //
+ // The object element can represent an external resource, which,
+ // depending on the type of the resource, will either be treated as
+ // image, as a child browsing context, or as an external resource to
+ // be processed by a plugin.
+ //
+ // [...]
+ //
+ // If the load failed (e.g. there was an HTTP 404 error, there was a
+ // DNS error), fire an event named error at the element, then jump to
+ // the step below labeled fallback.
+ //
+ // (And that happens before "Determine the resource type" which is what
+ // sets the nested browsing context).
+ //
+ // So the expected window.length is 0.
+ assert_equals(window.length, 0);
+ }, `Verify fallback content for failed cross-origin navigations is shown correctly: ${uri}`);
+ }
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-handler.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-handler.html
new file mode 100644
index 0000000000..a24554e0cc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-handler.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: object - handler</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>
+<object id="test" name="obj" data="test0.html" type="text/html"></object>
+<script>
+
+var t1 = async_test("The nested browsing context must be navigated to the resource specified by the data attribute.");
+var t2 = async_test("The object.data must not be updated if the browsing context gets further navigated.");
+
+function callback(data) {
+ if (data == "test0") {
+ t1.step(function() {
+ var testEle = document.getElementById("test");
+ assert_true(testEle.contentDocument.location.href.indexOf("test0.html") != -1, "The nested browsing context should be navigated to test0.html.");
+ window["obj"].history.replaceState({state:"ok"}, "mytitle ", "object-fallback.html");
+ assert_not_equals(testEle.contentDocument.location.href.indexOf("object-fallback.html"), -1, "The nested browsing context should be replacement enabled.");
+ });
+ t1.done();
+ } else if (data == "test1") {
+ t2.step(function() {
+ var testEle = document.getElementById("test");
+ assert_true(testEle.contentDocument.location.href.indexOf("test1.html") != -1, "The browsing context should be navigated to test1.html.");
+ assert_true(testEle.data.indexOf("test0.html") != -1, "The value of attribute data should not be updated.");
+ });
+ t2.done();
+ }
+}
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-ignored-in-media-element.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-ignored-in-media-element.html
new file mode 100644
index 0000000000..2bf84c2946
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-ignored-in-media-element.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>HTML Test: The embed element represents a document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<meta name="assert" content="Check if the object element is ignored when used inside a media element">
+<script type="application/javascript">
+ var nestingTest = async_test("Test <object> being ignored inside media element");
+ onload = nestingTest.step_func_done(function() {
+ assert_true(true, "We got to a load event without loading things we should not load");
+ });
+</script>
+<body>
+ <video>
+ <object type="text/html" data="../resources/should-not-load.html"
+ test-description="<object> in <video>"></object>
+ </video>
+ <audio>
+ <object type="text/html" data="../resources/should-not-load.html"
+ test-description="<object> in <audio>"></object>
+ </audio>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-in-display-none-load-event.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-in-display-none-load-event.html
new file mode 100644
index 0000000000..c8369365af
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-in-display-none-load-event.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html style="display:none">
+<meta charset=utf-8>
+<title>Test that an object in a display:none subtree does not block the load event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ async_test(t => {
+ window.onload = t.step_func_done();
+ document.documentElement.offsetTop;
+ }, "Load event triggered on window");
+</script>
+<object data="data:text/html,"></object>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-in-object-fallback-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-in-object-fallback-2.html
new file mode 100644
index 0000000000..47cf801693
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-in-object-fallback-2.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title></title>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script>
+ var loadedCount = 0;
+ var nestingTest = async_test("Test <object> nesting inside <object>");
+ onload = nestingTest.step_func_done(function() {
+ assert_equals(loadedCount, 12, "Should have loaded all should-load elements");
+ });
+ </script>
+ <style>
+ object { display: none }
+ </style>
+ </head>
+ <body>
+ <object data="../resources/should-load.html" style="width: 100px; height: 100px">
+ <object type="text/html" data="../resources/should-not-load.html"
+ test-description="<object> inside <object>"></object>
+ </object>
+ <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test">
+ <object type="text/html" data="../resources/should-load.html"></object>
+ </object>
+ <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test">
+ <div></div>
+ <object type="text/html" data="../resources/should-load.html"></object>
+ </object>
+ <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test">
+ <div>
+ <object type="text/html" data="../resources/should-load.html"></object>
+ </div>
+ </object>
+ <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test">
+ <object type="text/html" data="../resources/should-load.html"></object>
+ <object type="text/html" data="../resources/should-load.html"></object>
+ <object data="../resources/should-load.html">
+ <object type="text/html" data="../resources/should-not-load.html"
+ test-description="<object> inside loaded <object> inside non-loaded <object>"></object>
+ </object>
+ <object data="data:application/x-does-not-exist,test">
+ <object type="text/html" data="../resources/should-load.html"></object>
+ </object>
+ </object>
+ <div>
+ <object data="../resources/should-load.html" style="width: 100px; height: 100px"></object>
+ <object type="text/html" data="../resources/should-load.html"></object>
+ </div>
+ <div>
+ <object type="text/html" data="../resources/should-load.html"></object>
+ <object data="../resources/should-load.html" style="width: 100px; height: 100px"></object>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-param-url-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-param-url-ref.html
new file mode 100644
index 0000000000..7eb9256b0f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-param-url-ref.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>object element containing param element specifying a URL</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+
+<style>
+ div {
+ width:300px;
+ height:80px;
+ border:1px solid black;
+ margin: 5px;
+ overflow: hidden;
+ }
+</style>
+<body>
+<script>
+const smallPdf = 'JVBERi0xLjIgCjkgMCBvYmoKPDwKPj4Kc3RyZWFtCkJULyA5IFRmKFRlc3QpJyBFVAplbmRzdHJlYW0KZW5kb2JqCjQgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL1BhcmVudCA1IDAgUgovQ29udGVudHMgOSAwIFIKPj4KZW5kb2JqCjUgMCBvYmoKPDwKL0tpZHMgWzQgMCBSIF0KL0NvdW50IDEKL1R5cGUgL1BhZ2VzCi9NZWRpYUJveCBbIDAgMCA5OSA5IF0KPj4KZW5kb2JqCjMgMCBvYmoKPDwKL1BhZ2VzIDUgMCBSCi9UeXBlIC9DYXRhbG9nCj4+CmVuZG9iagp0cmFpbGVyCjw8Ci9Sb290IDMgMCBSCj4+CiUlRU9G';
+const dataUrl = `data:application/pdf;base64,${smallPdf}`;
+
+function addOne(html) {
+ const wrapper = document.createElement('div');
+ wrapper.innerHTML = html;
+ const objectElement = wrapper.querySelector('object');
+ document.body.appendChild(wrapper);
+}
+
+// This should be one <object> that loads a PDF, and the rest that don't.
+addOne(`<object data=${dataUrl}></object>`);
+addOne(`<object></object>`);
+addOne(`<object></object>`);
+addOne(`<object></object>`);
+addOne(`<object></object>`);
+addOne(`<object></object>`);
+
+// Not a great way to tell when any <object> that might load has loaded.
+setTimeout(() => document.documentElement.classList.remove("reftest-wait"),2000);
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-param-url.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-param-url.html
new file mode 100644
index 0000000000..5f1e54c4d9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-param-url.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>object element containing param element specifying a URL</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://github.com/whatwg/html/pull/7816">
+<link rel=match href="object-param-url-ref.html">
+
+<style>
+ div {
+ width:300px;
+ height:80px;
+ border:1px solid black;
+ margin: 5px;
+ overflow: hidden;
+ }
+</style>
+<body>
+<script>
+const smallPdf = 'JVBERi0xLjIgCjkgMCBvYmoKPDwKPj4Kc3RyZWFtCkJULyA5IFRmKFRlc3QpJyBFVAplbmRzdHJlYW0KZW5kb2JqCjQgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL1BhcmVudCA1IDAgUgovQ29udGVudHMgOSAwIFIKPj4KZW5kb2JqCjUgMCBvYmoKPDwKL0tpZHMgWzQgMCBSIF0KL0NvdW50IDEKL1R5cGUgL1BhZ2VzCi9NZWRpYUJveCBbIDAgMCA5OSA5IF0KPj4KZW5kb2JqCjMgMCBvYmoKPDwKL1BhZ2VzIDUgMCBSCi9UeXBlIC9DYXRhbG9nCj4+CmVuZG9iagp0cmFpbGVyCjw8Ci9Sb290IDMgMCBSCj4+CiUlRU9G';
+const dataUrl = `data:application/pdf;base64,${smallPdf}`;
+
+function addOne(html) {
+ const wrapper = document.createElement('div');
+ wrapper.innerHTML = html;
+ const objectElement = wrapper.querySelector('object');
+ document.body.appendChild(wrapper);
+}
+
+// This should be one <object> that loads a PDF, and the rest that don't.
+addOne(`<object data=${dataUrl}></object>`);
+addOne(`<object><param name=src value=${dataUrl}></object>`);
+addOne(`<object><param name=data value=${dataUrl}></object>`);
+addOne(`<object><param name=code value=${dataUrl}></object>`);
+addOne(`<object><param name=movie value=${dataUrl}></object>`);
+addOne(`<object><param name=url value=${dataUrl}></object>`);
+
+// Not a great way to tell when any <object> that might load has loaded.
+setTimeout(() => document.documentElement.classList.remove("reftest-wait"),2000);
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-remove-param-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-remove-param-crash.html
new file mode 100644
index 0000000000..e1e2ebb4e6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-remove-param-crash.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>HTML Test: object - crash removing a param after changing its style</title>
+<link rel="help" href="https://crbug.com/1195633">
+<object type="text/html">
+ <param id="param"></param>
+</object>
+<script>
+ getComputedStyle(param).color;
+ param.style.color = "red";
+ param.remove();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-setcustomvalidity.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-setcustomvalidity.html
new file mode 100644
index 0000000000..44574ffd11
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/object-setcustomvalidity.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<title>object setCustomValidity</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<object id='object_test'></object>
+
+<script>
+
+test(() => {
+ let elem = document.getElementById("object_test");
+ assert_false(elem.validity.customError);
+ elem.setCustomValidity("custom error");
+ assert_true(elem.validity.customError);
+}, "object setCustomValidity is correct")
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/test0.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/test0.html
new file mode 100644
index 0000000000..17df71daa2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/test0.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<script>
+
+parent.callback("test0");
+document.location.href = "test1.html";
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/test1.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/test1.html
new file mode 100644
index 0000000000..cf2423275e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/test1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<script>
+
+parent.callback("test1");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/usemap-casing.html b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/usemap-casing.html
new file mode 100644
index 0000000000..114a472fb6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-object-element/usemap-casing.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>object usemap case-sensitive</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-a-hash-name-reference">
+<!-- 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>
+
+<object data="/images/threecolors.png" usemap="#sanityCheck" width="300" height="300"></object>
+<map name="sanityCheck"><area shape="rect" coords="0,0,300,300"></map>
+
+<object data="/images/threecolors.png" usemap="#sImPlE" width="300" height="300"></object>
+<map name="simple"><area shape="rect" coords="0,0,300,300"></map>
+<map name="SIMPLE"><area shape="rect" coords="0,0,300,300"></map>
+
+<object data="/images/threecolors.png" usemap="#paSSfield-killroyß" width="300" height="300"></object>
+<map name="passfield-killroyß"><area shape="rect" coords="0,0,300,300"></map>
+<map name="PASSFIELD-KILLROYß"><area shape="rect" coords="0,0,300,300"></map>
+<map name="paſſfield-killroyß"><area shape="rect" coords="0,0,300,300"></map>
+<map name="passfield-&#x212a;illroyß"><area shape="rect" coords="0,0,300,300"></map>
+<map name="paßfield-killroyß"><area shape="rect" coords="0,0,300,300"></map>
+<map name="paẞfield-killroyß"><area shape="rect" coords="0,0,300,300"></map>
+<map name="passfield-killroyẞ"><area shape="rect" coords="0,0,300,300"></map>
+<map name="passfield-killroyß"><area shape="rect" coords="0,0,300,300"></map>
+<map name="passfıeld-killroyß"><area shape="rect" coords="0,0,300,300"></map>
+<map name="passfİeld-killroyß"><area shape="rect" coords="0,0,300,300"></map>
+
+<object data="/images/threecolors.png" usemap="#глупый" width="300" height="300"></object>
+<map name="глупы&#x438;&#x306;"><area shape="rect" coords="0,0,300,300"></map>
+<map name="ГЛУПЫЙ"><area shape="rect" coords="0,0,300,300"></map>
+<map name="ГЛУПЫ&#x418;&#x306;"><area shape="rect" coords="0,0,300,300"></map>
+
+<object data="/images/threecolors.png" usemap="#åωk" width="300" height="300"></object>
+<map name="ÅΩK"><area shape="rect" coords="0,0,300,300"></map>
+<map name="&#x212b;ωk"><area shape="rect" coords="0,0,300,300"></map>
+<map name="å&#x2126;k"><area shape="rect" coords="0,0,300,300"></map>
+<map name="åω&#x212a;"><area shape="rect" coords="0,0,300,300"></map>
+
+<object data="/images/threecolors.png" usemap="#blah1" width="300" height="300"></object>
+<map name="blah&#x2460;"><area shape="rect" coords="0,0,300,300"></map>
+<map name="bl&#x24b6;h1"><area shape="rect" coords="0,0,300,300"></map>
+<map name="bl&#x24d0;h1"><area shape="rect" coords="0,0,300,300"></map>
+
+<object data="/images/threecolors.png" usemap="#t&Eacute;dz5アパートFi" width="300" height="300"></object>
+<map name="T&Eacute;DZ5アパートFi"><area shape="rect" coords="0,0,300,300"></map>
+<map name="T&eacute;&#x01F1;&#x2075;アパートFi"><area shape="rect" coords="0,0,300,300"></map>
+<map name="t&Eacute;dz5&#x3300;Fi"><area shape="rect" coords="0,0,300,300"></map>
+<map name="t&Eacute;dz5&#x30A2;&#x30CF;&#x309A;&#x30FC;&#x30C8;Fi"><area shape="rect" coords="0,0,300,300"></map>
+<map name="T&Eacute;DZ⁵アパートFi"><area shape="rect" coords="0,0,300,300"></map>
+<map name="T&Eacute;DZ5アパートfi"><area shape="rect" coords="0,0,300,300"></map>
+
+<object data="/images/threecolors.png" usemap="#ΣΣ" width="300" height="300"></object>
+<map name="σς"><area shape="rect" coords="0,0,300,300"></map>
+
+<script>
+"use strict";
+setup({ explicit_done: true });
+
+onload = () => {
+ const objects = Array.from(document.querySelectorAll(`object`));
+
+ for (let object of objects) {
+ test(() => {
+ const objectRect = object.getBoundingClientRect();
+ const x = objectRect.left + objectRect.width / 2;
+ const y = objectRect.top + objectRect.height / 2;
+ const element = document.elementFromPoint(x, y);
+
+ const name = element.parentElement.getAttribute("name");
+ const messageSuffix = name ? `; used <map> with name "${name}"` : "";
+
+ assert_equals(element, object, "The element retrieved must be the object, not an area" + messageSuffix);
+ }, `Object with usemap of ${object.useMap} should not match any of the areas (it does not support usemap)`);
+ }
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/intrinsic_sizes.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/intrinsic_sizes.htm
new file mode 100644
index 0000000000..f9426a529c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/intrinsic_sizes.htm
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+<head>
+<title>video element - intrinsic sizes</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+</head>
+<body>
+<p><a href="https://html.spec.whatwg.org/multipage/#the-video-element">spec reference</a></p>
+<video id="v1"></video>
+<video id="v2" width="400"></video>
+<video id="v3" height="100"></video>
+<video id="v4"></video>
+<video id="v5" poster="/media/poster.png"></video>
+<div id="log"></div>
+<script>
+test(function() {
+ var s = getComputedStyle(document.getElementById("v1"));
+ assert_equals(s.width, "300px");
+ assert_equals(s.height, "150px");
+}, "default object size is 300x150");
+
+test(function() {
+ var s = getComputedStyle(document.getElementById("v2"));
+ assert_equals(s.width, "400px");
+ assert_equals(s.height, "200px");
+}, "default height is half the width");
+
+test(function() {
+ var s = getComputedStyle(document.getElementById("v3"));
+ assert_equals(s.width, "200px");
+ assert_equals(s.height, "100px");
+}, "default width is twice the height");
+
+async_test(function(t) {
+ var v = document.getElementById("v4");
+ var s = getComputedStyle(v);
+ v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+ v.onerror = t.unreached_func();
+ v.onloadedmetadata = t.step_func(function() {
+ assert_equals(s.width, '320px');
+ assert_equals(s.height, '240px');
+ v.removeAttribute("src");
+ v.load();
+ // Dimensions should be updated only on next layout.
+ requestAnimationFrame(t.step_func_done(function() {
+ assert_equals(s.width, "300px");
+ assert_equals(s.height, "150px");
+ }));
+ });
+}, "default object size after src is removed");
+
+async_test(function(t) {
+ var v = document.getElementById("v5");
+ var s = getComputedStyle(v);
+ v.onerror = t.unreached_func();
+ onload = t.step_func(function() {
+ assert_equals(s.width, '102px');
+ assert_equals(s.height, '77px');
+ v.removeAttribute("poster");
+ // Dimensions should be updated only on next layout.
+ requestAnimationFrame(t.step_func_done(function() {
+ assert_equals(s.width, "300px");
+ assert_equals(s.height, "150px");
+ }));
+ });
+}, "default object size after poster is removed");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/resize-during-playback.html b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/resize-during-playback.html
new file mode 100644
index 0000000000..e1f35768bc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/resize-during-playback.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+<head>
+<title>video element resizing during playback</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/media.html#concept-video-intrinsic-width">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+for (const format of ['mp4', 'webm']) {
+ promise_test(async (t) => {
+ const video = document.createElement('video');
+ assert_implements_optional(video.canPlayType(`video/${format}`), `${format} supported`);
+
+ const eventWatcher = new EventWatcher(t, video, ['resize', 'playing', 'error', 'ended']);
+
+ // Load the video and wait for initial resize event.
+ video.muted = true;
+ video.preload = 'auto';
+ video.onerror = t.unreached_func("error during playback");
+ video.src = `/media/400x300-red-resize-200x150-green.${format}`;
+ document.body.appendChild(video);
+
+ await eventWatcher.wait_for(['resize']);
+ assert_equals(video.videoWidth, 400, 'width after first resize event');
+ assert_equals(video.videoHeight, 300, 'height after first resize event');
+
+ // Now play and wait for a second resize event.
+ const playPromise = video.play();
+ if (playPromise) {
+ playPromise.catch(t.unreached_func("play rejected"));
+ }
+ await eventWatcher.wait_for(['playing', 'resize']);
+ assert_equals(video.videoWidth, 200, 'width after second resize event');
+ assert_equals(video.videoHeight, 150, 'height after second resize event');
+ }, `${format} video`);
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-import-to-inactive-document-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-import-to-inactive-document-crash.html
new file mode 100644
index 0000000000..1e14388efd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-import-to-inactive-document-crash.html
@@ -0,0 +1,7 @@
+<iframe id="i"></iframe>
+<video id="v"></video>
+<script>
+var doc = i.contentDocument;
+i.remove();
+doc.importNode(v);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-poster-shown-preload-auto-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-poster-shown-preload-auto-ref.html
new file mode 100644
index 0000000000..66b42e4025
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-poster-shown-preload-auto-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+ <title>Verifies that video poster is shown even if video element has 'preload="auto"' attribute</title>
+ <video preload="none" poster="/media/poster.png" src="/media/video.webm" width="100" height="100"></video>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-poster-shown-preload-auto.html b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-poster-shown-preload-auto.html
new file mode 100644
index 0000000000..95e32bb77c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-poster-shown-preload-auto.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>Verifies that video poster is shown even if video element has 'preload="auto"' attribute</title>
+ <link rel="match" href="video-poster-shown-preload-auto-ref.html">
+ <video preload="auto" poster="/media/poster.png" src="/media/video.webm" width="100" height="100"></video>
+ <script>
+ const video = document.querySelector("video");
+ video.oncanplaythrough = () => document.documentElement.classList.remove("reftest-wait");
+ </script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-tabindex.html b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-tabindex.html
new file mode 100644
index 0000000000..3044874789
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video-tabindex.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>tabindex on video elements</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#video">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="test">
+<video></video>
+</div>
+<script>
+var t = async_test("Attributes shouldn't magically appear");
+on_event(window, "load", t.step_func(function() {
+ var el = document.getElementById("test").getElementsByTagName("video")[0];
+ assert_equals(el.hasAttribute("tabindex"), false);
+ t.done()
+}))
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_content-ref.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_content-ref.htm
new file mode 100644
index 0000000000..c02abb1236
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_content-ref.htm
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>HTML5 Media Elements: Content inside the 'video' element is not shown to the user.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ </head>
+ <body>
+ <div id='testcontent'>
+ </div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_content_image.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_content_image.htm
new file mode 100644
index 0000000000..0808d894aa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_content_image.htm
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>HTML5 Media Elements: Content inside the 'video' element is not shown to the user.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#video" />
+ <link rel="match" href="video_content-ref.htm" />
+ <meta name="assert" content="Content inside the 'video' element is not shown to the user (image)." />
+ </head>
+ <body>
+ <div id='testcontent'>
+ <video><img src="../../../../images/fail.gif" /></video>
+ </div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_content_text.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_content_text.htm
new file mode 100644
index 0000000000..639fb73f8f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_content_text.htm
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>HTML5 Media Elements: Content inside the 'video' element is not shown to the user.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#video" />
+ <link rel="match" href="video_content-ref.htm" />
+ <meta name="assert" content="Content inside the 'video' element is not shown to the user." />
+ </head>
+ <body>
+ <div id='testcontent'>
+ <video><p style="color: red;">FAIL</p></video>
+ </div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_crash_empty_src.html b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_crash_empty_src.html
new file mode 100644
index 0000000000..3aecb4e1eb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_crash_empty_src.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Media Elements: An empty src should not crash the player.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ <link rel="author" title="Alicia Boya García" href="mailto:aboya@igalia.com"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+ function makeCrashTest(src) {
+ async_test((test) => {
+ const video = document.createElement("video");
+ video.src = src;
+ video.controls = true;
+ video.addEventListener("error", () => {
+ document.body.removeChild(video);
+ test.done();
+ });
+ document.body.appendChild(video);
+ }, `src="${src}" does not crash.`);
+ }
+
+ makeCrashTest("about:blank");
+ makeCrashTest("");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_dynamic_poster-ref.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_dynamic_poster-ref.htm
new file mode 100644
index 0000000000..78c03626e1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_dynamic_poster-ref.htm
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<meta charset=UTF-8>
+<title>Reference for poster tests</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<img src="/media/poster.png">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_dynamic_poster_absolute.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_dynamic_poster_absolute.htm
new file mode 100644
index 0000000000..bec2b0fba7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_dynamic_poster_absolute.htm
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<meta charset=UTF-8>
+<title>The 'HTMLVideoElement' interface supports setting 'poster' to an absolute URL</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-video-poster">
+<link rel="match" href="video_dynamic_poster-ref.htm">
+<meta name="assert" content="The 'HTMLVideoElement' interface supports setting 'poster' to an absolute URL">
+<video id="video0">Your browser does not support video.</video>
+<script>
+var testElem = document.getElementById("video0");
+testElem.poster = "/media/poster.png";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_dynamic_poster_relative.htm b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_dynamic_poster_relative.htm
new file mode 100644
index 0000000000..4faca61c40
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_dynamic_poster_relative.htm
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<meta charset=UTF-8>
+<title>The 'HTMLVideoElement' interface supports setting 'poster' to a relative URL</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-video-poster">
+<link rel="match" href="video_dynamic_poster-ref.htm">
+<meta name="assert" content="The 'HTMLVideoElement' interface supports setting 'poster' to a relative URL">
+<video id="video0">Your browser does not support video.</video>
+<script>
+var testElem = document.getElementById("video0");
+testElem.poster = "../../../../media/poster.png";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_initially_paused-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_initially_paused-ref.html
new file mode 100644
index 0000000000..8556aabf23
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_initially_paused-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<meta charset=UTF-8>
+<title>Video elements should initially be paused</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-paused">
+<script src="/common/media.js"></script>
+<p>The following video element should be paused. (All clocks at zero).</p>
+<img src='/images/movie_300_frame_0.png'>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_initially_paused.html b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_initially_paused.html
new file mode 100644
index 0000000000..b2725b04aa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_initially_paused.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<meta charset=UTF-8>
+<title>Video elements should initially be paused</title>
+<link rel="match" href="video_initially_paused-ref.html">
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-media-paused">
+<script src="/common/media.js"></script>
+<style>
+div#video {
+ padding: 6px 3px;
+}
+</style>
+<p>The following video element should be paused. (All clocks at zero).</p>
+<div id=video>
+<script>
+document.write(
+ "<video src='"+ getVideoURI('/media/movie_300') + "' >" +
+ "Your browser does not support the video element." +
+ "<\/video>");
+</script>
+</div>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_size_preserved_after_ended.html b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_size_preserved_after_ended.html
new file mode 100644
index 0000000000..9b2783c930
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-video-element/video_size_preserved_after_ended.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>HTML5 Media Elements: The size of the video shouldn't be lost after an 'ended' event.</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ <link rel="author" title="Alicia Boya García" href="mailto:aboya@igalia.com"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<video id="video">
+ <source src="/media/test-1s.mp4" type="video/mp4">
+ <source src="/media/test-1s.webm" type="video/webm">
+</video>
+<script>
+ promise_test(async (test) => {
+ const eventWatcher = new EventWatcher(test, video, ["loadedmetadata", "ended"]);
+ await eventWatcher.wait_for("loadedmetadata");
+ assert_equals(video.videoWidth, 320, "width when the video is loaded");
+ assert_equals(video.videoHeight, 240, "height when the video is loaded");
+ video.play();
+ await eventWatcher.wait_for(["ended"]);
+ assert_equals(video.videoWidth, 320, "width after playback");
+ assert_equals(video.videoHeight, 240, "height after playback");
+ if (video.videoTracks)
+ assert_equals(video.videoTracks.length, 1);
+ }, "Video dimensions are preserved at the end of the video.");
+</script>
+</body>
+</html>
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.html b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-ltr.html
new file mode 100644
index 0000000000..7bb7aae3b9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-ltr.html
@@ -0,0 +1,30 @@
+<!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>
+<script src="resources/dirname.js"></script>
+<div id="log"></div>
+<form action="resources/dirname-iframe.html" method=get target="iframe">
+ <p><label>User: <input type=text name="user" dirname="user.dir" required></label></p>
+ <p><label>Comment: <textarea name="comment" dirname="comment.dir" required></textarea></label></p>
+ <p><button type=submit>Post Comment</button></p>
+</form>
+<iframe name="iframe"></iframe>
+<script>
+ document.querySelector("input").value = "foobar";
+ document.querySelector("textarea").value = "foobar";
+ document.querySelector("button").click();
+
+ var t_inp = async_test("submit input element directionality");
+ onIframeLoadedDone(t_inp, function(params) {
+ assert_equals(params.get("user.dir"), "ltr");
+ });
+
+ var t_ta = async_test("submit textarea element directionality");
+ onIframeLoadedDone(t_ta, function(params) {
+ assert_equals(params.get("comment.dir"), "ltr");
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-only-if-applies.html b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-only-if-applies.html
new file mode 100644
index 0000000000..8af6826fa1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-only-if-applies.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Submitting element directionality: the dirname attribute</title>
+ <link rel="author" title="Vincent Hilla" href="mailto:vhilla@mozilla.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>
+ <script src="resources/dirname.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+ <form action="resources/dirname-iframe.html" method=get target="iframe">
+ <textarea name="textarea" dirname="textarea.dir"></textarea>
+ <p><input id="btn-submit" type=submit name=submit dirname=submit.dir value=Submit></p>
+ </form>
+ <iframe name="iframe"></iframe>
+
+ <script>
+ const types_applies = [
+ "hidden", "text", "search", "tel", "url", "email", "password", "submit"
+ ];
+ const types_not = [
+ "date", "month", "week", "time", "datetime-local", "number", "range", "color", "checkbox",
+ "radio", "file", "image", "reset", "button"
+ ];
+ const types = [...types_applies, ...types_not];
+ let form = document.querySelector("form");
+ for (const type of types) {
+ if (type === "submit") {
+ continue;
+ }
+ let p = document.createElement("p");
+ let lbl = document.createElement("label");
+ let txt = document.createTextNode(type + ": ");
+ let inp = document.createElement("input");
+ inp.type = type;
+ inp.name = type;
+ inp.dirName = type + ".dir";
+ inp.id = "testelement." + type
+ lbl.appendChild(txt);
+ lbl.appendChild(inp);
+ p.appendChild(lbl);
+ form.appendChild(p);
+ }
+ // Avoid continue in https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-form-data-set:attr-fe-dirname
+ document.getElementById("testelement.checkbox").checked = true;
+ document.getElementById("testelement.radio").checked = true;
+
+ function assertInputSubmission(data) {
+ for (const type of types_applies) {
+ assert_equals(data.get(type + ".dir"), "ltr", "Submit ltr for input type=" + type);
+ }
+ for (const type of types_not) {
+ assert_false(data.has(type + ".dir"), "Do not submit dir for input type=" + type);
+ }
+ }
+
+ const submitter = document.getElementById("btn-submit");
+ const data = new FormData(form, submitter);
+ test(function() {
+ assertInputSubmission(data);
+ }, "Submit input element directionality to FormData, if dirname applies.");
+ test(function() {
+ assert_equals(data.get("textarea.dir"), "ltr", "Submit ltr for textarea");
+ }, "Submit textarea element directionality to FormData.");
+
+ submitter.click();
+ const t_inp = async_test("Submit input element directionality, if dirname applies.");
+ onIframeLoadedDone(t_inp, function(params) {
+ assertInputSubmission(params);
+ });
+ const t_ta = async_test("Submit textarea element directionality.");
+ onIframeLoadedDone(t_ta, function(params) {
+ assert_equals(params.get("textarea.dir"), "ltr", "Submit ltr for textarea");
+ });
+ </script>
+ </body>
+</html>
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..02aeb79176
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-auto.html
@@ -0,0 +1,31 @@
+<!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>
+<script src="resources/dirname.js"></script>
+<div id="log"></div>
+<form action="resources/dirname-iframe.html" method=get target="iframe">
+ <p><label>User: <input type=text name="user" dir="auto" dirname="user.dir" required/></label></p>
+ <p><label>Comment: <textarea name="comment" dir="auto" dirname="comment.dir" required></textarea></label></p>
+ <p><button type=submit>Post Comment</button></p>
+</form>
+<iframe name="iframe"></iframe>
+<script>
+ var rtlValue = "مرحبا";
+ document.querySelector("input").value = rtlValue;
+ document.querySelector("textarea").value = rtlValue;
+ document.querySelector("button").click();
+
+ var t_inp = async_test("submit input element directionality");
+ onIframeLoadedDone(t_inp, function(params) {
+ assert_equals(params.get("user.dir"), "rtl");
+ });
+
+ var t_ta = async_test("submit textarea element directionality");
+ onIframeLoadedDone(t_ta, function(params) {
+ assert_equals(params.get("comment.dir"), "rtl");
+ });
+</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..0c5331f0ac
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-inherited.html
@@ -0,0 +1,32 @@
+<!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>
+<script src="resources/dirname.js"></script>
+<div id="log"></div>
+<div dir="rtl">
+ <form action="resources/dirname-iframe.html" method=get target="iframe">
+ <p><label>User: <input type=text name="user" dirname="user.dir" required/></label></p>
+ <p><label>Comment: <textarea name="comment" dirname="comment.dir" required></textarea></label></p>
+ <p><button type=submit>Post Comment</button></p>
+ </form>
+</div>
+<iframe name="iframe"></iframe>
+<script>
+ document.querySelector("input").value = "foobar";
+ document.querySelector("textarea").value = "foobar";
+ document.querySelector("button").click();
+
+ var t_inp = async_test("submit input element directionality");
+ onIframeLoadedDone(t_inp, function(params) {
+ assert_equals(params.get("user.dir"), "rtl");
+ });
+
+ var t_ta = async_test("submit textarea element directionality");
+ onIframeLoadedDone(t_ta, function(params) {
+ assert_equals(params.get("comment.dir"), "rtl");
+ });
+</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..c5f683101d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/dirname-rtl-manual.html
@@ -0,0 +1,29 @@
+<!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>User: <input type=text name="user" dirname="user.dir" required></label></p>
+ <p><label>Comment: <textarea name="comment" dirname="comment.dir" required></textarea></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 textarea, 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 userDir = getParameterByName("user.dir");
+ var commentDir = getParameterByName("comment.dir");
+ if (commentDir && userDir) {
+ var p = document.createElement("p");
+ var success = (commentDir == "rtl" && userDir == "rtl")
+ p.textContent = success ? "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/attributes-common-to-form-controls/resources/dirname-iframe.html b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/resources/dirname-iframe.html
new file mode 100644
index 0000000000..b5ed7e3d9a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/resources/dirname-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/resources/dirname.js b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/resources/dirname.js
new file mode 100644
index 0000000000..f0e97bc301
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/attributes-common-to-form-controls/resources/dirname.js
@@ -0,0 +1,12 @@
+function onIframeLoadedDone(t, cb, selector="iframe") {
+ const iframe = document.querySelector(selector);
+ iframe.addEventListener("load", 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; }
+
+ const params = new URLSearchParams(iframe.contentWindow.location.search);
+ t.step(() => cb(params))
+ t.done();
+ });
+}
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..286001b965
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/form-validation-validity-patternMismatch.html
@@ -0,0 +1,53 @@
+<!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: "\x41\x42\x43"}, 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: "[(]", value: "x"}, expected: false, name: "[target] Invalid `v` 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: "\x41\x42\x43,\x41\x42\x43"}, 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: "[(]", value: "x"}, expected: false, name: "[target] Invalid `v` 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.tentative.html b/testing/web-platform/tests/html/semantics/forms/constraints/infinite_backtracking.tentative.html
new file mode 100644
index 0000000000..52f6e316b1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/infinite_backtracking.tentative.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-maxlength-emoji.html b/testing/web-platform/tests/html/semantics/forms/constraints/input-maxlength-emoji.html
new file mode 100644
index 0000000000..068abad936
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/constraints/input-maxlength-emoji.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tests pasting an emoji in a text field with a maxlength attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://bugs.webkit.org/show_bug.cgi?id=252900">
+<input id="target" type="text" maxlength="10" />
+<script>
+test(function() {
+ let target = document.getElementById("target");
+ target.focus();
+ document.execCommand("InsertHTML", false, "👨‍👩‍👧‍👦");
+ assert_equals(target.value, "👨‍👩‍👧‍");
+}, "Emoji gets truncated due to maxlength attribute");
+</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..4890fd8623
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/FormDataEvent.window.js
@@ -0,0 +1,21 @@
+// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-formdataevent-interface
+
+test(() => {
+ let fd = new FormData();
+ assert_throws_js(TypeError, () => { FormDataEvent('', {formData:fd}) }, "Calling FormDataEvent constructor without 'new' must throw");
+ 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-requestsubmit.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-requestsubmit.html
new file mode 100644
index 0000000000..b4028784ed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-requestsubmit.html
@@ -0,0 +1,142 @@
+<!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 onclick requestSubmit() should get superseded by the default
+ action submit, which isn't preventDefaulted by onclick here.
+ This is per the Form Submission Algorithm [1], which
+ says that new planned navigations replace old planned navigations.
+ [1] https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#planned-navigation
+ -->
+
+<body>
+ <script>
+ function runTest({ submitterType, preventDefaultSubmitButton, preventDefaultRequestSubmit, passSubmitter, testName }) {
+ if (preventDefaultRequestSubmit && preventDefaultSubmitButton) {
+ // In this case, no submit action will take place.
+ return;
+ }
+
+ promise_test(async () => {
+ const form = populateForm(`<input name=n1 value=v1><${submitterType} type=submit name=n2 value=v2>${submitterType == 'button' ? '</button>' : ''}`);
+ const input = form.elements[0];
+ const submitter = form.elements[1];
+ submitter.addEventListener('click', e => {
+ form.addEventListener('submit', e => {
+ submitter.value = 'v3';
+ if (preventDefaultRequestSubmit) {
+ e.preventDefault();
+ }
+ }, { once: true });
+
+ form.requestSubmit(passSubmitter ? submitter : null);
+ input.value = 'v2';
+
+ form.addEventListener('submit', e => {
+ submitter.value = 'v4';
+ if (preventDefaultSubmitButton) {
+ e.preventDefault();
+ }
+ }, { once: true });
+ });
+
+ let formDataInEvent;
+ form.addEventListener('formdata', e => {
+ formDataInEvent = e.formData;
+ });
+
+ submitter.click();
+ assert_equals(formDataInEvent.get('n1'), preventDefaultSubmitButton ? 'v1' : 'v2');
+ if (preventDefaultSubmitButton && !passSubmitter) {
+ assert_false(formDataInEvent.has('n2'));
+ } else {
+ assert_equals(formDataInEvent.get('n2'),
+ preventDefaultSubmitButton && passSubmitter ? 'v3' : 'v4')
+ }
+
+ let iframe = form.previousSibling;
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, 'n1'), preventDefaultSubmitButton ? 'v1' : 'v2');
+ if (preventDefaultSubmitButton && !passSubmitter) {
+ assert_equals(getParamValue(iframe, 'n2'), null);
+ } else {
+ assert_equals(getParamValue(iframe, 'n2'),
+ preventDefaultSubmitButton && passSubmitter ? 'v3' : 'v4');
+ }
+ }, testName);
+ }
+
+ function runTest2({ submitterType, callRequestSubmit, callSubmit, preventDefault, passSubmitter, testName }) {
+ if (!callSubmit && preventDefault) {
+ // Without callSubmit, preventDefault will cause the form to not get
+ // submitted.
+ return;
+ }
+
+ promise_test(async () => {
+ const form = populateForm(`<input name=n1 value=v1><${submitterType} type=submit name=n2 value=v3>${submitterType == 'button' ? '</button>' : ''}`);
+ const input = form.elements[0];
+ const submitter = form.elements[1];
+
+ form.addEventListener('submit', e => {
+ if (callRequestSubmit) {
+ form.requestSubmit(passSubmitter ? submitter : null);
+ input.value = 'v2';
+ }
+ if (callSubmit) {
+ form.submit();
+ }
+ if (preventDefault) {
+ e.preventDefault();
+ }
+ });
+
+ form.requestSubmit(passSubmitter ? submitter : null);
+ let iframe = form.previousSibling;
+ await loadPromise(iframe);
+
+ assert_equals(getParamValue(iframe, 'n1'), callRequestSubmit ? 'v2' : 'v1');
+ if (callSubmit || !passSubmitter) {
+ assert_equals(getParamValue(iframe, 'n2'), null);
+ } else {
+ assert_equals(getParamValue(iframe, 'n2'), 'v3')
+ }
+ }, testName);
+ }
+
+ function callWithArgs(test, argsLeft, args) {
+ if (argsLeft.length == 0) {
+ args.testName = 'test ' + test.name + ' with ' + Object.entries(args).map(([key, value]) => `${key}: ${value}`).join(', ');
+ test(args);
+ return;
+ }
+
+ let [name, values] = argsLeft[0];
+ for (let value of values) {
+ callWithArgs(test, argsLeft.slice(1), { ...args, [name]: value })
+ }
+ }
+
+ let args = {
+ submitterType: ['input', 'button'],
+ preventDefaultRequestSubmit: [true, false],
+ preventDefaultSubmitButton: [true, false],
+ passSubmitter: [true, false],
+ };
+ callWithArgs(runTest, Object.entries(args), {});
+
+ args = {
+ submitterType: ['input', 'button'],
+ callRequestSubmit: [true, false],
+ callSubmit: [true, false],
+ preventDefault: [true, false],
+ passSubmitter: [true, false],
+ };
+ callWithArgs(runTest2, Object.entries(args), {});
+ </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..ca69c7dac7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/multipart-formdata.window.js
@@ -0,0 +1,357 @@
+// 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",
+});
+
+formTest({
+ name: "\uD800",
+ value: "\uD800",
+ formEncoding: "windows-1252",
+ expected: {
+ name: "&#65533;",
+ value: "&#65533;"
+ },
+ description: "lone surrogate in name and value",
+});
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..9ff77aed12
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/text-plain.window.js
@@ -0,0 +1,223 @@
+// 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",
+});
+
+formTest({
+ name: "\uD800",
+ value: "\uD800",
+ formEncoding: "windows-1252",
+ expected: "&#65533;=&#65533;\r\n",
+ description: "lone surrogate in name and value",
+});
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..7818f68619
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/urlencoded2.window.js
@@ -0,0 +1,223 @@
+// 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",
+});
+
+formTest({
+ name: "\uD800",
+ value: "\uD800",
+ formEncoding: "windows-1252",
+ expected: "%26%2365533%3B=%26%2365533%3B",
+ description: "lone surrogate in name and value",
+});
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..d1b46a22d8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/select-event.html
@@ -0,0 +1,154 @@
+<!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 waitForEvents() {
+ // Engines differ in when these events are sent (see:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1785615) so wait for both a
+ // frame to be rendered, and a timeout.
+ return new Promise(resolve => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ setTimeout(() => {
+ resolve();
+ });
+ });
+ });
+ });
+}
+
+function initialize(el) {
+ el.setRangeText("foobar", 0, el.value.length, "start");
+ // Make sure to flush async dispatches
+ return waitForEvents();
+}
+
+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 waitForEvents();
+ 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 waitForEvents();
+ 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 waitForEvents();
+ 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 waitForEvents();
+ 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..063836cd23
--- /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="?default">
+<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 !== "?default") {
+ // For non default case, use the parameters passed in through query string
+ // 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..86b92402e4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-click-submits.html
@@ -0,0 +1,232 @@
+<!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 => {
+ 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");
+
+async_test(t => {
+
+ const form = document.createElement("form");
+ const button = document.createElement("button");
+ button.disabled = true;
+ 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 => {
+ assert_true(true, "span was clicked");
+ t.step_timeout(() => t.done(), 500);
+ }));
+
+ span.click();
+
+}, "clicking the child of a disabled button with .click() 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..9cec2bd875
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-button-element/button-submit-children.html
@@ -0,0 +1,58 @@
+<!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">
+ <div id=parentdiv>
+ <button id=submitbutton type=submit>
+ <div id=buttonchilddiv>
+ button child div text
+ </div>
+ </button>
+ </div>
+</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.');
+
+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 => {
+ const form = document.getElementById('form1');
+ form.submit();
+ });
+
+ const parentDiv = document.getElementById("parentdiv");
+ parentDiv.addEventListener("click", event => {
+ // event was already handled for the button
+ // but it's activation behavior won't have run yet and we prevent that now
+ event.preventDefault();
+ })
+
+ submitButton.click();
+ });
+}, "clicking a submit button, which calls form.submit and has a parent calling e.prevenDefault() should still submit the form");
+</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/disabled-003.html b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/disabled-003.html
new file mode 100644
index 0000000000..de01eb57fc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-fieldset-element/disabled-003.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Disable nested fieldsets with focused element</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-fieldset-element">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1427047">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id=container1>
+ <fieldset id=target1>
+ <legend>foo</legend>
+ <fieldset>
+ <legend>bar</legend>
+ <input id=input1>
+ </fieldset>
+ </fieldset>
+</div>
+<script>
+test(() => {
+ input1.focus();
+ target1.disabled = true;
+ assert_not_equals(document.activeElement, input1);
+}, 'Disable light-nested fieldsets should not crash');
+</script>
+
+<div id=container2></div>
+<script>
+test(() => {
+ let n = 100;
+ let markup = '<fieldset><legend>foo</legend>'.repeat(n) +
+ '<input id=input2>' + '</fieldset>'.repeat(n);
+ container2.innerHTML = markup;
+ input2.focus();
+ container2.firstChild.disabled = true;
+ assert_not_equals(document.activeElement, input2);
+}, 'Disable deep-nested fieldsets should not hang');
+</script>
+
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-shadow.html b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-indexed-element-shadow.html
new file mode 100644
index 0000000000..a108ce8a93
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-form-element/form-indexed-element-shadow.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>form.elements: indexed access reflects DOM order, not flat tree</title>
+<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>
+<form id="target">
+ <div id="host">
+ <template shadowrootmode="open">
+ <slot name="first"></slot>
+ <slot name="second"></slot>
+ </template>
+ <input id="first" slot="second">
+ <input id="second" slot="first">
+ </div>
+</form>
+<script>
+test(function() {
+ let target = document.getElementById("target");
+ let host = document.getElementById("host");
+ assert_true(!!host.shadowRoot, "Should have a shadow tree");
+ assert_equals(target.elements[0], first, "form.elements reflects DOM order, not flat tree order");
+ assert_equals(target.elements[1], second);
+});
+</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/auto-direction-dynamic.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/auto-direction-dynamic.html
new file mode 100644
index 0000000000..68ea51185c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/auto-direction-dynamic.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<link rel="match" href="auto-direction-ref.html">
+<body>
+<script>
+const dirInput = document.createElement('input')
+dirInput.setAttribute('dir', 'auto')
+dirInput.setAttribute('value', 'شنينسنمس')
+document.body.appendChild(dirInput)
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/auto-direction-ref.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/auto-direction-ref.html
new file mode 100644
index 0000000000..675ba50914
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/auto-direction-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<input dir="auto" value="شنينسنمس">
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/change-to-empty-value.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/change-to-empty-value.html
new file mode 100644
index 0000000000..fd1eeb4584
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/change-to-empty-value.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Change event when clearing an input</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=1881457">
+<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="text" value="abc">
+<script>
+promise_test(async function() {
+ let input = document.querySelector("input");
+ let changeFired = false;
+ input.addEventListener("change", () => {
+ changeFired = true;
+ }, { once: true });
+ input.focus();
+ assert_equals(document.activeElement, input, "Should focus input");
+ assert_false(changeFired, "Shouldn't have fired change event after focus");
+ input.select();
+ const kBackspaceKey = "\uE003";
+ await test_driver.send_keys(input, kBackspaceKey)
+ assert_false(changeFired, "Shouldn't have fired change event after select");
+ input.blur();
+ assert_true(changeFired, "Should've have fired change event after blur");
+ assert_equals(input.value, "", "Should've have cleared the value");
+});
+</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-on-blur.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/focus-dynamic-type-change-on-blur.html
new file mode 100644
index 0000000000..23292d3a83
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/focus-dynamic-type-change-on-blur.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Input type switch on blur event should clean up properly</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, capture) {
+ 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() {
+ input.type = toType;
+ }
+ input.addEventListener("focus", onFocus);
+ input.addEventListener("blur", onBlur, capture);
+ await tick();
+
+ assert_equals(document.activeElement, input, `${fromType} input should still be focused after tick`);
+ assert_true(input.matches(":focus"), `${fromType} input should match :focus`);
+ assert_true(input.matches(":focus-visible"), `${fromType} input should match :focus-visible`);
+
+ input.blur();
+
+ assert_equals(document.activeElement, document.body, `${fromType} input should not remain focused after blur`);
+ assert_false(input.matches(":focus"), `${fromType} input should not match :focus`);
+ assert_false(input.matches(":focus-visible"), `${fromType} input should not match :focus-visible`);
+ assert_equals(input.type, toType, `${fromType} input should have changed to ${toType}`);
+ input.removeEventListener("focus", onFocus);
+ input.removeEventListener("blur", onBlur);
+ }, `${fromType} -> ${toType} ${capture}`);
+}
+
+for (let type of inputTypes) {
+ if (type == "hidden") {
+ continue; // hidden inputs are not focusable
+ }
+ for (let capture of [true, false]) {
+ test_from_to(type, "text", capture);
+ test_from_to("text", type, capture);
+ }
+}
+</script>
+</body>
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..982cda6c92
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/focus-dynamic-type-change.html
@@ -0,0 +1,55 @@
+<!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}`);
+ assert_true(input.matches(":focus"), `${fromType} input should still match :focus`);
+ assert_true(input.matches(":focus-visible"), `${fromType} input should still match :focus-visible`);
+ await tick();
+ assert_equals(document.activeElement, input, `${fromType} input should still be focused after change to ${toType}`);
+ assert_true(input.matches(":focus"), `${fromType} input should still match :focus`);
+ assert_true(input.matches(":focus-visible"), `${fromType} input should still match :focus-visible`);
+ 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-disabled-fieldset-dynamic.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-disabled-fieldset-dynamic.html
new file mode 100644
index 0000000000..eefcd972bf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-disabled-fieldset-dynamic.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<meta charset="utf-8">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1861027">
+<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>
+<fieldset id="fieldset" disabled>
+ <input id="target">
+</fieldset>
+<script>
+const target = document.getElementById("target");
+const fieldset = document.getElementById("fieldset");
+promise_test(async function() {
+ await new Promise(r => window.addEventListener("load", r, { once: true }));
+ assert_true(target.matches(":disabled"), "Fieldset disables the input");
+ assert_true(target.matches(":read-only"), "Disabled implies read-only");
+
+ // Try to focus, it shouldn't be focusable.
+ target.focus();
+
+ assert_not_equals(document.activeElement, target, "Should not be focusable");
+
+ fieldset.removeAttribute("disabled");
+
+ assert_false(target.matches(":disabled"), "Should go back to writable");
+ assert_false(target.matches(":read-only"), "No longer read-only");
+
+ // Should be focusable now.
+ target.focus();
+
+ assert_equals(document.activeElement, target, "Should not be focusable");
+
+ await test_driver.send_keys(target, "A");
+ assert_equals(target.value, "A", "Typing should work");
+});
+</script>
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-importNode-to-detached-document-crash.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-importNode-to-detached-document-crash.html
new file mode 100644
index 0000000000..5e0cff4fa3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-importNode-to-detached-document-crash.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<body>
+<p>This test passes if it does not crash.</p>
+<input id="input" type="image" src="data:image/gif;base64,">
+<iframe id="iframe"></iframe>
+<script>
+let i_doc = iframe.contentDocument;
+iframe.remove();
+i_doc.importNode(input);
+</script>
+</body>
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-change-empty-crash.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-change-empty-crash.html
new file mode 100644
index 0000000000..6e44250ccb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-change-empty-crash.html
@@ -0,0 +1,8 @@
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+ a.type = "foo"
+ document.execCommand("insertHorizontalRule", false)
+ a.type = "text"
+})
+</script>
+<input id="a" type="color">
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-change-value.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-change-value.html
new file mode 100644
index 0000000000..74aeef7cd5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-change-value.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Input type switch from / to color</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://html.spec.whatwg.org/#input-type-change">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1833477">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+function runTest(focus) {
+ let input = document.createElement("input");
+ input.type = "color";
+ document.body.appendChild(input);
+ if (focus) {
+ input.focus();
+ }
+ assert_equals(input.value, "#000000", "Invalid color should return a non-empty sanitized value");
+ input.type = "text";
+ assert_equals(input.value, "", "Value dirty flag should remain false");
+ input.type = "color";
+ input.value = "#ffffff";
+ assert_equals(input.value, "#ffffff", "Valid color is returned");
+ input.type = "text";
+ assert_equals(input.value, "#ffffff", "Value dirty flag should remain true");
+ if (focus) {
+ assert_equals(document.activeElement, input, "Focus is preserved");
+ }
+}
+test(() => runTest(false), "Without focus");
+test(() => runTest(true), "With focus");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.js b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.js
new file mode 100644
index 0000000000..6128a62a0f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.js
@@ -0,0 +1,19 @@
+test(t => {
+ const input = document.createElement("input");
+ input.switch = true;
+
+ assert_true(input.hasAttribute("switch"));
+ assert_equals(input.getAttribute("switch"), "");
+ assert_equals(input.type, "text");
+}, "switch IDL attribute, setter");
+
+test(t => {
+ const container = document.createElement("div");
+ container.innerHTML = "<input type=checkbox switch>";
+ const input = container.firstChild;
+
+ assert_true(input.hasAttribute("switch"));
+ assert_equals(input.getAttribute("switch"), "");
+ assert_equals(input.type, "checkbox");
+ assert_true(input.switch);
+}, "switch IDL attribute, getter");
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-type-number-rtl-invalid-crash.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-number-rtl-invalid-crash.html
new file mode 100644
index 0000000000..d749d1faad
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/input-type-number-rtl-invalid-crash.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<meta charset="utf-8">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1835437">
+<script>
+window.onload = () => {
+ a.stepDown(251)
+ document.execCommand("delete", false, null)
+}
+</script>
+<form lang="ar-SA">
+<input id="a" type="number" autofocus dir="auto">
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-constraint-validation.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/number-constraint-validation.html
new file mode 100644
index 0000000000..959726bb50
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/number-constraint-validation.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Form input type=number constraint validation</title>
+<link rel="author" title="Adam Vandolder" href="mailto:avandolder@mozilla.com">
+<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>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<div id="log"></div>
+<input type="number">
+<script>
+ const input = document.querySelector("input");
+ const invalidInputNumber = "1.e";
+ const invalidSetNumber = "1.";
+
+ promise_test(async () => {
+ await test_driver.click(input);
+ await test_driver.send_keys(input, invalidInputNumber);
+ assert_equals(input.value.length, 0);
+ assert_true(input.validity.badInput);
+ }, "Unparsable number user input triggers sanitization and causes badInput to be set.");
+
+ promise_test(async () => {
+ input.value = invalidInputNumber;
+ assert_equals(input.value.length, 0);
+ assert_false(input.validity.badInput);
+ }, "Setting .value to an unparsable number triggers sanitization but doesn't set badInput.");
+
+ promise_test(async () => {
+ await test_driver.click(input);
+ await test_driver.send_keys(input, invalidSetNumber);
+ assert_equals(input.value, "1");
+ assert_false(input.validity.badInput);
+ }, "Users inputting a parsable but invalid floating point number doesn't trigger sanitization and doesn't set badInput.");
+
+ promise_test(async () => {
+ input.value = invalidSetNumber;
+ assert_equals(input.value.length, 0);
+ assert_false(input.validity.badInput);
+ }, "Setting .value to a parsable but invalid floating point number triggers sanitization but doesn't set badInput.");
+</script>
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..93cbd2caec
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/pattern_attribute.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>pattern attribute</title>
+<meta name=viewport content="width=device-width">
+<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="Mathias Bynens" href="https://mathiasbynens.be/">
+<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>
+<h1><code>pattern</code> attribute</h1>
+<div style="display: none">
+ <input pattern="[a-z]{3}" value="abcd" id="basic">
+
+ <input pattern="a.b" value="a&#x1D306;b" id="unicode-code-points">
+ <input pattern="\p{ASCII_Hex_Digit}+" value="c0ff33" id="unicode-property">
+
+ <input pattern="\p{RGI_Emoji}+" value="&#x1F618;&#x1F48B;" id="unicode-property-of-strings">
+ <input pattern="[\p{ASCII_Hex_Digit}--[Ff]]" value="0123456789abcdefABCDEF" id="set-difference">
+ <input pattern="[_\q{a|bc|def}]" value="q" id="string-literal">
+
+ <div class="breaking-changes-from-u-to-v">
+ <!-- Unescaped special characters in character classes. -->
+ <input pattern="[(]" value="foo">
+ <input pattern="[)]" value="foo">
+ <input pattern="[[]" value="foo">
+ <input pattern="[{]" value="foo">
+ <input pattern="[}]" value="foo">
+ <input pattern="[/]" value="foo">
+ <input pattern="[-]" value="foo">
+ <input pattern="[|]" value="foo">
+ <!-- Double punctuators in character classes. -->
+ <input pattern="[&&]" value="foo">
+ <input pattern="[!!]" value="foo">
+ <input pattern="[##]" value="foo">
+ <input pattern="[$$]" value="foo">
+ <input pattern="[%%]" value="foo">
+ <input pattern="[**]" value="foo">
+ <input pattern="[++]" value="foo">
+ <input pattern="[,,]" value="foo">
+ <input pattern="[..]" value="foo">
+ <input pattern="[::]" value="foo">
+ <input pattern="[;;]" value="foo">
+ <input pattern="[<<]" value="foo">
+ <input pattern="[==]" value="foo">
+ <input pattern="[>>]" value="foo">
+ <input pattern="[??]" value="foo">
+ <input pattern="[@@]" value="foo">
+ <input pattern="[``]" value="foo">
+ <input pattern="[~~]" value="foo">
+ <input pattern="[_^^]" value="foo">
+ </div>
+</div>
+<div id="log"></div>
+<script>
+ test(() => {
+ const input = document.querySelector("#basic");
+
+ assert_idl_attribute(input, "pattern");
+ assert_equals(input.pattern, "[a-z]{3}");
+
+ assert_inherits(input, "validity");
+ assert_false(input.validity.valid);
+ assert_true(input.validity.patternMismatch);
+
+ assert_true(input.matches(":invalid"));
+ }, "basic <input pattern> support");
+
+ test(() => {
+ const input = document.querySelector("#unicode-code-points");
+ assert_true(input.validity.valid);
+ assert_true(input.matches(":valid"));
+ assert_false(input.validity.patternMismatch);
+ }, "<input pattern> is Unicode code point-aware");
+
+ test(() => {
+ const input = document.querySelector("#unicode-property");
+ assert_true(input.validity.valid);
+ assert_true(input.matches(":valid"));
+ assert_false(input.validity.patternMismatch);
+ }, "<input pattern> supports Unicode property escape syntax");
+
+ test(() => {
+ const input = document.querySelector("#unicode-property-of-strings");
+ assert_true(input.validity.valid);
+ assert_true(input.matches(":valid"));
+ assert_false(input.validity.patternMismatch);
+ }, "<input pattern> supports Unicode property escape syntax for properties of strings");
+
+ test(() => {
+ const input = document.querySelector("#set-difference");
+ assert_false(input.validity.valid);
+ assert_false(input.matches(":valid"));
+ assert_true(input.validity.patternMismatch);
+ }, "<input pattern> supports set difference syntax");
+
+ test(() => {
+ const input = document.querySelector("#string-literal");
+ assert_false(input.validity.valid);
+ assert_true(input.validity.patternMismatch);
+ assert_true(input.matches(":invalid"));
+ }, "<input pattern> supports string literal syntax");
+
+ test(() => {
+ const inputs = document.querySelectorAll(".breaking-changes-from-u-to-v input");
+ // These examples are all written such that they’re all `:invalid`
+ // when using `u`, but would become `:valid` when using `v` because
+ // the pattern would error, in turn resulting in
+ // `validity.valid === true`.
+ for (const input of inputs) {
+ const html = input.outerHTML;
+ assert_true(input.validity.valid, `${html} should be valid`);
+ assert_true(input.matches(":valid"), `${html} should match \`:valid\``);
+ assert_false(input.validity.patternMismatch, `${html} should not trigger a pattern mismatch`);
+ }
+ }, "<input pattern> enables the RegExp v flag");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/placeholder-update-ref.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/placeholder-update-ref.html
new file mode 100644
index 0000000000..38da019539
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/placeholder-update-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<input value="content">
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/placeholder-update.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/placeholder-update.html
new file mode 100644
index 0000000000..af0c0793ee
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/placeholder-update.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<link rel="match" href="placeholder-update-ref.html">
+<body>
+<div id="app"></div>
+<script>
+const rootElement = document.getElementById("app");
+const input = document.createElement("input");
+input.placeholder = "placeholder";
+input.value = "content";
+rootElement.appendChild(input);
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-disconnected-group-owner.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-disconnected-group-owner.html
new file mode 100644
index 0000000000..60feb2b0ba
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-disconnected-group-owner.html
@@ -0,0 +1,168 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta charset="utf-8">
+ <title>Test for validity of disconnected radio buttons</title>
+</head>
+
+<body>
+ <input type="radio" name="group" id="radio1" required />
+ <input type="radio" name="group" id="radio2" checked />
+ <form>
+ <input type="radio" name="group" id="radio3" required />
+ <input type="radio" name="group" id="radio4" checked />
+ </form>
+
+ <script>
+ test(() => {
+ const radio1 = document.getElementById("radio1");
+ const radio2 = document.getElementById("radio2");
+ assert_false(radio1.validity.valueMissing, "Element should not suffer from value missing");
+
+ radio2.remove();
+ assert_true(radio1.validity.valueMissing, "Element should suffer from value missing");
+
+ radio1.checked = true;
+ radio2.required = true;
+ assert_false(radio2.validity.valueMissing, "Element should not suffer from value missing");
+
+ const radio3 = document.getElementById("radio3");
+ const radio4 = document.getElementById("radio4");
+ assert_false(radio3.validity.valueMissing, "Element should not suffer from value missing");
+
+ radio4.remove();
+ assert_true(radio3.validity.valueMissing, "Element should suffer from value missing");
+
+ radio3.checked = true;
+ assert_true(radio4.checked, "Element should remain checked");
+ assert_false(radio3.validity.valueMissing, "Element should not suffer from value missing");
+
+ document.querySelector("form").appendChild(radio2);
+ assert_false(radio3.checked, "Element should be unchecked");
+ assert_false(radio1.validity.valueMissing, "Element should not suffer from value missing");
+ }, "Removed elements are moved into separate radio groups.");
+
+ test(() => {
+ const container = document.createElement("div");
+ const radio = document.createElement("input");
+ radio.type = "radio";
+ radio.name = "group";
+ radio.id = "radio5";
+ radio.required = true;
+ container.appendChild(radio);
+ assert_true(radio.validity.valueMissing, "Element should suffer from value missing");
+
+ const outerContainer = document.createElement("div");
+ const outerRadio = document.createElement("input");
+ outerRadio.type = "radio";
+ outerRadio.name = "group";
+ outerRadio.id = "radio6";
+ outerRadio.checked = true;
+ outerContainer.appendChild(outerRadio);
+ outerContainer.appendChild(container);
+ assert_false(radio.validity.valueMissing, "Element should not suffer from value missing");
+
+ container.remove();
+ assert_true(radio.validity.valueMissing, "Element should suffer from value missing");
+ }, "Disconnected radio buttons should be contained by their tree root.");
+
+ test(() => {
+ const radioParent = document.createElement("input");
+ radioParent.type = "radio";
+ radioParent.name = "group";
+ radioParent.id = "radio-parent";
+ radioParent.required = true;
+ assert_true(radioParent.validity.valueMissing, "Element should suffer from value missing");
+
+ const radioChild = document.createElement("input");
+ radioChild.type = "radio";
+ radioChild.name = "group";
+ radioChild.id = "radio-child";
+ radioChild.checked = true;
+ assert_false(radioChild.validity.valueMissing, "Element should not suffer from value missing");
+
+ radioParent.appendChild(radioChild);
+ assert_false(radioChild.validity.valueMissing, "Element should not suffer from value missing");
+ assert_false(radioParent.validity.valueMissing, "Element should not suffer from value missing");
+
+ radioParent.checked = true;
+ assert_false(radioChild.checked, "Element should no longer be checked");
+ }, "Disconnected radio buttons can serve as radio group containers.");
+
+ test(() => {
+ const shadowHost = document.createElement("div");
+ const root = shadowHost.attachShadow({mode: "open"});
+ const container = document.createElement("div");
+ container.appendChild(shadowHost);
+
+ const radio1 = document.createElement("input");
+ radio1.type = "radio";
+ radio1.name = "group";
+ radio1.required = true;
+ const radio2 = document.createElement("input");
+ radio2.type = "radio";
+ radio2.name = "group";
+ radio2.checked = true;
+ const radio3 = document.createElement("input");
+ radio3.type = "radio";
+ radio3.name = "group";
+ radio3.required = true;
+
+ root.appendChild(radio1);
+ container.appendChild(radio3);
+ assert_true(radio1.validity.valueMissing, "Element should suffer from value missing");
+ assert_true(radio3.validity.valueMissing, "Element should suffer from value missing");
+
+ root.appendChild(radio2);
+ assert_false(radio1.validity.valueMissing, "Element should not suffer from value missing");
+ assert_true(radio3.validity.valueMissing, "Element should suffer from value missing");
+ }, "Shadow roots in disconnected trees can serve as radio group containers.");
+
+ test(() => {
+ const svgRoot = document.createElementNS("http://www.w3.org/2000/svg", "g")
+ const htmlContainer = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
+ svgRoot.appendChild(htmlContainer);
+
+ const radio1 = document.createElement("input");
+ radio1.type = "radio";
+ radio1.name = "group";
+ radio1.required = true;
+ const radio2 = document.createElement("input");
+ radio2.type = "radio";
+ radio2.name = "group";
+ radio2.checked = true;
+
+ htmlContainer.appendChild(radio1);
+ assert_true(radio1.validity.valueMissing, "Element should suffer from value missing");
+ htmlContainer.appendChild(radio2);
+ assert_false(radio1.validity.valueMissing, "Element should not suffer from value missing");
+ radio1.checked = true;
+ assert_false(radio2.checked, "Element should no longer be checked");
+ }, "Non-HTML elements in disconnected trees can serve as radio group containers.");
+
+ test(() => {
+ const fragment = document.createDocumentFragment();
+
+ const radio1 = document.createElement("input");
+ radio1.type = "radio";
+ radio1.name = "group";
+ radio1.required = true;
+ const radio2 = document.createElement("input");
+ radio2.type = "radio";
+ radio2.name = "group";
+ radio2.checked = true;
+
+ fragment.appendChild(radio1);
+ assert_true(radio1.validity.valueMissing, "Element should suffer from value missing");
+ fragment.appendChild(radio2);
+ assert_false(radio1.validity.valueMissing, "Element should not suffer from value missing");
+ radio1.checked = true;
+ assert_false(radio2.checked, "Element should no longer be checked");
+ }, "Disconnected document fragments can serve as radio group containers.");
+ </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-keyboard-navigation-order.html b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-keyboard-navigation-order.html
new file mode 100644
index 0000000000..d019ca982c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/radio-keyboard-navigation-order.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Radio button group keyboard navigation order</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>
+<form id="inside">
+ <input type="radio" name="inside" id="inside1"/>
+ <input type="radio" name="inside" id="inside2"/>
+ <input type="radio" name="inside" id="inside3"/>
+</form>
+<form id="before"></form>
+<input type="radio" form="before" name="before" id="before1"/>
+<input type="radio" form="before" name="before" id="before2"/>
+<input type="radio" form="before" name="before" id="before3"/>
+<input type="radio" form="after" name="after" id="after1"/>
+<input type="radio" form="after" name="after" id="after2"/>
+<input type="radio" form="after" name="after" id="after3"/>
+<form id="after"></form>
+<input type="radio" name="mix" id="mix1"/>
+<form id="mix"><input type="radio" name="mix" id="mix2"/></form>
+<input type="radio" name="mix" id="mix3"/>
+<input type="radio" name="doc" id="doc1"/>
+<input type="radio" name="doc" id="doc2"/>
+<input type="radio" name="doc" id="doc3"/>
+<script>
+async function pressRight() {
+ return new test_driver.Actions()
+ .keyDown("\uE014")
+ .keyUp("\uE014")
+ .send();
+}
+
+promise_test(async () => {
+ for (const groupName of ["inside", "before", "after", "mix", "doc"]) {
+ const firstInGroup = document.querySelector(`input[name="${groupName}"]`);
+ const newInput = document.createElement("input");
+ newInput.id = groupName + "New";
+ newInput.type = "radio";
+ if (groupName != "doc") {
+ newInput.setAttribute("form", groupName);
+ }
+ newInput.name = groupName;
+ firstInGroup.after(newInput);
+ }
+
+ for (const formId of ["inside", "before", "after", "mix"]) {
+ document.forms[formId].elements[0].focus();
+ for (const radio of document.forms[formId].elements) {
+ assert_equals(radio, document.activeElement, `Navigated to next radio button in form '${formId}'`);
+ await pressRight();
+ }
+ }
+
+ const radios = document.querySelectorAll("input[name='doc']");
+ radios[0].focus();
+ for (const radio of radios) {
+ assert_equals(radio, document.activeElement, `Navigated to next radio button on document`);
+ await pressRight();
+ }
+}, "Radio button keyboard navigation should proceed in tree-order.");
+</script>
+</body>
+</html>
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..27cc6abe9c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/range.html
@@ -0,0 +1,245 @@
+<!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(getComputedStyle(document.getElementById('range_basic')).overflow, "visible");
+ }, "range overflow styles");
+
+ 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..7f06ae24a2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-input-element/selection-pointer.html
@@ -0,0 +1,56 @@
+<!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();
+ const nRanges = selection.rangeCount;
+ assert_true(nRanges > 0);
+ const expectedStart = foo.childNodes[0];
+ const expectedEnd = bar.childNodes[0];
+ if (nRanges === 1) {
+ assert_equals(selection.anchorNode, expectedStart, "anchorNode");
+ assert_equals(selection.focusNode, expectedEnd, "focusNode");
+ } else {
+ // In case multiple ranges are supported, make sure the set of ranges
+ // spans the full selection, across the input.
+ const ranges = [...Array(nRanges).keys()].map(n => selection.getRangeAt(n));
+ assert_true(ranges.some(r => r.startContainer === expectedStart),"startContainer");
+ assert_true(ranges.some(r => r.endContainer === expectedEnd),"endContainer");
+ }
+ }, `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-optgroup-element/optgroup-removal.window.js b/testing/web-platform/tests/html/semantics/forms/the-optgroup-element/optgroup-removal.window.js
new file mode 100644
index 0000000000..4e4c2df77e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-optgroup-element/optgroup-removal.window.js
@@ -0,0 +1,7 @@
+test(() => {
+ const select = document.createElement("select");
+ select.innerHTML = "<optgroup><option>1<optgroup><option>2";
+ assert_equals(select.value, "1");
+ select.querySelector("optgroup").remove();
+ assert_equals(select.value, "2");
+}, "<select> needs to be updated when <optgroup> is removed");
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..0db2bf0e77
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/inserted-or-removed.html
@@ -0,0 +1,103 @@
+<!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');
+
+test (() => {
+ for (let insert_location = 0; insert_location < 3; ++insert_location) {
+ const target = document.querySelector('#by-innerHTML');
+ target.innerHTML = '<option>A</option>' +
+ '<option selected>C</option>' +
+ '<option>D</option>';
+ const refNode = target.querySelectorAll('option')[insert_location];
+
+ const opt = document.createElement('option');
+ opt.selected = true;
+ opt.textContent = 'B';
+ target.insertBefore(opt, refNode);
+ assert_equals(target.selectedOptions[0].textContent, 'B');
+ }
+}, 'If an OPTION says it is selected, it should be selected after it is inserted.');
+</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/resources/show-picker-child-iframe.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/resources/show-picker-child-iframe.html
new file mode 100644
index 0000000000..bba3989824
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/resources/show-picker-child-iframe.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Test showPicker() in an iframe</title>
+<script type=module>
+ const urlParams = new URLSearchParams(location.search);
+ const documentDomain = urlParams.get('documentDomain');
+ if (documentDomain) {
+ document.domain = documentDomain;
+ }
+
+ let securityErrors = [];
+ const select = document.createElement("select");
+ try {
+ select.showPicker();
+ } catch (error) {
+ if (error instanceof DOMException && error.name == 'SecurityError') {
+ securityErrors.push("select");
+ }
+ }
+ parent.postMessage(securityErrors.join(','), "*");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/resources/stylable-select-styles.css b/testing/web-platform/tests/html/semantics/forms/the-select-element/resources/stylable-select-styles.css
new file mode 100644
index 0000000000..a7e9a87cdf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/resources/stylable-select-styles.css
@@ -0,0 +1,18 @@
+.stylable-select-datalist {
+ 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;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ border-radius: 0.25em;
+ padding: 0.25em 0;
+ background-color: Field;
+ margin: 0;
+ inset: auto;
+ min-inline-size: anchor-size(self-inline);
+ min-block-size: 1lh;
+ inset-block-start: anchor(self-end);
+ inset-inline-start: anchor(self-start);
+
+ font-family: Arial;
+ font-size: 13.3333px;
+}
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-add-optgroup.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-add-optgroup.html
new file mode 100644
index 0000000000..fab5332f26
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-add-optgroup.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1477785">
+<link rel=help href="https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmloptionscollection-add">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<select>
+ <option id=opt1>opt1</option>
+ <optgroup label=group1>
+ <option id=opt2>opt2</option>
+ </optgroup>
+</select>
+
+<script>
+test(() => {
+ const select = document.querySelector('select');
+ const optgroup = document.querySelector('optgroup');
+ const newOption = document.createElement('option');
+ newOption.textContent = 'new option';
+
+ select.options.add(newOption, 1);
+ assert_equals(select.options.length, 3);
+ assert_equals(select.options[0], opt1, 'First item should be opt1.');
+ assert_equals(select.options[1], newOption, 'Second item should be newOption.');
+ assert_equals(select.options[2], opt2, 'Third item should be opt2.');
+ assert_equals(newOption.parentNode, optgroup, 'The new option should be inside the optgroup.');
+}, 'select.add() with an index should work when the target is inside an optgroup.');
+</script>
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-child-button-and-datalist-ref.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-child-button-and-datalist-ref.html
new file mode 100644
index 0000000000..46bbd0ccd0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-child-button-and-datalist-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<link rel=stylesheet href="resources/stylable-select-styles.css">
+
+<button popovertarget=popover id=button>button</button>
+<div id=popover popover=auto anchor=button class=stylable-select-datalist>
+ <option>one</option>
+ <option>two</option>
+</div>
+
+<script>
+document.getElementById('popover').showPopover();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-child-button-and-datalist.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-child-button-and-datalist.tentative.html
new file mode 100644
index 0000000000..b74957feed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-child-button-and-datalist.tentative.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/9799">
+<link rel=match href="select-child-button-and-datalist-ref.html">
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<select>
+ <button type=popover>button</button>
+ <datalist>
+ <option>one</option>
+ <option>two</option>
+ </datalist>
+</select>
+
+<script>
+document.querySelector('button').click();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/select-in-table-crash.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-in-table-crash.html
new file mode 100644
index 0000000000..d1f1cee217
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-in-table-crash.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1519397">
+<table><select>A0A0AA<<datalist><title></title><table><table><td>
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-parsing.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-parsing.tentative.html
new file mode 100644
index 0000000000..31133446d4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/select-parsing.tentative.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/9799">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+
+<select id=s1>
+ <div>div 1</div>
+ <button>button</button>
+ <div>div 2</div>
+ <datalist>
+ <option>option</option>
+ </datalist>
+ <div>div 3</div>
+</select>
+
+<select id=s2>
+ <button>button
+</select>
+
+<select id=s3>
+ <datalist>datalist
+</select>
+
+<select id=s4>
+ <button>
+ <select></select>
+ </button>
+</select>
+
+<select id=s5>
+ <button>
+ <div>
+ <select>
+</select>
+
+<select id=s6>
+<button>
+<button></button>
+</button>
+<datalist>
+<datalist></datalist>
+</datalist>
+</select>
+
+<div id=afterlast>
+ keep this div after the last test case
+</div>
+
+<script>
+test(() => {
+ // The document.body check here and in the other tests is to make sure that a
+ // previous test case didn't leave the HTML parser open on another element.
+ assert_equals(document.getElementById('s1').parentNode, document.body);
+ assert_equals(document.getElementById('s1').innerHTML, `
+ div 1
+ <button>button</button>
+ div 2
+ <datalist>
+ <option>option</option>
+ </datalist>
+ div 3
+`);
+}, '<button>s and <datalist>s should be allowed in <select>.');
+
+test(() => {
+ assert_equals(document.getElementById('s2').parentNode, document.body);
+ assert_equals(document.getElementById('s2').innerHTML, `
+ <button>button
+</button>`);
+}, '</select> should close <button>.');
+
+test(() => {
+ assert_equals(document.getElementById('s3').parentNode, document.body);
+ assert_equals(document.getElementById('s3').innerHTML, `
+ <datalist>datalist
+</datalist>`);
+}, '</select> should close <datalist>.');
+
+test(() => {
+ assert_equals(document.getElementById('s4').parentNode, document.body);
+ assert_equals(document.getElementById('s4').innerHTML, `
+ <button>
+ </button>`);
+}, '<select> in <button> in <select> should remove inner <select>.');
+
+test(() => {
+ assert_equals(document.getElementById('s5').parentNode, document.body);
+ assert_equals(document.getElementById('s5').innerHTML, `
+ <button>
+ <div>
+ </div></button>`);
+}, '<select> in <select><button><div> should remove inner <select>.');
+
+test(() => {
+ assert_equals(document.getElementById('s6').parentNode, document.body);
+ assert_equals(document.getElementById('s6').innerHTML, `
+<button>
+</button>
+
+<datalist>
+</datalist>
+
+`);
+}, 'Nested <button>s or <datalist>s in <select> should be dropped.');
+
+test(() => {
+ assert_equals(document.getElementById('afterlast').parentNode, document.body);
+}, 'The last test should not leave any tags open after parsing.');
+</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-select-element/show-picker-being-cv-hidden.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-being-cv-hidden.html
new file mode 100644
index 0000000000..8990734f93
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-being-cv-hidden.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Test showPicker() being rendered requirement with content-visibility</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>
+<div style="content-visibility: hidden">
+ <select id="select">
+ <option>Item 1</option>
+ </select>
+</div>
+<script>
+promise_test(async t => {
+ await test_driver.bless('show picker');
+ assert_throws_dom('NotSupportedError', () => { select.showPicker(); });
+
+ // Test that dynamically changing to actually being rendered works.
+ await test_driver.bless('show picker');
+ select.parentElement.style.contentVisibility = 'visible';
+ select.showPicker();
+ select.blur();
+}, 'select showPicker() throws when content-visibility hidden');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-being-rendered.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-being-rendered.html
new file mode 100644
index 0000000000..e7be4aea52
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-being-rendered.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Test showPicker() being rendered 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>
+<select id="select" style="display: none;">
+ <option>Item 1</option>
+</select>
+<script>
+promise_test(async t => {
+ await test_driver.bless('show picker');
+ assert_throws_dom('NotSupportedError', () => { select.showPicker(); });
+
+ // Test that dynamically changing to actually being rendered works.
+ await test_driver.bless('show picker');
+ select.style.display = 'inline-block';
+ select.showPicker();
+ select.blur();
+}, 'select showPicker() throws when not being rendered');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-cross-origin-iframe.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-cross-origin-iframe.tentative.html
new file mode 100644
index 0000000000..3f710b39c6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-cross-origin-iframe.tentative.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,
+ "select",
+ "In cross-origin iframes, showPicker() throws a SecurityError."
+ );
+ });
+
+ 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,
+ "select",
+ "In cross-origin but same-origin-domain iframes, showPicker() throws a SecurityError."
+ );
+ });
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-disabled.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-disabled.tentative.html
new file mode 100644
index 0000000000..f20bc2cf61
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-disabled.tentative.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Test showPicker() disabled 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>
+<select id="select" disabled>
+ <option>Item 1</option>
+</select>
+<script>
+ test(() => {
+ assert_throws_dom('InvalidStateError', () => { select.showPicker(); });
+ }, 'select showPicker() throws when disabled');
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-multiple.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-multiple.tentative.html
new file mode 100644
index 0000000000..c38e98ee4e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-multiple.tentative.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Test showPicker() on multiple selects</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>
+<select id="select" multiple>
+ <option>Item 1</option>
+</select>
+<script>
+promise_test(async t => {
+ await test_driver.bless('show picker');
+ select.showPicker();
+ select.blur();
+}, `select showPicker() does not throw when called on a <select multiple>`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-size.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-size.tentative.html
new file mode 100644
index 0000000000..b9b10c42a9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-size.tentative.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Test showPicker() on sized selects</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>
+<select id="select" size="4">
+ <option>Item 1</option>
+</select>
+<script>
+promise_test(async t => {
+ await test_driver.bless('show picker');
+ select.showPicker();
+ select.blur();
+}, `select showPicker() does not throw when called on a <select size="4">`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-user-gesture.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-user-gesture.tentative.html
new file mode 100644
index 0000000000..e917fe2a61
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-select-element/show-picker-user-gesture.tentative.html
@@ -0,0 +1,24 @@
+<!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>
+ test(() => {
+ const select = document.createElement("select");
+
+ assert_throws_dom('NotAllowedError', () => { select.showPicker(); });
+ }, `select showPicker() requires a user gesture`);
+
+ promise_test(async t => {
+ const select = document.createElement("select");
+ document.body.append(select)
+
+ await test_driver.bless('show picker');
+ select.showPicker();
+ select.blur();
+ }, `select showPicker() does not throw when user activation is active`);
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/button-type-selectlist-appearance-ref.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/button-type-selectlist-appearance-ref.html
new file mode 100644
index 0000000000..fd7f2b7bca
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/button-type-selectlist-appearance-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<button>button</button>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/button-type-selectlist-appearance.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/button-type-selectlist-appearance.tentative.html
new file mode 100644
index 0000000000..3ab7044366
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/button-type-selectlist-appearance.tentative.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/702">
+<link rel=match href="button-type-selectlist-appearance-ref.html">
+
+<button type=selectlist>button</button>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-ask-for-reset.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-ask-for-reset.html
new file mode 100644
index 0000000000..7cf2aff515
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-ask-for-reset.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectListElement Test: ask-for-reset</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<form name="fm1" id="form1">
+ <selectlist id="selectlist1">
+ <option>one</option>
+ <option>two</option>
+ </selectlist>
+
+ <selectlist id="selectlist2">
+ <option>one</option>
+ <option selected>two</option>
+ </selectlist>
+
+ <selectlist id="selectlist3">
+ <option>one</option>
+ <option selected>two</option>
+ <option selected>three</option>
+ </selectlist>
+</form>
+
+<script>
+function createSelectList(numberOfOptions) {
+ let selectList = document.createElement("selectlist");
+ for (let i = 0; i < numberOfOptions; i++) {
+ let option = document.createElement("option");
+ option.value = i;
+ selectList.appendChild(option);
+ }
+ return selectList;
+}
+
+function checkSelection(selectList, selectedOptionIndex, msg) {
+ for (let i = 0; i < selectList.children.length; i++) {
+ if (i != selectedOptionIndex) {
+ assert_false(selectList.children[i].selected);
+ }
+ }
+ assert_true(selectList.children[selectedOptionIndex].selected, msg);
+ assert_equals(selectList.value, selectList.children[selectedOptionIndex].value);
+}
+
+test(() => {
+ let selectList = createSelectList(5);
+
+ selectList.children[4].selected = true;
+ checkSelection(selectList, 4);
+
+ selectList.children[4].remove();
+ checkSelection(selectList, 0, "After removing the selected option, selection should default to first option.");
+
+ selectList.children[3].selected = true;
+ checkSelection(selectList, 3);
+ selectList.children[0].remove();
+ checkSelection(selectList, 2, "Removing non-selected option should have no effect.");
+}, "ask-for-reset when removing option");
+
+test(() => {
+ let selectList = createSelectList(3);
+ selectList.children[1].selected = true;
+
+ let newOption = document.createElement("option");
+ newOption.selected = true;
+ selectList.appendChild(newOption);
+ checkSelection(selectList, 3, "Inserting a selected option should update selection.");
+
+ let newOption2 = document.createElement("option");
+ newOption2.selected = true;
+ selectList.prepend(newOption2);
+ checkSelection(selectList, 0, "Inserting a selected option should update selection, even though it's not last in tree order.");
+
+ let newOption3 = document.createElement("option");
+ selectList.appendChild(newOption3);
+ checkSelection(selectList, 0, "Inserting a non-selected option should have no effect.");
+}, "ask-for-reset when inserting option");
+
+test(() => {
+ let selectList = createSelectList(3);
+ let options = selectList.children;
+
+ // select options from first to last
+ for (let i = 0; i < options.length; i++) {
+ options[i].selected = true;
+ checkSelection(selectList, i);
+ }
+
+ // select options from last to first
+ for (let i = options.length - 1; i >= 0; i--) {
+ options[i].selected = true;
+ checkSelection(selectList, i);
+ }
+
+ options[2].selected = true;
+ checkSelection(selectList, 2);
+ options[2].selected = false;
+ checkSelection(selectList, 0, "First non-disabled option should be selected.");
+
+ options[0].disabled = true;
+ options[2].selected = true;
+ checkSelection(selectList, 2);
+ options[2].selected = false;
+ checkSelection(selectList, 1, "First non-disabled option should be selected.");
+}, "ask-for-reset when changing selectedness of option");
+
+test(() => {
+ let selectList1 = document.getElementById("selectlist1");
+ let selectList2 = document.getElementById("selectlist2");
+ let selectList3 = document.getElementById("selectlist3");
+
+ document.getElementById("form1").reset();
+
+ assert_equals(selectList1.value, "one", "First non-disabled option should be selected.");
+ assert_equals(selectList2.value, "two", "The selected option should be selected.");
+ assert_equals(selectList3.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-selectlist-element/selectlist-button-closes-listbox.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-closes-listbox.tentative.html
new file mode 100644
index 0000000000..d3e6febd78
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-closes-listbox.tentative.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1408838">
+<link rel=help href="https://github.com/openui/open-ui/issues/639">
+<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>
+
+<selectlist id=defaultbutton-defaultlistbox>
+ <option>one</option>
+ <option>two</option>
+</selectlist>
+
+<selectlist id=custombutton-defaultlistbox>
+ <button type=selectlist>custom button</button>
+ <option>one</option>
+ <option>two</option>
+</selectlist>
+
+<selectlist id=defaultbutton-customlistbox>
+ <listbox>
+ <option>one</option>
+ <option>two</option>
+ </listbox>
+</selectlist>
+
+<selectlist id=custombutton-customlistbox>
+ <button type=selectlist>custom button</button>
+ <listbox>
+ <option>one</option>
+ <option>two</option>
+ </listbox>
+</selectlist>
+
+<script>
+document.querySelectorAll('selectlist').forEach(selectlist => {
+ promise_test(async () => {
+ assert_false(selectlist.matches(':open'),
+ 'The listbox should not be showing at the start of the test.');
+
+ await test_driver.click(selectlist);
+ assert_true(selectlist.matches(':open'),
+ 'The listbox should be showing after clicking the button.');
+
+ await test_driver.click(selectlist);
+ assert_false(selectlist.matches(':open'),
+ 'The listbox should be closed after clicking the button.');
+ }, `${selectlist.id}: Clicking the selectlist's button should toggle the listbox.`);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-type-appearance-ref.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-type-appearance-ref.html
new file mode 100644
index 0000000000..37d4b1b471
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-type-appearance-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<button>hello world</button>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-type-appearance.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-type-appearance.tentative.html
new file mode 100644
index 0000000000..50ad339b05
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-type-appearance.tentative.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/702">
+<link rel=match href="selectlist-button-type-appearance-ref.html">
+
+<selectlist>
+ <button type=selectlist>hello world</button>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-type-behavior.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-type-behavior.tentative.html
new file mode 100644
index 0000000000..cc7eb62a63
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-button-type-behavior.tentative.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/702">
+<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>
+
+<selectlist>
+ <button type=selectlist id=b1>first button</button>
+ <button type=selectlist id=b2>second button</button>
+ <button id=b3>third button</button>
+ <option>option</option>
+</selectlist>
+
+<script>
+const ESC = '\uE00C'
+
+promise_test(async () => {
+ const selectlist = document.querySelector('selectlist');
+ const b1 = document.getElementById('b1');
+ const b2 = document.getElementById('b2');
+ const b3 = document.getElementById('b3');
+
+ assert_false(selectlist.open, 'The selectlist should start closed.');
+ await test_driver.click(b1);
+ assert_true(selectlist.open, 'The selectlist should get opened when the button is clicked.');
+
+ await test_driver.send_keys(selectlist, ESC);
+ assert_false(selectlist.open, 'Pressing escape should close the selectlist.');
+
+ await test_driver.click(b2);
+ assert_true(selectlist.open, 'The selectlist should get opened when a second type=selectlist button is clicked.');
+ await test_driver.send_keys(selectlist, ESC);
+ assert_false(selectlist.open, 'Pressing escape should close the selectlist.');
+
+ await test_driver.click(b3);
+ assert_false(selectlist.open, 'Clicking a button witout type=selectlist should not open the listbox.');
+
+ b1.removeAttribute('type');
+ await test_driver.click(b1);
+ assert_false(selectlist.open, 'If the button is not type=selectlist, it should not open the selectlist.');
+}, '<button type=selectlist> should open the parent selectlist when clicked.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-default-button-slot-ref.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-default-button-slot-ref.html
new file mode 100644
index 0000000000..faa96e1f27
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-default-button-slot-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<style>
+.container {
+ display: inline-flex;
+ font-family: sans-serif;
+ font-size: 0.875em;
+}
+</style>
+<div class=container>
+ <div>first child</div>
+ <div>second child</div>
+</div>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-default-button-slot.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-default-button-slot.tentative.html
new file mode 100644
index 0000000000..d8688d5957
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-default-button-slot.tentative.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/702">
+<link rel=match href="selectlist-default-button-slot-ref.html">
+<meta rel=assert title="Child nodes of selectlist should be slotted into the button slot by default">
+
+<selectlist>
+ <div>first child</div>
+ <div>second child</div>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-disabled.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-disabled.tentative.html
new file mode 100644
index 0000000000..f557804d9b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-disabled.tentative.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<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>
+
+<script>
+focused_element_id = null;
+
+function OnFocus(event) {
+ focused_element_id = event.target.id;
+}
+</script>
+
+<selectlist id="selectlist" onfocus="OnFocus(event)" disabled>
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectlist>
+
+<script>
+promise_test(async () => {
+ const selectlist = document.getElementById("selectlist");
+ selectlist.focus();
+ assert_equals(focused_element_id, null);
+}, "Check that disabled <selectlist> cannot be focused");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-events.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-events.tentative.html
new file mode 100644
index 0000000000..a88a3b1f7d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-events.tentative.html
@@ -0,0 +1,244 @@
+<!DOCTYPE html>
+<title>HTMLSelectListElement 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>
+
+<selectlist id="selectList0">
+ <div slot="button" behavior="button">
+ <span behavior="selected-value"></span>
+ <button id="selectList0-button">selectList0-button</button>
+ </div>
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectlist>
+
+<selectlist id="selectList1">
+ <option>one</option>
+ <option>
+ two
+ <button id="selectList1-button">selectList1-button</button>
+ </option>
+ <option>three</option>
+</selectlist>
+
+<selectlist id="selectList2">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectlist>
+
+<selectlist id="selectList3">
+ <option>same</option>
+ <option>same</option>
+</selectlist>
+
+<selectlist id="selectList4">
+ <option>one</option>
+ <option id="selectList4-option2">two</option>
+</selectlist>
+
+<selectlist id="selectList5WithTabIndex" tabindex="1">
+ <option>one</option>
+ <option>two</option>
+</selectlist>
+
+<input id="input6"/>
+<selectlist id="selectList7">
+ <button slot="button" behavior="button" id="selectList7-button">
+ selectList7-button
+ </button>
+ <option>one</option>
+ <option>two</option>
+</selectlist>
+
+<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 selectList = document.getElementById("selectList0");
+ const selectListButton = document.getElementById("selectList0-button");
+ assert_false(selectList.open);
+ const selectListButtonPromise = new Promise(async resolve => {
+ selectListButton.addEventListener("click", (e) => {
+ assert_false(selectList.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 selectListPromise = new Promise(async resolve => {
+ selectList.addEventListener("click", (e) => {
+ assert_true(e.defaultPrevented, "Event should have been defaultPrevented by selectListButton click handler");
+ assert_false(selectList.open, "Listbox shouldn't have opened, because click event was defaultPrevented.");
+ resolve();
+ });
+ });
+
+ await clickOn(selectListButton);
+ return Promise.all([selectListButtonPromise, selectListPromise]);
+ }, "Button controller code should not run if the click event is preventDefaulted.");
+
+ // See https://w3c.github.io/webdriver/#keyboard-actions
+ const KEY_CODE_MAP = {
+ 'Tab': '\uE004',
+ 'Enter': '\uE007',
+ 'Space': '\uE00D',
+ 'ArrowUp': '\uE013',
+ 'ArrowDown': '\uE015',
+ };
+
+ promise_test(async () => {
+ const selectList = document.getElementById("selectList1");
+ const selectListButton = document.getElementById("selectList1-button");
+ await clickOn(selectList);
+ assert_true(selectList.open);
+ const selectListButtonPromise = new Promise(async resolve => {
+ selectListButton.addEventListener("click", (e) => {
+ assert_true(selectList.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 selectListPromise = new Promise(async resolve => {
+ selectList.addEventListener("click", (e) => {
+ assert_true(e.defaultPrevented, "Event should have been defaultPrevented by selectListButton click handler");
+ assert_true(selectList.open, "Listbox shouldn't have closed, because keydown event was defaultPrevented.");
+ assert_equals(selectList.value, "one", "<selectlist> shouldn't have changed value, because keydown event was defaultPrevented.");
+ resolve();
+ });
+ });
+
+ await clickOn(selectListButton);
+ return Promise.all([selectListButtonPromise, selectListPromise]);
+ }, "Listbox controller code should not run if the click event is preventDefaulted.");
+
+ promise_test(async () => {
+ const selectList = document.getElementById("selectList2");
+ const events = [];
+
+ selectList.addEventListener("input", (e) => {
+ assert_true(e.composed, "input event should be composed.");
+ events.push('input');
+ });
+ selectList.addEventListener("change", (e) => {
+ assert_false(e.composed, "change event should not be composed.");
+ events.push('change');
+ });
+
+ await clickOn(selectList);
+ assert_true(selectList.open);
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.Enter);
+ assert_false(selectList.open);
+ assert_equals(selectList.value, "one");
+ assert_array_equals(events, [], "input and change shouldn't fire if value wansn't changed.");
+
+ await clickOn(selectList);
+ assert_true(selectList.open);
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.ArrowDown);
+ assert_equals(selectList.value, "one", "value shouldn't change when user switches options with arrow key.");
+ assert_array_equals(events, ['input'], "input event should fire when user switches options with arrow key.");
+
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.Enter);
+ assert_equals(selectList.value, "two");
+ assert_array_equals(events, ['input', 'input', 'change'], "input and change should fire after pressing enter.");
+ }, "<selectlist> should fire input and change events when new option is selected.");
+
+ promise_test(async () => {
+ const selectList = document.getElementById("selectList3");
+ const events = [];
+
+ selectList.addEventListener("input", (e) => {
+ assert_true(e.composed, "input event should be composed.");
+ events.push('input');
+ });
+ selectList.addEventListener("change", (e) => {
+ assert_false(e.composed, "change event should not be composed.");
+ events.push('change');
+ });
+
+ await clickOn(selectList);
+ assert_true(selectList.open);
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.ArrowDown);
+ assert_array_equals(events, ['input'], "input event should have fired after ArrowDown.");
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.Enter);
+ assert_array_equals(events, ['input', 'input', 'change'], "input and change should fire after pressing Enter.");
+ }, "<selectlist> should fire input and change events even when new selected option has the same value as the old.");
+
+ promise_test(async () => {
+ const selectList = document.getElementById("selectList4");
+ const selectListOption2 = document.getElementById("selectList4-option2");
+ let input_event_count = 0;
+ let change_event_count = 0;
+
+ selectList.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++;
+ });
+
+ selectList.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(selectList);
+ assert_true(selectList.open);
+ await clickOn(selectListOption2);
+ 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");
+ }, "<selectlist> should fire input and change events when option in listbox is clicked");
+
+ promise_test(async() => {
+ const selectList = document.getElementById("selectList2");
+ await test_driver.send_keys(selectList, " ");
+ assert_true(selectList.open, "<Space> should open selectlist");
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.Enter);
+ assert_false(selectList.open, "<Enter> should close selectlist");
+ }, "Check that <Space> opens <selectlist>.");
+
+ promise_test(async() => {
+ const selectList = document.getElementById("selectList5WithTabIndex");
+ await test_driver.send_keys(selectList, " ");
+ assert_true(selectList.open, "<Space> should open selectlist");
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.Enter);
+ assert_false(selectList.open, "<Enter> should close selectlist");
+ }, "Check that <Space> opens <selectlist> when <selectlist> specifies tabindex");
+
+ promise_test(async() => {
+ const input6 = document.getElementById("input6");
+ const selectList = document.getElementById("selectList7");
+ const selectListButton = document.getElementById("selectList7-button")
+
+ var keydown_count = 0;
+ selectListButton.addEventListener("keydown", (e) => {
+ keydown_count++;
+ });
+
+ // Focus selectlist via Tab traversal because focus() does not work when selectlist
+ // has custom slot.
+ // TODO(http://crbug.com/1440573) Fix this.
+ await test_driver.send_keys(input6, KEY_CODE_MAP.Tab);
+
+ await test_driver.send_keys(selectList, "a");
+ assert_equals(keydown_count, 1, "button in shadowroot should have observed keydown");
+}, "Test that <selectlist> button slot receives key events.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-explicit-size-ref.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-explicit-size-ref.tentative.html
new file mode 100644
index 0000000000..46d932c4b2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-explicit-size-ref.tentative.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script src="support/fake-selectlist.js"></script>
+<body>
+<script>
+ const selectlist = createFakeSelectlist('option');
+ document.body.appendChild(selectlist);
+ const button = selectlist.querySelector('.fake-selectlist-internal-selectlist-button');
+ button.style.width = "400px";
+ button.style.height = "50px";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-explicit-size.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-explicit-size.tentative.html
new file mode 100644
index 0000000000..d9e52a9fff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-explicit-size.tentative.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<!-- Tests that selectlist respects explicit size -->
+<link rel=author href="mailto:pkotwicz@chromium.org">
+<link rel="match" href="selectlist-explicit-size-ref.tentative.html">
+
+<style>
+selectlist {
+ width:400px;
+ height:50px;
+}
+</style>
+<selectlist>
+ <option>option</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-font-size-ref.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-font-size-ref.tentative.html
new file mode 100644
index 0000000000..46d6dbdb9b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-font-size-ref.tentative.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script src="support/fake-selectlist.js"></script>
+<body>
+<script>
+ const selectlist = createFakeSelectlist('option');
+ document.body.appendChild(selectlist);
+ selectlist.style.fontSize = "48px";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-font-size.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-font-size.tentative.html
new file mode 100644
index 0000000000..9c30e71bda
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-font-size.tentative.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<!-- Tests that selectlist respects explicit size -->
+<link rel=author href="mailto:pkotwicz@chromium.org">
+<link rel="match" href="selectlist-font-size-ref.tentative.html">
+
+<style>
+selectlist {
+ font-size:48px;
+}
+</style>
+<selectlist>
+ <option>option</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-attribute.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-attribute.tentative.html
new file mode 100644
index 0000000000..c1872b9303
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-attribute.tentative.html
@@ -0,0 +1,231 @@
+<!-- This test is tentative until the <selectlist> 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 = [
+ "selectlist",
+ ];
+
+ 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-selectlist-element/selectlist-form-elements.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-elements.tentative.html
new file mode 100644
index 0000000000..07e5be6ef4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-elements.tentative.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectListElement Test: form.elements</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>
+
+<form id="form0">
+<selectlist id="selectlist0">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectlist>
+<form>
+
+<script>
+promise_test(async t => {
+ // TODO: Move test to /the-form-element/form-elements-filter.html once
+ // <selectlist> becomes part of the HTML spec.
+ const formElements = document.querySelector("#form0").elements;
+ assert_equals(formElements.length, 1);
+ assert_equals(formElements[0].id, "selectlist0");
+}, "Check that <selectlist> is exposed in form.elements");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-state-restore.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-state-restore.tentative.html
new file mode 100644
index 0000000000..f98494a950
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-state-restore.tentative.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectListElement 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">
+<selectlist id="selectlist0">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectlist>
+</form>
+
+<script>
+async_test(t => {
+ window.onload = () => t.step_timeout(() => {
+ let state = document.getElementById('emptyOnFirstVisit');
+ let selectList = document.getElementById("selectlist0");
+
+ if (!state.value) {
+ // First visit.
+ t.step_timeout(() => {
+ state.value = 'visited';
+ assert_equals(selectList.value, "one");
+ selectList.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(selectList.value, "two");
+ t.done();
+ }
+ }, 1);
+}, "Test restoring state after form submission");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-submission.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-submission.tentative.html
new file mode 100644
index 0000000000..4b5e497028
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-form-submission.tentative.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectListElement 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">
+ <selectlist name="s0" id="selectlist0">
+ <option selected>one</option>
+ <option>two</option>
+ <option>three</option>
+ </selectlist>
+</form>
+
+<form id="form1">
+ <input type="text" name="i1" value="test">
+ <selectlist id="selectlist1">
+ <option selected>one</option>
+ <option>two</option>
+ <option>three</option>
+ </selectlist>
+</form>
+
+<script>
+
+test(() => {
+ const form0 = document.getElementById("form0");
+ const selectList0 = document.getElementById("selectlist0");
+ assert_equals(selectList0.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 HTMLSelectList.value is used for form submission");
+
+test(() => {
+ const form1 = document.getElementById("form1");
+ const selectList1 = document.getElementById("selectlist1");
+ assert_equals(selectList1.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 HTMLSelectList.value is not used for form submission without name attribute");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-keyboard-behavior.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-keyboard-behavior.tentative.html
new file mode 100644
index 0000000000..a70ab7e6a5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-keyboard-behavior.tentative.html
@@ -0,0 +1,182 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1422275">
+<link rel=help href="https://github.com/openui/open-ui/issues/433#issuecomment-1452461404">
+<link rel=help href="https://github.com/openui/open-ui/issues/386#issuecomment-1452469497">
+<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/testdriver-actions.js"></script>
+
+<form></form>
+
+<div id=notform>
+ <selectlist id=defaultbutton>
+ <option class=one>one</option>
+ <option class=two>two</option>
+ <option class=three>three</option>
+ </selectlist>
+
+ <selectlist id=custombutton>
+ <button type=selectlist>custom button</button>
+ <option class=one>one</option>
+ <option class=two>two</option>
+ <option class=three>three</option>
+ </selectlist>
+</div>
+
+<script>
+const Enter = '\uE007';
+const Escape = '\uE00C';
+const ArrowLeft = '\uE012';
+const ArrowUp = '\uE013';
+const ArrowRight = '\uE014';
+const ArrowDown = '\uE015';
+const Space = ' ';
+const form = document.querySelector('form');
+const notform = document.getElementById('notform');
+
+for (const id of ['defaultbutton', 'custombutton']) {
+ const selectlist = document.getElementById(id);
+
+ async function closeListbox() {
+ await test_driver.click(selectlist);
+ }
+
+ function addCloseCleanup(t) {
+ t.add_cleanup(async () => {
+ if (selectlist.matches(':open')) {
+ await closeListbox();
+ }
+ if (selectlist.matches(':open')) {
+ throw new Error('selectlist failed to close!');
+ }
+ selectlist.value = 'one';
+ });
+ }
+
+ promise_test(async t => {
+ addCloseCleanup(t);
+ // TODO(http://crbug.com/1350299): When focus for custom buttons is fixed,
+ // then we shouldn't need to explicitly focus the custom button like this
+ // anymore.
+ const customButton = selectlist.querySelector('button');
+ if (customButton) {
+ customButton.focus();
+ } else {
+ selectlist.focus();
+ }
+ assert_false(selectlist.matches(':open'),
+ 'The selectlist should initially be closed.');
+ await test_driver.send_keys(selectlist, Space);
+ assert_true(selectlist.matches(':open'),
+ 'The selectlist should be open after pressing space.');
+ }, `${id}: When the listbox is closed, spacebar should open the listbox.`);
+
+ promise_test(async t => {
+ addCloseCleanup(t);
+ selectlist.value = 'two';
+ selectlist.focus();
+ assert_false(selectlist.matches(':open'),
+ 'The selectlist should initially be closed.');
+
+ await test_driver.send_keys(selectlist, ArrowLeft);
+ assert_true(selectlist.matches(':open'),
+ 'Arrow left should open the listbox.');
+ assert_equals(selectlist.value, 'two',
+ 'Arrow left should not change the selected value.');
+ await closeListbox();
+
+ await test_driver.send_keys(selectlist, ArrowUp);
+ assert_true(selectlist.matches(':open'),
+ 'Arrow up should open the listbox.');
+ assert_equals(selectlist.value, 'two',
+ 'Arrow up should not change the selected value.');
+ await closeListbox();
+
+ await test_driver.send_keys(selectlist, ArrowRight);
+ assert_true(selectlist.matches(':open'),
+ 'Arrow right should open the listbox.');
+ assert_equals(selectlist.value, 'two',
+ 'Arrow right should not change the selected value.');
+ await closeListbox();
+
+ await test_driver.send_keys(selectlist, ArrowDown);
+ assert_true(selectlist.matches(':open'),
+ 'Arrow down should open the listbox.');
+ assert_equals(selectlist.value, 'two',
+ 'Arrow down should not change the selected value.');
+ }, `${id}: When the listbox is closed, all arrow keys should open the listbox.`);
+
+ promise_test(async t => {
+ addCloseCleanup(t);
+
+ // TODO(http://crbug.com/1350299): When focus for custom buttons is fixed,
+ // then we shouldn't need to explicitly use the custom button like this
+ // anymore.
+ const customButton = selectlist.querySelector('button');
+ if (customButton) {
+ await test_driver.send_keys(customButton, Enter);
+ } else {
+ await test_driver.send_keys(selectlist, Enter);
+ }
+ assert_false(selectlist.matches(':open'),
+ 'Enter should not open the listbox when outside a form.');
+
+ form.appendChild(selectlist);
+ let formWasSubmitted = false;
+ form.addEventListener('submit', event => {
+ event.preventDefault();
+ formWasSubmitted = true;
+ }, {once: true});
+ if (customButton) {
+ await test_driver.send_keys(customButton, Enter);
+ } else {
+ await test_driver.send_keys(selectlist, Enter);
+ }
+ assert_true(formWasSubmitted,
+ 'Enter should submit the form when the listbox is closed.');
+ assert_false(selectlist.matches(':open'),
+ 'Enter should not open the listbox when it is in a form.');
+ }, `${id}: When the listbox is closed, the enter key should submit the form or do nothing.`);
+
+ promise_test(async t => {
+ addCloseCleanup(t);
+ const optionOne = selectlist.querySelector('.one');
+ const optionTwo = selectlist.querySelector('.two');
+ const optionThree = selectlist.querySelector('.three');
+
+ selectlist.value = 'two';
+ await test_driver.click(selectlist);
+ assert_true(selectlist.matches(':open'),
+ 'The selectlist should open when clicked.');
+ assert_equals(document.activeElement, optionTwo,
+ 'The selected option should receive initial focus.');
+
+ await test_driver.send_keys(document.activeElement, ArrowDown);
+ assert_equals(document.activeElement, optionThree,
+ 'The next option should receive focus when the down arrow key is pressed.');
+ assert_equals(selectlist.value, 'two',
+ 'The selectlists value should not change when focusing another option.');
+
+ await test_driver.send_keys(document.activeElement, ArrowUp);
+ assert_equals(document.activeElement, optionTwo,
+ 'The previous option should receive focus when the up arrow key is pressed.');
+ assert_equals(selectlist.value, 'two',
+ 'The selectlists value should not change when focusing another option.');
+
+ await test_driver.send_keys(document.activeElement, ArrowUp);
+ assert_equals(document.activeElement, optionOne,
+ 'The first option should be selected.');
+ assert_equals(selectlist.value, 'two',
+ 'The selectlists value should not change when focusing another option.');
+
+ await test_driver.send_keys(document.activeElement, Enter);
+ assert_false(selectlist.matches(':open'),
+ 'The listbox should be closed after pressing enter.');
+ assert_equals(selectlist.value, 'one',
+ 'The selectlists value should change after pressing enter on a different option.');
+ }, `${id}: When the listbox is open, the enter key should commit the selected option.`);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-keyboard.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-keyboard.tentative.html
new file mode 100644
index 0000000000..5d1c65e941
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-keyboard.tentative.html
@@ -0,0 +1,142 @@
+<!doctype html>
+<title>HTMLSelectListElement 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>
+ <selectlist id="selectList0">
+ <button id="selectList0-button0" type=selectlist>button</button>
+ <option class=one>one</option>
+ <option class=two>two</option>
+ <option class=three>three</option>
+ </selectlist>
+
+ <selectlist id="selectList1">
+ <option id="selectList1-child0">one</option>
+ </selectlist>
+
+ <selectlist id="selectList2" disabled>
+ <button id="selectList2-button0" type=selectlist>button</button>
+ <option disabled>one</option>
+ <option>two</option>
+ <option>three</option>
+ </selectlist>
+
+ <selectlist id="selectList3">
+ <button id="selectList3-button0" type=selectlist>button</button>
+ <option class=one>one</option>
+ <option disabled>two</option>
+ <option class=three>three</option>
+ </selectlist>
+<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 selectList = document.querySelector("#selectList0");
+ const button = document.querySelector("#selectList0-button0");
+ assert_false(selectList.open, "selectlist should not be initially open");
+
+ await test_driver.send_keys(button, KEY_CODE_MAP.Enter);
+ assert_false(selectList.open, "Enter key shouldn't open selectlist");
+ await test_driver.send_keys(button, KEY_CODE_MAP.Space);
+ assert_true(selectList.open, "Space key should open selectlist");
+ assert_equals(selectList.value, "one");
+
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.ArrowDown);
+ assert_equals(document.activeElement, selectList.querySelector('.two'),
+ "Down arrow should focus the next option.");
+ assert_equals(selectList.value, "one", "Down arrow should not commit the newly focused option.");
+
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.ArrowDown);
+ assert_equals(document.activeElement, selectList.querySelector('.three'),
+ "Down arrow should focus the next option.");
+ assert_equals(selectList.value, "one", "Down arrow should not commit the newly focused option.");
+
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.ArrowDown);
+ assert_equals(document.activeElement, selectList.querySelector('.three'),
+ "Down arrow should do nothing if already at the last option.");
+ assert_equals(selectList.value, "one", "Down arrow should not commit the newly focused option.");
+
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.ArrowUp);
+ assert_equals(document.activeElement, selectList.querySelector('.two'),
+ "Up arrow should focus the previous option.");
+ assert_equals(selectList.value, "one", "Up arrow should not commit the newly focused option.");
+
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.ArrowUp);
+ assert_equals(document.activeElement, selectList.querySelector('.one'),
+ "Up arrow should focus the previous option.");
+ assert_equals(selectList.value, "one", "Up arrow should not commit the newly focused option.");
+
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.ArrowUp);
+ assert_equals(document.activeElement, selectList.querySelector('.one'),
+ "Up arrow should do nothing if already at the first option.");
+ assert_equals(selectList.value, "one", "Up arrow should not commit the newly focused option.");
+
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.Enter);
+ assert_false(selectList.open, "Enter key should close selectlist");
+
+ await test_driver.send_keys(button, KEY_CODE_MAP.Space);
+ assert_true(selectList.open, "Space key should open selectlist");
+
+ // This behavior is suspicious (since Space key can open the selectlist),
+ // but it maches <select>. See https://github.com/openui/open-ui/issues/386
+ await test_driver.send_keys(selectList, " ");
+ assert_true(selectList.open, "Space key should *not* close selectlist");
+
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.Enter);
+ assert_false(selectList.open, "Enter key should close selectlist");
+}, "Validate Enter, Up/Down Arrow, and Space keyboard accessibility support for <selectlist>");
+
+promise_test(async t => {
+ const selectListOption = document.getElementById("selectList1-child0");
+ const event = document.createEvent("Event");
+ event.initEvent("keydown");
+ selectListOption.dispatchEvent(event);
+}, "Firing a synthetic event at a selectlist's option doesn't crash");
+
+promise_test(async t => {
+ const selectList2 = document.querySelector("#selectList2");
+ const selectList2Button = document.querySelector("#selectList2-button0");
+ assert_false(selectList2.open, "selectlist should not be initially open");
+
+ await test_driver.send_keys(selectList2Button, KEY_CODE_MAP.Enter);
+ assert_false(selectList2.open, "Enter key should not open a disabled selectlist");
+ await clickOn(selectList2);
+ assert_false(selectList2.open, "Click should not open a disabled selectlist");
+ assert_equals(selectList2.value, "one");
+
+ const selectList3 = document.querySelector("#selectList3");
+ const selectList3Button = document.querySelector("#selectList3-button0");
+ assert_false(selectList3.open, "selectlist should not be initially open");
+
+ await test_driver.send_keys(selectList3Button, KEY_CODE_MAP.Enter);
+ assert_false(selectList3.open, "Enter key shouldn't open selectlist");
+
+ await test_driver.send_keys(selectList3Button, KEY_CODE_MAP.Space);
+ assert_true(selectList3.open, "Space key should open selectlist");
+ assert_equals(selectList3.value, "one");
+
+ await test_driver.send_keys(selectList3, KEY_CODE_MAP.ArrowDown);
+ assert_equals(document.activeElement, selectList3.querySelector('.three'),
+ "Down arrow should go to next non-disabled option");
+
+ await test_driver.send_keys(selectList3, KEY_CODE_MAP.ArrowUp);
+ assert_equals(document.activeElement, selectList3.querySelector('.one'),
+ "Up arrow should go to the previous non-disabled option");
+}, "Validate Enter, Up/Down Arrow keyboard accessibility support for disabled <selectlist>");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-labels.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-labels.tentative.html
new file mode 100644
index 0000000000..819da49fdd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-labels.tentative.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectListElement 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="selectlist1">Label 1</label>
+<selectlist id="selectlist1">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+ <option>four</option>
+</selectlist>
+<label id="label2" for="selectlist1">Label 2</label>
+<label id="label3" for="invalid-selectlist1">Label 3</label>
+
+<script>
+
+test(() => {
+ let selectList = document.getElementById("selectlist1");
+
+ assert_true(selectList.labels instanceof NodeList);
+ assert_equals(selectList.labels.length, 2);
+ assert_equals(selectList.labels[0], document.getElementById("label1"));
+ assert_equals(selectList.labels[1], document.getElementById("label2"));
+}, "Validate selectlist.labels");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-listbox-element-ref.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-listbox-element-ref.html
new file mode 100644
index 0000000000..5533a97f60
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-listbox-element-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<script src="support/fake-selectlist.js"></script>
+<body>
+<script>
+const selectlist = createFakeSelectlist('button', /*includeListbox=*/true);
+document.body.appendChild(selectlist);
+
+selectlist.querySelector('button').remove();
+selectlist.insertAdjacentHTML('afterbegin', `<button>button</button>`);
+
+const listbox = selectlist.querySelector('.fake-selectlist-listbox');
+listbox.innerHTML = `
+ <option id=optone tabindex=0>one</option>
+ <option>two</option>
+`;
+document.getElementById('optone').focus();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-listbox-element.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-listbox-element.tentative.html
new file mode 100644
index 0000000000..14223633b9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-listbox-element.tentative.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/702">
+<link rel=match href="selectlist-listbox-element-ref.html">
+
+<selectlist>
+ <button type=selectlist>button</button>
+ <listbox>
+ <option>one</option>
+ <option>two</option>
+ </listbox>
+</selectlist>
+
+<script>
+document.querySelector('button').click();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-listbox-fallback-change-crash.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-listbox-fallback-change-crash.tentative.html
new file mode 100644
index 0000000000..1a9e81d817
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-listbox-fallback-change-crash.tentative.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<html class="test-wait">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1400522">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+
+<selectlist id="selectList" style="position: absolute;">
+ <div slot="button">
+ <button id="b1" behavior="button"></button>
+ </div>
+ <selectlist>
+ <div slot="button">
+ <button id="b2" behavior="button">x</button>
+ </div>
+ <div id="listbox" popover slot="listbox" behavior="listbox">
+ <option>y</option>
+ </div>
+ </selectlist>
+</selectlist>
+
+<script type="module">
+ const raf = () => new Promise(resolve => requestAnimationFrame(resolve));
+
+ document.querySelector('#b1').click();
+ document.querySelector('#b2').click();
+
+ document.querySelector('#selectList').style.top = '-25px';
+
+ await raf();
+ await raf();
+
+ document.querySelector('#selectList').style.top = '0px';
+
+ document.documentElement.classList.remove('test-wait');
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-many-options.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-many-options.tentative.html
new file mode 100644
index 0000000000..36bae1a82b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-many-options.tentative.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<html>
+<title>HTMLSelectListElement 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>
+ #selectList0 {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ }
+
+ #selectList0-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>
+
+<selectlist id="selectList0">
+ <div popover slot="listbox" behavior="listbox" id="selectList0-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>
+</selectlist>
+<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 selectList0 = document.getElementById("selectList0");
+ const selectList0Popover = document.getElementById("selectList0-popover");
+
+ await clickOn(selectList0);
+ assert_equals(Math.round(selectList0.getBoundingClientRect().bottom), Math.round(selectList0Popover.getBoundingClientRect().top));
+ assert_equals(Math.round(selectList0.getBoundingClientRect().left), Math.round(selectList0Popover.getBoundingClientRect().left));
+ assert_equals(window.innerHeight, Math.round(selectList0Popover.getBoundingClientRect().bottom));
+ }, "The popover should be bottom left positioned");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-end-aligned-ref.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-end-aligned-ref.tentative.html
new file mode 100644
index 0000000000..b1df8a6581
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-end-aligned-ref.tentative.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<style>
+div {
+ width:300px;
+ display:flex;
+ justify-content:flex-end;
+}
+selectlist {
+ width:100px;
+}
+selectlist::part(button) {
+ border-style:none;
+ background-color:rgba(0,0,0,0)
+}
+</style>
+<div>
+<selectlist>
+ <option></option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-end-aligned.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-end-aligned.tentative.html
new file mode 100644
index 0000000000..c2540acb36
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-end-aligned.tentative.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<!-- Tests that <selectlist> marker is end-aligned -->
+<link rel=author href="mailto:pkotwicz@chromium.org">
+<link rel="match" href="selectlist-marker-end-aligned-ref.tentative.html">
+
+<style>
+div {
+ width:300px;
+ display:flex;
+ justify-content:flex-end;
+}
+selectlist {
+ width:200px;
+}
+selectlist::part(button) {
+ border-style:none;
+ background-color:rgba(0,0,0,0)
+}
+</style>
+<div>
+<selectlist>
+ <option></option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-part-ref.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-part-ref.html
new file mode 100644
index 0000000000..1a1302b96d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-part-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="support/fake-selectlist.js"></script>
+<body>
+<script>
+ const selectlist = createFakeSelectlist('hello world');
+ document.body.appendChild(selectlist);
+ selectlist.querySelector('.fake-selectlist-internal-selectlist-button-icon')
+ .style.backgroundColor = 'red';
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-part.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-part.tentative.html
new file mode 100644
index 0000000000..dedabc9bab
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-part.tentative.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=match href="selectlist-marker-part-ref.html">
+
+<style>
+selectlist::part(marker) {
+ background-color: red;
+}
+</style>
+<selectlist>
+ <option>hello world</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-slot-ref.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-slot-ref.html
new file mode 100644
index 0000000000..e910df4c5a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-slot-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="support/fake-selectlist.js"></script>
+<body>
+<script>
+ const selectlist = createFakeSelectlist('hello world');
+ document.body.appendChild(selectlist);
+
+ const oldMarker = selectlist.querySelector('.fake-selectlist-internal-selectlist-button-icon');
+ const newMarker = document.createElement('div');
+ newMarker.textContent = 'marker';
+
+ replaceChildElement(newMarker, oldMarker);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-slot.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-slot.tentative.html
new file mode 100644
index 0000000000..de43a02c33
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-slot.tentative.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=match href="selectlist-marker-slot-ref.html">
+
+<selectlist>
+ <div slot=marker>marker</div>
+ <option>hello world</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-visible-overflow-ref.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-visible-overflow-ref.tentative.html
new file mode 100644
index 0000000000..dda53db5bf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-visible-overflow-ref.tentative.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+selectlist {
+ width:100px;
+}
+</style>
+<div>
+<selectlist>
+ <option>&nbsp;</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-visible-overflow.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-visible-overflow.tentative.html
new file mode 100644
index 0000000000..345c205983
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-marker-visible-overflow.tentative.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<!-- Tests that the marker is visible when the <selectlist> contains more text than it can show -->
+<link rel=author href="mailto:pkotwicz@chromium.org">
+<link rel="match" href="selectlist-marker-visible-overflow-ref.tentative.html">
+
+<style>
+selectlist {
+ width:100px;
+}
+</style>
+<div>
+<selectlist>
+ <option>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-nested.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-nested.tentative.html
new file mode 100644
index 0000000000..c29413f180
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-nested.tentative.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectListElement 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>
+
+<selectlist id="selectList0">
+ <div popover slot="listbox" behavior="listbox">
+ <selectlist id="nested0">
+ <option id="child1">one</option>
+ <option id="child2">two</option>
+ </selectlist>
+ <option id="child3">three</option>
+ </div>
+</selectlist>
+
+<selectlist id="selectList1">
+ <div popover slot="listbox" behavior="listbox">
+ <select>
+ <option>one</option>
+ <option>two</option>
+ </select>
+ <option>three</option>
+ </div>
+</selectlist>
+
+<selectlist id="selectList2">
+ <div slot="button">
+ <selectlist id="nested2">
+ <button type=selectlist id="selectList2-button0">button0</button>
+ <option id="nested2-option1">one</option>
+ </selectlist>
+ <button type=selectlist id="selectList2-button1">button1</button>
+ </div>
+ <option>two</option>
+</selectlist>
+
+<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 selectList0 = document.getElementById("selectList0");
+ const nested0 = document.getElementById("nested0");
+ const child2 = document.getElementById("child2");
+ assert_equals(selectList0.value, "three", "Options nested in another <selectlist> should not get controller code from outer <selectlist>");
+ await clickOn(selectList0);
+ assert_true(selectList0.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(selectList0.open, "click on option in inner <selectlist> should not close outer <selectlist>");
+ assert_equals(selectList0.value, "three", "click on option in inner <selectlist> should not change value of outer <selectlist>");
+ }, "A <selectlist> shouldn't apply controller code to parts nested in a <selectlist> child");
+
+ promise_test(async () => {
+ const selectList1 = document.getElementById("selectList1");
+ assert_equals(selectList0.value, "three");
+ }, "A <selectlist> shouldn't apply controller code to parts nested in a <select> child");
+
+ promise_test(async () => {
+ const selectList2 = document.getElementById("selectList2");
+ const nested2 = document.getElementById("nested2");
+ const button0 = document.getElementById("selectList2-button0");
+ const button1 = document.getElementById("selectList2-button1");
+ const nested2Option1 = document.getElementById("nested2-option1");
+ assert_false(selectList2.open);
+ assert_false(nested2.open);
+
+ await clickOn(button0);
+ assert_false(selectList2.open, "Clicking the button of a nested <selectlist> should not open the outer <selectlist>");
+ assert_true(nested2.open, "Clicking the button of a nested <selectlist> should open the outer <selectlist>");
+
+ await clickOn(nested2Option1);
+ assert_false(nested2.open);
+
+ await clickOn(button1);
+ assert_true(selectList2.open);
+ assert_false(nested2.open);
+ }, "A nested button part in a nested <selectlist> 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-selectlist-element/selectlist-option-arbitrary-content-displayed-ref.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-displayed-ref.tentative.html
new file mode 100644
index 0000000000..126bbc5dd7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-displayed-ref.tentative.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<link rel="stylesheet" href="/fonts/ahem.css">
+
+<selectlist id="selectList0">
+ <option>option with image displayed</option>
+</selectlist>
+<div id=fakelistbox>
+ option with image displayed
+ <img src="/images/green-256x256.png">
+</div>
+
+<style>
+ html,selectlist {
+ font-family: Ahem;
+ }
+ #fakelistbox {
+ /* Per spec: */
+ display: block;
+ position: fixed;
+ top: 30px;
+ left: 0;
+ font-size: 0.765625em /* 0.875 * 0.875 */;
+ /* Per settings in test file: */
+ width: fit-content;
+ height: fit-content;
+ background: light-dark(white, black);
+ color: light-dark(black, white);
+ border: 1px solid rgba(0, 0, 0, 1);
+ 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;
+ }
+
+ selectlist {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ height: 30px;
+ }
+</style>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-displayed.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-displayed.tentative.html
new file mode 100644
index 0000000000..2d51002fb2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-displayed.tentative.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>HTMLSelectListElement Test: option arbitrary content displayed</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<link rel=match href="selectlist-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,selectlist {
+ font-family: Ahem;
+ }
+ selectlist {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ height: 30px;
+ }
+
+ [popover] {
+ width: fit-content;
+ height: fit-content;
+ background: white;
+ color: black;
+ border: 1px solid rgba(0, 0, 0, 1);
+ 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>
+
+<selectlist id="selectList0">
+ <div popover slot="listbox" behavior="listbox">
+ <option>
+ option with image displayed
+ <img src="/images/green-256x256.png">
+ </option>
+ </div>
+</selectlist>
+
+<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 selectList0 = document.getElementById("selectList0");
+
+ await clickOn(selectList0);
+ document.documentElement.classList.remove('reftest-wait');
+ }
+
+ test();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-not-displayed-ref.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-not-displayed-ref.tentative.html
new file mode 100644
index 0000000000..fa44198fff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-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>
+
+<selectlist>
+</selectlist>
+
+<option>
+ option with image not displayed
+</option> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-not-displayed.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-not-displayed.tentative.html
new file mode 100644
index 0000000000..e7cacdba27
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-arbitrary-content-not-displayed.tentative.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<title>HTMLSelectListElement Test: option arbitrary content not displayed</title>
+<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
+<link rel=match href="selectlist-option-arbitrary-content-not-displayed-ref.tentative.html">
+
+<option>
+ option with image not displayed
+ <img src="/images/green-256x256.png">
+</option>
+
+<selectlist id="selectList0">
+ <option id="selectList0-option">
+ option with image not displayed
+ <img src="/images/green-256x256.png">
+ </option>
+</selectlist>
+
+<script>
+ const selectList0Option = document.getElementById("selectList0-option");
+
+ // removing an option from <selectlist> should revert back to its original display behavior
+ selectList0Option.remove();
+ document.body.append(selectList0Option);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-focusable.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-focusable.tentative.html
new file mode 100644
index 0000000000..993ef007e6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-focusable.tentative.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectListElement 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>
+
+<selectlist id="selectlist0">
+ <option>one</option>
+ <option id="selectlist0-option2">two</option>
+ <option>three</option>
+</selectlist>
+
+<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 selectList = document.querySelector("#selectlist0");
+ assert_false(selectList.open, "selectlist should not be initially open");
+
+ await clickOn(selectList);
+ assert_true(selectList.open);
+ assert_equals(selectList.value, "one");
+
+ const option2 = document.querySelector('#selectlist0-option2');
+ option2.focus();
+ assert_equals(document.activeElement, option2);
+
+ await test_driver.send_keys(selectList, KEY_CODE_MAP.Enter);
+ assert_equals(selectList.value, "two");
+}, "Validate <option> is focusable when is a descendant of <selectlist>");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-label-rendering-ref.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-label-rendering-ref.html
new file mode 100644
index 0000000000..1ab1d54722
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-label-rendering-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<selectlist>
+ <option>textcontent</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-label-rendering.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-label-rendering.tentative.html
new file mode 100644
index 0000000000..c719ee0e07
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-option-label-rendering.tentative.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=match href="selectlist-option-label-rendering-ref.html">
+<link rel=assert title="option elements should not render their label attributes when used in selectlist.">
+
+<selectlist>
+ <option label=label>textcontent</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-overflow-x-ref.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-overflow-x-ref.tentative.html
new file mode 100644
index 0000000000..df87060359
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-overflow-x-ref.tentative.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<style>
+selectlist {
+ width:30px;
+ height:20px;
+}
+selectlist::part(button) {
+ background-color:blue;
+}
+</style>
+<selectlist>
+ <option>&nbsp;</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-overflow-x.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-overflow-x.tentative.html
new file mode 100644
index 0000000000..93c1b948a0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-overflow-x.tentative.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<!-- Tests that selectlist button part text is truncated by the button. -->
+<link rel=author href="mailto:pkotwicz@chromium.org">
+<link rel="match" href="selectlist-overflow-x-ref.tentative.html">
+<style>
+selectlist {
+ width:30px;
+ height:20px;
+}
+selectlist::part(button) {
+ background-color:blue;
+}
+</style>
+<selectlist>
+ <option>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-parts-structure.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-parts-structure.tentative.html
new file mode 100644
index 0000000000..104ae841f7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-parts-structure.tentative.html
@@ -0,0 +1,495 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectListElement 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>
+
+<selectlist id="selectList0">
+ <div popover slot="listbox" behavior="listbox">
+ <option id="selectList0-child1">one</option>
+ <option id="selectList0-child2">two</option>
+ <div behavior="option" id="selectList0-child3">three</div>
+ </div>
+ <option id="selectList0-child4">four</option>
+</selectlist>
+
+<selectlist id="selectList1">
+ <listbox id="selectList1-popover">
+ <button type=selectlist id="selectList1-button">
+ Custom button
+ </button>
+ <option>one</option>
+ <option id="selectList1-child2">two</option>
+ </listbox>
+</selectlist>
+
+<selectlist id="selectList2">
+ <button type=selectlist id="selectList2-button">
+ Custom button
+ <listbox id="selectList2-popover">
+ Custom listbox
+ </listbox>
+ </button>
+ <option>three</option>
+ <div>
+ This is some text.
+ <option id="selectList2-child4">four</option>
+ More text.
+ </div>
+</selectlist>
+
+<selectlist id="selectList3">
+ <div slot="button" id="selectList3-button-slot">
+ <button type=selectlist id="selectList3-button0">button0</button>
+ </div>
+ <option>one</option>
+</selectlist>
+
+<selectlist id="selectList4">
+ <button type=selectlist id="selectList4-button0">button0</button>
+ <div slot="listbox" id="selectList4-listbox-slot">
+ <div popover behavior="listbox" id="selectList4-listbox0">
+ <option>one</option>
+ <option id="selectList4-option2">two</option>
+ </div>
+ </div>
+</selectlist>
+
+<selectlist id="selectList5">
+ <div slot="button" id="selectList5-button-slot">
+ <button type=selectlist id="selectList5-button0">button0</button>
+ <div behavior="selected-value" id="selectList5-selectedValue0"></div>
+ </div>
+ <option>one</option>
+ <option id="selectList5-option0">two</option>
+</selectlist>
+
+<!-- No associated JS test -- just don't crash when parsing! -->
+<selectlist id="selectList6">
+ <div slot="button"></div>
+ <div popover slot="listbox" behavior="listbox"></div>
+</selectlist>
+
+<!-- No associated JS test -- just don't crash when parsing! -->
+<selectlist id="selectList7">
+ <div slot="listbox"></div>
+ <button type=selectlist></button>
+</selectlist>
+
+<!-- No associated JS test -- just don't crash when parsing! -->
+<selectlist id="selectList8">
+ <div slot="listbox"></div>
+ <option>one</option>
+</selectlist>
+
+<selectlist id="selectList9">
+ <div slot="listbox" id="selectList9-listbox-slot">
+ <div popover behavior="listbox" id="selectList9-originalListbox">
+ <option>one</option>
+ <option id="selectList9-option2">two</option>
+ </div>
+ </div>
+</selectlist>
+
+<selectlist id="selectList11">
+ <div popover slot="listbox" behavior="listbox">
+ <option>one</option>
+ </div>
+ <div slot="button" behavior="listbox" id="selectList11-button">Test</div>
+</selectlist>
+
+<selectlist id="selectList12">
+ <button type=selectlist id="selectList12-button0">button0</button>
+ <listbox id="selectList12-listbox">
+ <option id="selectList12-option1">one</option>
+ <option>two</option>
+ </listbox>
+</selectlist>
+
+<selectlist id="selectList13">
+ <div slot="button" id="selectList12-button-slot">
+ <div id="selectList13-removeContent-button">
+ <button type=selectlist id="selectList13-button0">button0</button>
+ <button type=selectlist id="selectList13-button1">button1</button>
+ </div>
+ <button type=selectlist id="selectList13-button2">button2</button>
+ </div>
+ <div slot="listbox" id="selectList13-listbox-slot">
+ <div id="selectList13-removeContent-listbox">
+ <div popover behavior="listbox" id="selectList13-originalListbox">
+ <option id="selectList13-option1">one</option>
+ <option id="selectList13-option2">two</option>
+ </div>
+ </div>
+ <div popover behavior="listbox" id="selectList13-newListbox">
+ <option>three</option>
+ <option id="selectList13-option4">four</option>
+ </div>
+ </div>
+</selectlist>
+
+<selectlist id="selectList14">
+ <button type=selectlist id="selectList14-button0">button0</button>
+ <option>one</option>
+ <option id="selectList14-option2">two</option>
+</selectlist>
+
+<selectlist id="selectList15">
+ <div slot="button" id="selectList15-div0"></div>
+ <option>one</option>
+</selectlist>
+
+<selectlist id="selectList16">
+ <div slot="button">
+ <div id="selectList16-div0">
+ <button type=selectlist id="selectList16-button0">button</button>
+ </div>
+ </div>
+ <option>one</option>
+</selectlist>
+
+<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 selectList0 = document.getElementById("selectList0");
+ const selectList0Child1 = document.getElementById("selectList0-child1");
+ const selectList0Child2 = document.getElementById("selectList0-child2");
+ const selectList0Child3 = document.getElementById("selectList0-child3");
+ assert_equals(selectList0.value, "one");
+ await clickOn(selectList0);
+ await clickOn(selectList0Child2);
+ assert_equals(selectList0.value, "two");
+
+ await clickOn(selectList0);
+ await clickOn(selectList0Child3);
+ assert_equals(selectList0.value, "two", "Clicking a non-HTMLOptionElement labeled as an option should do nothing");
+
+ await clickOn(selectList0Child1);
+ assert_equals(selectList0.value, "one");
+ assert_false(selectList0.open);
+ }, "HTMLOptionElements (and not other element types) should receive option controller code");
+
+
+ promise_test(async () => {
+ const selectList0 = document.getElementById("selectList0");
+ const selectList0Child4 = document.getElementById("selectList0-child4");
+
+ assert_equals(selectList0.value, "one");
+ await clickOn(selectList0);
+ assert_true(selectList0.open);
+ selectList0Child4.click();
+ assert_equals(selectList0.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 selectList1 = document.getElementById("selectList1");
+ const selectList1Popover = document.getElementById("selectList1-popover");
+ const selectList1Button = document.getElementById("selectList1-button");
+ const selectList1Child2 = document.getElementById("selectList1-child2");
+ assert_false(selectList1Popover.matches(':popover-open'));
+ selectList1Button.click();
+ assert_false(selectList1Popover.matches(':popover-open'), "Clicking a button part that is a descendant of the listbox part should have no effect");
+
+ assert_equals(selectList1.value, "one");
+ await clickOn(selectList1);
+ assert_true(selectList1Popover.matches(':popover-open'));
+ await clickOn(selectList1Child2);
+ assert_equals(selectList1.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 selectList2 = document.getElementById("selectList2");
+ const selectList2Popover = document.getElementById("selectList2-popover");
+ const selectList2Button = document.getElementById("selectList2-button");
+ const selectList2Child2 = document.getElementById("selectList2-child2");
+ const selectList2Child4 = document.getElementById("selectList2-child4");
+
+ assert_false(selectList2Popover.matches(':popover-open'));
+ await clickOn(selectList2Button);
+ assert_false(selectList2Popover.matches(':popover-open'), "Clicking a button part should not show an invalid listbox part");
+
+ assert_equals(selectList2.value, "three");
+ await clickOn(selectList2Child4);
+ assert_equals(selectList2.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 selectList3 = document.getElementById("selectList3");
+ const selectList3ButtonSlot = document.getElementById("selectList3-button-slot");
+ const selectList3Button0 = document.getElementById("selectList3-button0");
+
+ assert_false(selectList3.open);
+
+ let button1 = document.createElement("div");
+ button1.innerText = "button1";
+ button1.setAttribute("behavior", "button");
+ selectList3ButtonSlot.appendChild(button1);
+
+ await clickOn(button1);
+ assert_false(selectList3.open, "A button part should only get controller code if it's first in document order, even if added dynamically");
+
+ await clickOn(selectList3Button0);
+ assert_true(selectList3.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 selectList4 = document.getElementById("selectList4");
+ const selectList4Button0 = document.getElementById("selectList4-button0");
+ const selectList4ListboxSlot = document.getElementById("selectList4-listbox-slot");
+ const selectList4Option2 = document.getElementById("selectList4-option2");
+
+ assert_false(selectList4.open);
+
+ let listbox2 = document.createElement("div");
+ listbox2.innerHTML = `
+ <option>three</option>
+ <option id="selectList4-option4">four</option>
+ `;
+ listbox2.setAttribute("behavior", "listbox");
+ selectList4ListboxSlot.appendChild(listbox2);
+
+ await clickOn(selectList4Button0);
+ assert_true(selectList4.open);
+
+ const selectList4Option4 = document.getElementById("selectList4-option4");
+ await clickOn(selectList4Option4);
+ assert_equals(selectList3.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(selectList4Button0);
+ assert_true(selectList4.open);
+
+ await clickOn(selectList4Option2);
+ assert_equals(selectList4.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 selectList5 = document.getElementById("selectList5");
+ const selectList5ButtonSlot = document.getElementById("selectList5-button-slot");
+ const selectList5Button0 = document.getElementById("selectList5-button0");
+ const selectList5SelectedValue0 = document.getElementById("selectList5-selectedValue0");
+
+ assert_false(selectList3.open);
+ assert_equals(selectList5SelectedValue0.innerText, "one");
+
+ let selectedValue1 = document.createElement("div");
+ selectList5ButtonSlot.appendChild(selectedValue1);
+
+ await clickOn(selectList5Button0);
+ assert_true(selectList5.open);
+
+ await clickOn(document.getElementById("selectList5-option0"));
+ assert_false(selectList5.open);
+ assert_equals(selectList5SelectedValue0.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 selectList = document.getElementById("selectList9");
+ const originalListbox = document.getElementById("selectList9-originalListbox");
+ const option2 = document.getElementById("selectList9-option2");
+ assert_equals(selectList.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(selectList);
+ assert_true(selectList.open, "Menu should open when clicked");
+
+ option2.click(); // clickOn doesn't work because the old options are not displayed
+ assert_equals(selectList.value, "three", "Elements in second popover should no longer be option parts");
+ assert_true(selectList.open, "Clicking non-part options shouldn't close the popover");
+
+ await clickOn(newOption2);
+ assert_false(selectList.open);
+ assert_equals(selectList.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 selectList = document.getElementById("selectList11");
+ const selectList11Button= document.getElementById("selectList11-button");
+
+ await clickOn(selectList11Button);
+ assert_false(selectList.open, "Controller code not applied due to part attribute not being button");
+ selectList11Button.remove();
+
+ await clickOn(selectList);
+ assert_true(selectList.open, "Default button part should be used");
+ }, "Ensure that controller code is applied when slot and part attributes are different");
+
+ promise_test(async () => {
+ const selectList = document.getElementById("selectList12");
+ const originalListbox = document.getElementById("selectList12-listbox");
+ assert_equals(selectList.value, "one", "Initial value should be the first option");
+
+ const selectListButton0 = document.getElementById("selectList12-button0");
+ const selectListOption1 = document.getElementById("selectList12-option1");
+
+ assert_false(selectList.open);
+ let button1 = document.createElement("button");
+ button1.innerText = "button1";
+ button1.setAttribute("type", "selectlist");
+ selectList.insertBefore(button1, selectListButton0);
+ button1.click();
+ assert_true(selectList.open, "Controller code should be applied to the new first button in document order");
+ await clickOn(selectListOption1);
+ assert_false(selectList.open, "listbox should close after clicking option1.");
+ selectListButton0.click();
+ assert_true(selectList.open, "listbox should open after clicking button0.");
+ selectListButton0.click();
+ assert_false(selectList.open, "listbox should close after clicking button0 again.");
+
+ let button2 = document.createElement("button");
+ button2.innerText = "button2";
+ selectList.insertBefore(button2, button1);
+ button2.click();
+ assert_false(selectList.open, "Controller code should not be applied to button2 since it doesn't have type=selectlist.");
+ button2.setAttribute("type", "selectlist");
+ button2.click();
+ assert_true(selectList.open, "Controller code should be applied to the new button part");
+ await clickOn(selectListOption1);
+ assert_false(selectList.open);
+
+ let newListbox = document.createElement("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(selectList.value, "three", "New value should be the first option");
+
+ newListbox.innerHTML = "<option>five</option><option>six</option>";
+ assert_equals(selectList.value, "five", "New value should be the first option");
+
+ selectList.innerHTML = "<option>seven</option><option id='selectList12-option2'>eight</option>";
+ assert_equals(selectList.value, "seven", "New value should be the first option");
+ const selectListOption2 = document.getElementById("selectList12-option2");
+ await clickOn(selectList);
+ assert_true(selectList.open);
+ await clickOn(selectListOption2);
+ assert_equals(selectList.value, "eight", "Controller code should be applied to new options");
+
+ selectListOption2.slot = "button";
+ assert_equals(selectList.value, "seven", "Previous selected option should become invalid");
+ }, "Ensure that controller code is synchronously applied");
+
+ promise_test(async () => {
+ const selectList = document.getElementById("selectList13");
+ assert_equals(selectList.value, "one");
+
+ const selectListButton0 = document.getElementById("selectList13-button0");
+ const selectListButton1 = document.getElementById("selectList13-button1");
+ selectListButton1.click();
+ assert_false(selectList.open);
+ selectListButton0.click();
+ assert_true(selectList.open, "First button should receive controller code");
+ await clickOn(document.getElementById("selectList13-option2"));
+ assert_equals(selectList.value, "two");
+ let divButtonToRemove = document.getElementById("selectList13-removeContent-button");
+ divButtonToRemove.innerHTML = "";
+ selectListButton0.click();
+ assert_false(selectList.open, "The first button is invalid");
+ const selectListButton2 = document.getElementById("selectList13-button2");
+ selectListButton2.click();
+ assert_true(selectList.open, "The button part should be updated")
+ await clickOn(document.getElementById("selectList13-option1"));
+ assert_equals(selectList.value, "one");
+
+ const selectListOption4 = document.getElementById("selectList13-option4");
+ selectListOption4.click();
+ assert_equals(selectList.value, "one");
+ let divListboxToRemove = document.getElementById("selectList13-removeContent-listbox");
+ divListboxToRemove.innerHTML = "";
+ assert_equals(selectList.value, "three", "The listbox part should be updated");
+ selectListOption4.click();
+ assert_equals(selectList.value, "four", "Controller code should be applied to the new options");
+
+ let selectListNewListbox = document.getElementById("selectList13-newListbox");
+ selectListNewListbox.innerHTML = "";
+ assert_equals(selectList.value, "");
+ selectListOption4.click();
+ assert_equals(selectList.value, "");
+ }, "Controller code should be updated when nested parts are removed");
+
+ promise_test(async () => {
+ let selectList = document.getElementById("selectList14");
+ assert_equals(selectList.value, "one");
+ const selectListButton0 = document.getElementById("selectList14-button0");
+ const selectListOption2 = document.getElementById("selectList14-option2");
+
+ selectListButton0.click();
+ assert_true(selectList.open);
+ await clickOn(selectListOption2);
+ assert_equals(selectList.value, "two");
+
+ document.body.removeChild(selectList);
+ selectList.removeChild(selectListOption2);
+ assert_equals(selectList.value, "one");
+ let newOption = document.createElement("option");
+ newOption.innerText = "three";
+ selectList.appendChild(newOption);
+ newOption.click();
+ assert_equals(selectList.value, "three", "New option should receive controller code");
+
+ let doc = document.implementation.createHTMLDocument('');
+ let selectList1 = doc.createElement('selectlist');
+ let firstOption = doc.createElement('option');
+ firstOption.innerText = 'one';
+ let secondOption = doc.createElement('option');
+ secondOption.innerText = 'two';
+ selectList1.appendChild(firstOption);
+ selectList1.appendChild(secondOption);
+ assert_equals(selectList1.value, "one");
+ secondOption.click();
+ assert_equals(selectList1.value, "two");
+ document.body.appendChild(selectList1);
+ selectList1.removeChild(secondOption);
+ assert_equals(selectList1.value, "one");
+ }, "Moving a selectlist between documents should keep controller code active");
+
+ promise_test(async () => {
+ const selectList = document.getElementById("selectList15");
+ const selectListButtonContainer = document.getElementById("selectList15-div0");
+
+ const outerDiv = document.createElement("div");
+ const button = document.createElement("input");
+ button.type = button.value = "button";
+ button.setAttribute("behavior", "button");
+ outerDiv.appendChild(button);
+ selectListButtonContainer.appendChild(outerDiv);
+
+ await clickOn(selectList);
+ assert_true(selectList.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 selectList = document.getElementById("selectList16");
+ const selectListButtonContainer = document.getElementById("selectList16-div0");
+ const selectListButton = document.getElementById("selectList16-button0");
+
+ selectListButtonContainer.remove();
+
+ selectListButton.click();
+ assert_false(selectList.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-selectlist-element/selectlist-popover-position-with-zoom.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-popover-position-with-zoom.tentative.html
new file mode 100644
index 0000000000..692eed7caa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-popover-position-with-zoom.tentative.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+<title>HTMLSelectListElement 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>
+ #selectList0 {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ zoom: 2;
+ }
+
+ #selectList1 {
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+ zoom: 1.5;
+ }
+
+ #selectList1-popover {
+ zoom: 2;
+ }
+
+ #selectList2 {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ zoom: 3;
+ }
+
+ #selectList3 {
+ position: absolute;
+ bottom: 0px;
+ right: 0px;
+ zoom: 4;
+ }
+
+ #selectList3-popover {
+ zoom: 1.5;
+ }
+</style>
+
+<selectlist id="selectList0">
+ <button type=selectlist id="selectList0-button">Custom bottom left</button>
+ <listbox id="selectList0-popover">
+ <option>bottom left</option>
+ <option>two</option>
+ <option>three</option>
+ </listbox>
+</selectlist>
+<br>
+
+<selectlist id="selectList1">
+ <button type=selectlist id="selectList1-button">Custom top left</button>
+ <listbox id="selectList1-popover">
+ <option>top left</option>
+ <option>two</option>
+ <option>three</option>
+ </listbox>
+</selectlist>
+
+<selectlist id="selectList2">
+ <button type=selectlist id="selectList2-button">Custom bottom right</button>
+ <listbox id="selectList2-popover">
+ <option>bottom right</option>
+ <option>two</option>
+ <option>three</option>
+ </listbox>
+</selectlist>
+
+<selectlist id="selectList3">
+ <button type=selectlist id="selectList3-button">Custom top right</button>
+ <listbox id="selectList3-popover">
+ <option>top right</option>
+ <option>two</option>
+ <option>three</option>
+ </listbox>
+</selectlist>
+
+<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 selectList0 = document.getElementById("selectList0");
+ const selectList0Popover = document.getElementById("selectList0-popover");
+ const selectList0Button = document.getElementById("selectList0-button");
+
+ await clickOn(selectList0);
+ assert_equals(Math.abs(Math.trunc(selectList0.getBoundingClientRect().bottom - selectList0Popover.getBoundingClientRect().top)), 0);
+ assert_equals(Math.abs(Math.trunc(selectList0.getBoundingClientRect().left - selectList0Popover.getBoundingClientRect().left)), 0);
+ }, "The popover should be bottom left positioned");
+
+ promise_test(async () => {
+ const selectList1 = document.getElementById("selectList1");
+ const selectList1Popover = document.getElementById("selectList1-popover");
+ const selectList1Button = document.getElementById("selectList1-button");
+
+ selectList1Button.click();
+ assert_equals(Math.abs(Math.trunc(selectList1.getBoundingClientRect().top - selectList1Popover.getBoundingClientRect().bottom * 2)), 0);
+ assert_equals(Math.abs(Math.trunc(selectList1.getBoundingClientRect().left - selectList1Popover.getBoundingClientRect().left * 2)), 0);
+ }, "The popover should be top left positioned");
+
+ promise_test(async () => {
+ const selectList2 = document.getElementById("selectList2");
+ const selectList2Popover = document.getElementById("selectList2-popover");
+ const selectList2Button = document.getElementById("selectList2-button");
+
+ selectList2Button.click();
+ assert_equals(Math.abs(Math.trunc(selectList2.getBoundingClientRect().bottom - selectList2Popover.getBoundingClientRect().top)), 0);
+ assert_equals(Math.abs(Math.trunc(selectList2.getBoundingClientRect().right - selectList2Popover.getBoundingClientRect().right)), 0);
+ }, "The popover should be bottom right positioned");
+
+ promise_test(async () => {
+ const selectList3 = document.getElementById("selectList3");
+ const selectList3Popover = document.getElementById("selectList3-popover");
+ const selectList3Button = document.getElementById("selectList3-button");
+
+ selectList3Button.click();
+ assert_equals(Math.abs(Math.trunc(selectList3.getBoundingClientRect().top - selectList3Popover.getBoundingClientRect().bottom * 1.5)), 0);
+ assert_equals(Math.abs(Math.trunc(selectList3.getBoundingClientRect().right - selectList3Popover.getBoundingClientRect().right * 1.5)), 0);
+ }, "The popover should be top right positioned");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-popover-position.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-popover-position.tentative.html
new file mode 100644
index 0000000000..a19e2b0d28
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-popover-position.tentative.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html>
+<title>HTMLSelectListElement 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>
+ #selectList0 {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ }
+
+ #selectList1 {
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+ }
+
+ #selectList2 {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ }
+
+ #selectList3 {
+ position: absolute;
+ bottom: 0px;
+ right: 0px;
+ }
+</style>
+
+<selectlist id="selectList0">
+ <div popover slot="listbox" behavior="listbox" id="selectList0-popover">
+ <option>bottom left</option>
+ <option>two</option>
+ <option>three</option>
+ </div>
+</selectlist>
+<br>
+
+<selectlist id="selectList1">
+ <div popover slot="listbox" behavior="listbox" id="selectList1-popover">
+ <option>top left</option>
+ <option>two</option>
+ <option>three</option>
+ </div>
+</selectlist>
+
+<selectlist id="selectList2">
+ <div popover slot="listbox" behavior="listbox" id="selectList2-popover">
+ <option>bottom right</option>
+ <option>two</option>
+ <option>three</option>
+ </div>
+</selectlist>
+
+<selectlist id="selectList3">
+ <div popover slot="listbox" behavior="listbox" id="selectList3-popover">
+ <option>top right</option>
+ <option>two</option>
+ <option>three</option>
+ </div>
+</selectlist>
+
+<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 selectList0 = document.getElementById("selectList0");
+ const selectList0Popover = document.getElementById("selectList0-popover");
+
+ await clickOn(selectList0);
+ assert_equals(Math.abs(Math.trunc(selectList0.getBoundingClientRect().bottom - selectList0Popover.getBoundingClientRect().top)), 0);
+ assert_equals(Math.abs(Math.trunc(selectList0.getBoundingClientRect().left - selectList0Popover.getBoundingClientRect().left)), 0);
+ }, "The popover should be bottom left positioned");
+
+ promise_test(async () => {
+ const selectList1 = document.getElementById("selectList1");
+ const selectList1Popover = document.getElementById("selectList1-popover");
+
+ await clickOn(selectList1);
+ assert_equals(Math.abs(Math.trunc(selectList1.getBoundingClientRect().top - selectList1Popover.getBoundingClientRect().bottom)), 0);
+ assert_equals(Math.abs(Math.trunc(selectList1.getBoundingClientRect().left - selectList1Popover.getBoundingClientRect().left)), 0);
+ }, "The popover should be top left positioned");
+
+ promise_test(async () => {
+ const selectList2 = document.getElementById("selectList2");
+ const selectList2Popover = document.getElementById("selectList2-popover");
+
+ await clickOn(selectList2);
+ assert_equals(Math.abs(Math.trunc(selectList2.getBoundingClientRect().bottom - selectList2Popover.getBoundingClientRect().top)), 0);
+ assert_equals(Math.abs(Math.trunc(selectList2.getBoundingClientRect().right - selectList2Popover.getBoundingClientRect().right)), 0);
+ }, "The popover should be bottom right positioned");
+
+ promise_test(async () => {
+ const selectList3 = document.getElementById("selectList3");
+ const selectList3Popover = document.getElementById("selectList3-popover");
+
+ await clickOn(selectList3);
+ assert_equals(Math.abs(Math.trunc(selectList3.getBoundingClientRect().top - selectList3Popover.getBoundingClientRect().bottom)), 0);
+ assert_equals(Math.abs(Math.trunc(selectList3.getBoundingClientRect().right - selectList3Popover.getBoundingClientRect().right)), 0);
+ }, "The popover should be top right positioned");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-popover.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-popover.tentative.html
new file mode 100644
index 0000000000..a26d026649
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-popover.tentative.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<title>HTMLSelectListElement 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>
+
+<selectlist id="selectList0">
+ <option>one</option>
+ <option id="selectList0-child2">two</option>
+ <option>three</option>
+ <option>four</option>
+</selectlist>
+
+<selectlist id="selectList1">
+ <button type=selectlist class=button>
+ Custom button
+ </button>
+ <listbox>
+ <option>one</option>
+ <option class="child2">two</option>
+ <option class="child3">three</option>
+ </listbox>
+</selectlist>
+
+<selectlist id="selectList2">
+ <!-- Swap out the listbox part without providing a replacement -->
+ <div slot="listbox"></div>
+</selectlist>
+
+<selectlist id="selectList3">
+ <div slot="listbox">
+ <div popover behavior="listbox" id="selectList3-listbox">
+ <option>one</option>
+ </div>
+ </div>
+</selectlist>
+<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 selectList0 = document.getElementById("selectList0");
+ const selectList0Child2 = document.getElementById("selectList0-child2");
+ assert_equals(selectList0.value, "one");
+ assert_equals(selectList0.open, false);
+ await clickOn(selectList0);
+ assert_equals(selectList0.open, true);
+ await clickOn(selectList0Child2);
+ assert_equals(selectList0.value, "two");
+ assert_equals(selectList0.open, false);
+
+ await clickOn(selectList0);
+ assert_equals(selectList0.open, true);
+ await clickOn(selectList0Child2);
+ assert_equals(selectList0.open, false);
+ }, "Opening the popover and clicking an option should change the selectlist's value");
+
+ promise_test(async () => {
+ const selectList1 = document.getElementById("selectList1");
+ const button = selectList1.querySelector(".button");
+ const child2 = selectList1.querySelector(".child2");
+ const child3 = selectList1.querySelector(".child3");
+ assert_equals(selectList1.value, "one");
+ assert_equals(selectList1.open, false);
+ await clickOn(button);
+ assert_equals(selectList1.open, true);
+ await clickOn(child2);
+ assert_equals(selectList1.value, "two", "Clicking an <option> should change the value");
+ assert_equals(selectList1.open, false);
+
+ await clickOn(button);
+ assert_equals(selectList1.open, true);
+ await clickOn(child3);
+ assert_equals(selectList1.value, "three", "Clicking a <div part='option'> should change the value");
+ assert_equals(selectList1.open, false);
+ }, "With custom button and popover: opening the popover and clicking an option should change the selectlist's value");
+
+ promise_test(async () => {
+ const selectList2 = document.getElementById("selectList2");
+ await clickOn(selectList2);
+ assert_equals(selectList2.value, "");
+ assert_equals(selectList2.open, false);
+ }, "Clicking a popover with no listbox part does nothing");
+
+ promise_test(async () => {
+ const selectList3 = document.getElementById("selectList3");
+ const selectList3Listbox = document.getElementById("selectList3-listbox");
+ selectList3Listbox.remove();
+
+ await clickOn(selectList3);
+ assert_equals(selectList3.value, "");
+ assert_equals(selectList3.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-selectlist-element/selectlist-pseudo-light-dismiss-invalidation.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-pseudo-light-dismiss-invalidation.tentative.html
new file mode 100644
index 0000000000..bda5842a37
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-pseudo-light-dismiss-invalidation.tentative.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="http://crbug.com/1429839">
+<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>
+
+<selectlist id=selectlist>
+ <option id=optone>one</option>
+ <option id=opttwo>two</option>
+</selectlist>
+<style>
+selectlist {
+ background-color: rgb(0, 0, 255);
+}
+selectlist:closed {
+ background-color: rgb(0, 255, 0);
+}
+selectlist:open {
+ background-color: rgb(255, 0, 0);
+}
+</style>
+<button id=button>hello world</button>
+
+<script>
+promise_test(async () => {
+ assert_equals(getComputedStyle(selectlist).backgroundColor, 'rgb(0, 255, 0)',
+ 'The selectlist should match :closed at the start of the test.');
+
+ await test_driver.click(selectlist);
+ assert_equals(getComputedStyle(selectlist).backgroundColor, 'rgb(255, 0, 0)',
+ 'The selectlist should match :open when opened.');
+
+ await test_driver.click(opttwo);
+ assert_equals(getComputedStyle(selectlist).backgroundColor, 'rgb(0, 255, 0)',
+ 'The selectlist should match :closed after clicking an option.');
+
+ await test_driver.click(selectlist);
+ assert_equals(getComputedStyle(selectlist).backgroundColor, 'rgb(255, 0, 0)',
+ 'The selectlist should match :open when reopened.');
+
+ await test_driver.click(button);
+ assert_equals(getComputedStyle(selectlist).backgroundColor, 'rgb(0, 255, 0)',
+ 'The selectlist should match :closed after light dismiss.');
+}, 'selectlist should not match :open when light dismissed.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-pseudo-open-closed.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-pseudo-open-closed.tentative.html
new file mode 100644
index 0000000000..1d5a082c03
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-pseudo-open-closed.tentative.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/547">
+<link rel=help href="https://drafts.csswg.org/selectors/#open-state">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<selectlist id=myselectlist>
+ <button type=selectlist id=custombutton>button</button>
+ <option>one</option>
+ <option>two</option>
+</selectlist>
+
+<script>
+test(() => {
+ assert_false(myselectlist.matches(':open'),
+ 'Selectlist should not match :open while it is closed.');
+ assert_true(myselectlist.matches(':closed'),
+ 'Selectlist should match :closed while it is closed.');
+
+ custombutton.click();
+
+ assert_true(myselectlist.matches(':open'),
+ 'Selectlist should match :open while it is open.');
+ assert_false(myselectlist.matches(':closed'),
+ 'Selectlist should not match :closed while it is open.');
+}, 'Selectlist should support :open and :closed pseudo selectors.');
+</script>
+
+<selectlist id=selectlistinvalidation>
+ <button type=selectlist>button</button>
+ <option>one</option>
+ <option>two</option>
+</selectlist>
+<style>
+selectlist:closed {
+ background-color: red;
+}
+selectlist:open {
+ background-color: green;
+}
+</style>
+
+<script>
+test(() => {
+ const selectlist = document.getElementById('selectlistinvalidation');
+ const button = selectlist.querySelector('button');
+ const option = selectlist.querySelector('option');
+
+ assert_equals(getComputedStyle(selectlist).backgroundColor, 'rgb(255, 0, 0)',
+ 'The style rules from :closed should apply when the selectlist is closed.');
+
+ button.click();
+ assert_equals(getComputedStyle(selectlist).backgroundColor, 'rgb(0, 128, 0)',
+ 'The style rules from :open should apply when the selectlist is open.');
+
+ option.click();
+ assert_equals(getComputedStyle(selectlist).backgroundColor, 'rgb(255, 0, 0)',
+ 'The style rules from :closed should apply when the selectlist is opened and closed again.');
+}, 'Selectlist :open and :closed should invalidate correctly.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-required-attribute.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-required-attribute.tentative.html
new file mode 100644
index 0000000000..ef4408915b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-required-attribute.tentative.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectListElement 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>
+ selectlist:required {
+ border: 3px dashed rgb(255, 0, 0);
+ }
+
+ selectlist:optional {
+ border: 1px solid rgb(128, 128, 128);
+ }
+</style>
+
+<selectlist id="selectlist0" required>
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectlist>
+
+<selectlist id="selectlist1">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectlist>
+
+<selectlist id="selectlist2">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectlist>
+
+<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 selectList0 = document.getElementById("selectlist0");
+ const selectList1 = document.getElementById("selectlist1");
+ const selectList2 = document.getElementById("selectlist2");
+
+ checkRequired(window.getComputedStyle(selectList0));
+ checkOptional(window.getComputedStyle(selectList1));
+ checkOptional(window.getComputedStyle(selectList2));
+ selectList2.required = true;
+ checkRequired(window.getComputedStyle(selectList2));
+}, "Test required attribute");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-rtl-ref.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-rtl-ref.tentative.html
new file mode 100644
index 0000000000..7c71ea079c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-rtl-ref.tentative.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script src="support/fake-selectlist.js"></script>
+<body>
+<script>
+ const selectlist = createFakeSelectlist('option');
+ document.body.appendChild(selectlist);
+ const button = selectlist.querySelector('.fake-selectlist-internal-selectlist-button');
+ button.style.direction = "rtl";
+ button.style.width = "300px";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-rtl.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-rtl.tentative.html
new file mode 100644
index 0000000000..24f7959632
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-rtl.tentative.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<!-- Tests selectlist text alignment in rtl -->
+<link rel=author href="mailto:pkotwicz@chromium.org">
+<link rel="match" href="selectlist-rtl-ref.tentative.html">
+
+<style>
+selectlist {
+ direction:rtl;
+ width:300px;
+}
+</style>
+<selectlist>
+ <option>option</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-behavior-ref.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-behavior-ref.html
new file mode 100644
index 0000000000..7d76f1c817
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-behavior-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="support/fake-selectlist.js"></script>
+<body>
+<script>
+ const selectlist = createFakeSelectlist('hello world');
+ document.body.appendChild(selectlist);
+ selectlist.querySelector('.fake-selectlist-selected-value')
+ .style.color = 'blue';
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-behavior.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-behavior.tentative.html
new file mode 100644
index 0000000000..a43e43d267
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-behavior.tentative.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=match href="selectlist-selected-value-behavior-ref.html">
+
+<selectlist>
+ <div style="color:blue" slot=selected-value behavior=selected-value></div>
+ <option>hello world</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-part-ref.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-part-ref.html
new file mode 100644
index 0000000000..3be168beba
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-part-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="support/fake-selectlist.js"></script>
+<body>
+<script>
+ const selectlist = createFakeSelectlist('hello world');
+ document.body.appendChild(selectlist);
+ selectlist.querySelector('.fake-selectlist-selected-value')
+ .style.backgroundColor = 'red';
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-part.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-part.tentative.html
new file mode 100644
index 0000000000..8fc05480d1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-part.tentative.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=match href="selectlist-selected-value-part-ref.html">
+
+<style>
+selectlist::part(selected-value) {
+ background-color: red;
+}
+</style>
+<selectlist>
+ <option>hello world</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-slot-ref.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-slot-ref.html
new file mode 100644
index 0000000000..ecb8c886a1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-slot-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="support/fake-selectlist.js"></script>
+<body>
+<script>
+ const selectlist = createFakeSelectlist('hello world');
+ document.body.appendChild(selectlist);
+
+ const oldSelectedValue = selectlist.querySelector('.fake-selectlist-selected-value');
+ const newSelectedValue = document.createElement('div');
+ newSelectedValue.textContent = 'new selected value';
+
+ replaceChildElement(newSelectedValue, oldSelectedValue);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-slot.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-slot.tentative.html
new file mode 100644
index 0000000000..c69a962c29
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selected-value-slot.tentative.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=match href="selectlist-selected-value-slot-ref.html">
+
+<selectlist>
+ <div slot=selected-value>new selected value</div>
+ <option>hello world</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selectedoption-element-cloning-ref.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selectedoption-element-cloning-ref.html
new file mode 100644
index 0000000000..1c0be58d67
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selectedoption-element-cloning-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<button>
+ <span style="color:red">red</span> one
+</button>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selectedoption-element-cloning.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selectedoption-element-cloning.tentative.html
new file mode 100644
index 0000000000..5d273c21bf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selectedoption-element-cloning.tentative.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/571">
+<link rel=match href="selectlist-selectedoption-element-cloning-ref.html">
+
+<selectlist>
+ <button type=selectlist>
+ <selectedoption></selectedoption>
+ </button>
+ <option><span style="color:red">red</span> one</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selectedoption-element.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selectedoption-element.tentative.html
new file mode 100644
index 0000000000..593c4a6f21
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-selectedoption-element.tentative.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/702">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<selectlist>
+ <div>Foo <selectedoption></selectedoption> Bar</div>
+ <option>one</option>
+ <option>two</option>
+</selectlist>
+
+<script>
+test(() => {
+ const selectlist = document.querySelector('selectlist');
+ const selectedoption = document.querySelector('selectedoption');
+ const div = document.querySelector('div');
+
+ assert_equals(selectlist.value, 'one', 'one should be selected initially.');
+ assert_equals(selectedoption.textContent, 'one', "selectedoption's initial text content should be one.");
+ assert_equals(div.textContent, 'Foo one Bar', 'Outer textContent should include default selectedoption.');
+
+ selectlist.value = 'two';
+ assert_equals(selectlist.value, 'two', "assigning two into selectlists's value should work.");
+ assert_equals(selectedoption.textContent, 'two', "selectedoption's text content should be updated with the new value.");
+ assert_equals(div.textContent, 'Foo two Bar', 'Outer textContent should include new selectedoption.');
+}, "<selectedoption>'s text contents should be replaced with its ancestor <selectlist>'s selected value.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-tab-navigation.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-tab-navigation.tentative.html
new file mode 100644
index 0000000000..3b7d9d3d9a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-tab-navigation.tentative.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<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 id="input1">
+<selectlist id="selectlist">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectlist>
+<input id="input3">
+
+<script>
+promise_test(async () => {
+ const TAB_KEY = "\uE004";
+
+ const input1 = document.getElementById("input1");
+ const selectlist = document.getElementById("selectlist");
+
+ input1.focus();
+ assert_equals(document.activeElement.id, "input1", "input1 should be active");
+
+ await test_driver.send_keys(input1, TAB_KEY);
+ assert_equals(document.activeElement.id, "selectlist", "selectlist should be active");
+
+ await test_driver.send_keys(selectlist, TAB_KEY);
+ assert_equals(document.activeElement.id, "input3", "input3 should be active");
+}, "Check that <selectlist> occupies just one slot in tab navigation.");
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-tabindex-order.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-tabindex-order.tentative.html
new file mode 100644
index 0000000000..c93efe1118
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-tabindex-order.tentative.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<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 id="input1">
+<selectlist id="selectlist" tabindex="2">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectlist>
+<input id="input3" tabindex="1">
+
+<script>
+promise_test(async () => {
+ const TAB_KEY = "\uE004";
+
+ const input1 = document.getElementById("input1");
+ const input3 = document.getElementById("input3");
+
+ input1.focus();
+ assert_equals(document.activeElement.id, "input1", "input1 should be active");
+
+ await test_driver.send_keys(input1, TAB_KEY);
+ assert_equals(document.activeElement.id, "input3", "input3 should be active");
+
+ await test_driver.send_keys(input3, TAB_KEY);
+ assert_equals(document.activeElement.id, "selectlist", "selectlist should be active");
+}, "Check that tabindex applies to <selectlist>");
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-text-only-ref.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-text-only-ref.html
new file mode 100644
index 0000000000..ab28fbede6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-text-only-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<style>
+.container {
+ display: inline-flex;
+ font-family: sans-serif;
+ font-size: 0.875em;
+}
+</style>
+<div class=container>text node</div>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-text-only.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-text-only.tentative.html
new file mode 100644
index 0000000000..ceefeca608
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-text-only.tentative.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/702">
+<link rel=match href="selectlist-text-only-ref.html">
+
+<selectlist>
+ text node
+ <option>option</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-user-select.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-user-select.tentative.html
new file mode 100644
index 0000000000..078e8a4d83
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-user-select.tentative.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/687">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<selectlist id=useragent>
+ <option id=useragentoptionone>one</option>
+ <option id=useragentoptiontwo>two</option>
+</selectlist>
+
+<selectlist id=custom>
+ <div id=custombutton slot=button behavior=button>button</div>
+ <div id=customlistbox popover=auto slot=listbox behavior=listbox>listbox</div>
+</selectlist>
+
+<selectlist id=customwithselection style="user-select:auto">
+ <div id=custombuttonwithselection slot=button behavior=button>button</div>
+ <div id=customlistboxwithselection popover=auto slot=listbox behavior=listbox>listbox</div>
+</selectlist>
+
+<selectlist id=customwithmixedselection>
+ <div id=custombuttonwithmixedselection slot=button behavior=button style="user-select:auto">button</div>
+ <div id=customlistboxwithmixedselection popover=auto slot=listbox behavior=listbox style="user-select:auto">listbox</div>
+</selectlist>
+
+<script>
+test(() => {
+ assert_equals(getComputedStyle(useragent).userSelect, 'none',
+ 'The selectlist should have user-select:none.');
+ assert_equals(getComputedStyle(useragentoptionone).userSelect, 'none',
+ 'The first option should have user-select:none.');
+ assert_equals(getComputedStyle(useragentoptiontwo).userSelect, 'none',
+ 'The second option should have user-select:none.');
+}, 'Option elements should have user-select:none without slotting buttons or listboxes.');
+
+test(() => {
+ assert_equals(getComputedStyle(custom).userSelect, 'none',
+ 'The selectlist should have user-select:none.');
+ assert_equals(getComputedStyle(custombutton).userSelect, 'none',
+ 'The custom button should have user-select:none.');
+ assert_equals(getComputedStyle(customlistbox).userSelect, 'none',
+ 'The custom listbox should have user-select:none.');
+}, 'Slotted in buttons and listboxes should have user-select:none.');
+
+test(() => {
+ assert_equals(getComputedStyle(customwithselection).userSelect, 'auto',
+ 'The selectlist should have user-select:auto.');
+ assert_equals(getComputedStyle(custombuttonwithselection).userSelect, 'auto',
+ 'The custom button should have user-select:auto.');
+ assert_equals(getComputedStyle(customlistboxwithselection).userSelect, 'auto',
+ 'The custom listbox should have user-select:auto.');
+}, 'Setting user-select:auto on selectlists should re-enable selection.');
+
+test(() => {
+ assert_equals(getComputedStyle(customwithmixedselection).userSelect, 'none',
+ 'The selectlist should have user-select:none.');
+ assert_equals(getComputedStyle(custombuttonwithmixedselection).userSelect, 'auto',
+ 'The custom button should have user-select:auto.');
+ assert_equals(getComputedStyle(customlistboxwithmixedselection).userSelect, 'auto',
+ 'The custom listbox should have user-select:auto.');
+}, 'Children of selectlist should be able to opt-in to user-select.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-validity.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-validity.tentative.html
new file mode 100644
index 0000000000..2b2033f668
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-validity.tentative.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html lang="en">
+<title>HTMLSelectListElement 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>
+
+<selectlist id="selectlist1" required>
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+ <option>four</option>
+</selectlist>
+
+<form>
+ <selectlist id="selectlist2" required>
+ </selectlist>
+</form>
+
+<script>
+
+test(() => {
+ let selectList = document.createElement('selectlist');
+ assert_true(selectList.willValidate, "A selectlist element is a submittable element that is a candidate for constraint validation.");
+ let option = document.createElement('option');
+ selectList.appendChild(option);
+ assert_true(selectList.checkValidity(), "Always valid when the selectlist isn't a required value.");
+
+ selectList.required = true;
+ assert_equals(selectList.value, "");
+ assert_false(selectList.checkValidity(), "A selected placeholder option should invalidate the selectlist.");
+
+ let emptyOption = document.createElement('option');
+ selectList.appendChild(emptyOption);
+ assert_false(selectList.checkValidity(), "A selected placeholder option should invalidate the selectlist even if there are multiple options.");
+ emptyOption.selected = true;
+ assert_true(selectList.checkValidity(), "An empty non-placeholder option should be a valid choice.");
+
+ let filledOption = document.createElement('option');
+ filledOption.value = "test";
+ selectList.appendChild(filledOption);
+ filledOption.selected = true;
+ assert_equals(selectList.value, "test", "The non-empty value should be set.");
+ assert_true(selectList.checkValidity(), "A non-empty non-placeholder option should be a valid choice.");
+
+ selectList.removeChild(option);
+ selectList.appendChild(emptyOption);
+ emptyOption.selected = true;
+ assert_equals(selectList.value, "", "The empty value should be set.");
+ assert_true(selectList.checkValidity(), "Only the first option can be seen as a placeholder.");
+
+ selectList.removeChild(filledOption);
+ assert_false(selectList.checkValidity(), "A selected placeholder option should invalidate the selectlist.");
+
+ emptyOption.value = "test2";
+ assert_equals(selectList.value, "test2");
+ assert_true(selectList.checkValidity(), "A non-empty option value should be a valid choice.");
+
+ emptyOption.removeAttribute("value");
+ assert_equals(selectList.value, "");
+ assert_false(selectList.checkValidity());
+ emptyOption.innerText = "test";
+ assert_equals(selectList.value, "test");
+ assert_true(selectList.checkValidity(), "A non-empty option should be a valid choice.");
+
+ const selectList1 = document.getElementById('selectlist1');
+ assert_equals(selectList1.value, "one");
+ assert_true(selectList1.checkValidity(), "A selectlist with non-empty placeholder option should be valid.");
+}, "Validation for placeholder option");
+
+test(() => {
+ const selectList2 = document.getElementById('selectlist2');
+ assert_equals(selectList2.value, "");
+ assert_false(selectList2.checkValidity());
+ let form = document.querySelector('form');
+ let invalidControl = form.querySelector('selectlist:invalid');
+ assert_equals(selectList2, 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 selectlist");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-value-option.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-value-option.tentative.html
new file mode 100644
index 0000000000..243067937c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-value-option.tentative.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/664">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<selectlist id=selectlist>
+ <option id=optone>innertext one</option>
+ <option id=opttwo value=valueattribute>innertext two</option>
+</selectlist>
+
+<script>
+test(() => {
+ assert_equals(selectlist.value, 'innertext one',
+ 'The first option should be selected initially.');
+ selectlist.value = 'valueattribute';
+ assert_equals(selectlist.value, 'valueattribute',
+ 'Assigning value should look at the options value, not innertext');
+}, 'selectlist.value should reflect option.value');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-value-selectedOption.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-value-selectedOption.tentative.html
new file mode 100644
index 0000000000..20e72ac1dc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-value-selectedOption.tentative.html
@@ -0,0 +1,224 @@
+<!DOCTYPE html>
+<title>HTMLSelectListElement Test: value and selectedOption</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<selectlist id="selectList0"></selectlist>
+
+<selectlist id="selectList1">
+ <option>one</option>
+ <option>two</option>
+ <div>I'm a div with no part attr</div>
+ <option id="selectList1-option3">three</option>
+ <option>four</option>
+</selectlist>
+
+<selectlist id="selectList2">
+ <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>
+</selectlist>
+
+<selectlist id="selectList3">
+ <div>I'm a div with no part attr</div>
+ <option id="selectList3-child1">one</option>
+ <option id="selectList3-child2">two</option>
+ <option id="selectList3-child3">three</option>
+</selectlist>
+
+<selectlist id="selectList4">
+ <div slot="button" behavior="button">
+ <div behavior="selected-value" id="selectList4-custom-selected-value">Default custom selected-value text</div>
+ </div>
+ <option id="selectList4-option1">one</option>
+ <option id="selectList4-option2">two</option>
+</selectlist>
+
+<selectlist id="selectList5">
+ <div slot="button" behavior="button">
+ <div behavior="selected-value" id="selectList5-custom-selected-value">Default custom selected-value text</div>
+ </div>
+ <div popover slot="listbox" behavior="listbox">
+ <option id="selectList5-option1">one</option>
+ <option id="selectList5-option2">two</option>
+ </div>
+</selectlist>
+
+<selectlist id="selectList6">
+ <option id="selectList6-option1">one</option>
+ <option id="selectList6-option2" selected>two</option>
+ <option id="selectList6-option3">three</option>
+</selectlist>
+
+<selectlist id="selectList7">
+ <option id="selectList7-option1">one</option>
+ <option id="selectList7-option2" selected value="test">two</option>
+ <option>three</option>
+</selectlist>
+
+<script>
+
+test(() => {
+ const selectList0 = document.getElementById("selectList0");
+ assert_equals(selectList0.value, "");
+ assert_equals(selectList0.selectedOption, null);
+ selectList0.value = "something";
+ assert_equals(selectList0.value, "", "If there is no matching option, selectlist should be cleared");
+ assert_equals(selectList0.selectedOption, null);
+}, "Test that HTMLSelectList with no options has empty string for value and null for selectedOption");
+
+test(() => {
+ const selectList1 = document.getElementById("selectList1");
+ assert_equals(selectList1.value, "one", "value should start with the text of the first option part");
+
+ selectList1.value = "three";
+ assert_equals(selectList1.value, "three", "value can be set to the text of an option part");
+ assert_equals(selectList1.selectedOption, document.getElementById("selectList1-option3"));
+
+ selectList1.value = "I'm a div with no part attr";
+ assert_equals(selectList1.value, "", "If there is no matching option selectlist should be cleared");
+ assert_equals(selectList1.selectedOption, null);
+}, "Test value and selectedOption with HTMLOptionElement element option parts");
+
+test(() => {
+ const selectList1 = document.getElementById("selectList1");
+ selectList1.value = "one";
+ assert_equals(selectList1.value, "one");
+
+ selectList1.value = null;
+ assert_equals(selectList1.value, "");
+ assert_equals(selectList1.selectedOption, null);
+}, "Test value and selectedOption when value is null");
+
+test(() => {
+ const selectList1 = document.getElementById("selectList1");
+ selectList1.value = "one";
+ assert_equals(selectList1.value, "one");
+
+ selectList1.value = undefined;
+ assert_equals(selectList1.value, "");
+ assert_equals(selectList1.selectedOption, null);
+}, "Test value and selectedOption when value is undefined");
+
+test(() => {
+ const selectList2 = document.getElementById("selectList2");
+ assert_equals(selectList2.value, "", "Non-HTMLOptionElements shouldn't be treated as option parts");
+ assert_equals(selectList2.selectedOption, null);
+
+ selectList2.value = "three";
+ assert_equals(selectList2.value, "", "value can't be set when there are no option parts'");
+ assert_equals(selectList2.selectedOption, null);
+}, "Test value with non-HTMLOptionElement elements labeled as parts");
+
+test(() => {
+ const selectList3 = document.getElementById("selectList3");
+ assert_equals(selectList3.value, "one", "value should start with the text of the first option part");
+ assert_equals(selectList3.selectedOption, document.getElementById("selectList3-child1"));
+
+ document.getElementById("selectList3-child3").remove();
+ assert_equals(selectList3.value, "one", "Removing a non-selected option should not change the value");
+ assert_equals(selectList3.selectedOption, document.getElementById("selectList3-child1"));
+
+ document.getElementById("selectList3-child1").remove();
+ assert_equals(selectList3.value, "two", "When the selected option is removed, the new first option should become selected");
+ assert_equals(selectList3.selectedOption, document.getElementById("selectList3-child2"));
+
+ document.getElementById("selectList3-child2").remove();
+ assert_equals(selectList3.value, "", "When all options are removed, value should be the empty string");
+ assert_equals(selectList3.selectedOption, null);
+}, "Test that value and selectedOption are updated when options are removed");
+
+test(() => {
+ const selectList4 = document.getElementById("selectList4");
+ let customSelectedValuePart = document.getElementById("selectList4-custom-selected-value");
+ assert_equals(selectList4.value, "one", "value should start with the text of the first option part");
+ assert_equals(selectList4.selectedOption, document.getElementById("selectList4-option1"));
+ assert_equals(customSelectedValuePart.innerText, "one", "Custom selected value part should be set to initial value of selectlist");
+
+ selectList4.value = "two";
+ assert_equals(customSelectedValuePart.innerText, "two", "Custom selected value part should be updated when value of selectlist changes");
+ assert_equals(selectList4.selectedOption, document.getElementById("selectList4-option2"));
+}, "Test that slotted-in selected-value part is updated to value of selectlist");
+
+test(() => {
+ const selectList5 = document.getElementById("selectList5");
+ let customSelectedValuePart = document.getElementById("selectList5-custom-selected-value");
+ assert_equals(selectList5.value, "one", "value should start with the text of the first option part");
+ assert_equals(selectList5.selectedOption, document.getElementById("selectList5-option1"));
+ assert_equals(customSelectedValuePart.innerText, "one", "Custom selected value part should be set to initial value of selectlist");
+
+ selectList5.value = "two";
+ assert_equals(customSelectedValuePart.innerText, "two", "Custom selected value part should be updated when value of selectlist changes");
+ assert_equals(selectList5.selectedOption, document.getElementById("selectList5-option2"));
+}, "Test that option parts in a slotted-in listbox are reflected in the value property");
+
+test(() => {
+ let selectList = document.createElement('selectlist');
+ assert_equals(selectList.value, "");
+ let option = document.createElement('option');
+ option.innerText = "one";
+ selectList.appendChild(option);
+ assert_equals(selectList.value, "one");
+ assert_equals(selectList.selectedOption, option);
+
+ let newOption = document.createElement('option');
+ newOption.innerText = 'two';
+ selectList.appendChild(newOption);
+ selectList.value = "two";
+ assert_equals(selectList.value, "two");
+ assert_equals(selectList.selectedOption, newOption);
+
+ option.click();
+ assert_equals(selectList.value, "one");
+ assert_equals(selectList.selectedOption, option);
+}, "Test that value and selectedOption are correctly updated");
+
+test(() => {
+ const selectList = document.getElementById("selectList6");
+ let selectListOption1 = document.getElementById("selectList6-option1");
+
+ assert_equals(selectList.value, "two");
+ assert_equals(selectList.selectedOption, document.getElementById("selectList6-option2"));
+ assert_false(selectListOption1.selected);
+ selectListOption1.selected = true;
+ assert_equals(selectList.value, "one");
+ assert_equals(selectList.selectedOption, selectListOption1);
+
+ let newOption = document.createElement("option");
+ newOption.innerText = "four";
+ newOption.selected = true;
+ selectList.appendChild(newOption);
+ assert_equals(selectList.value, "four");
+ assert_equals(selectList.selectedOption, newOption);
+ assert_false(selectListOption1.selected);
+
+ selectList.value = "three";
+ assert_equals(selectList.selectedOption, document.getElementById("selectList6-option3"));
+ assert_false(newOption.selected);
+}, "Test that HTMLOption.selected updates selectlist.value and selectlist.selectedOption");
+
+test(() => {
+ const selectList = document.getElementById("selectList7");
+ let selectListOption1 = document.getElementById("selectList7-option1");
+
+ assert_equals(selectList.value, "test");
+ assert_equals(selectList.selectedOption, document.getElementById("selectList7-option2"));
+ assert_false(selectListOption1.selected);
+ selectListOption1.selected = true;
+ assert_equals(selectList.value, "one");
+ assert_equals(selectList.selectedOption, selectListOption1);
+
+ selectListOption1.value = "new test";
+ assert_equals(selectList.value, "new test");
+ assert_equals(selectList.selectedOption, selectListOption1);
+ selectListOption1.removeAttribute("value");
+ assert_equals(selectList.value, "one");
+ assert_equals(selectList.selectedOption, selectListOption1);
+ selectListOption1.value = "";
+ assert_equals(selectList.value, "");
+ assert_equals(selectList.selectedOption, selectListOption1);
+}, "Test that HTMLOption.value updates selectlist.value");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-writingmode-lr.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-writingmode-lr.tentative.html
new file mode 100644
index 0000000000..9973696ddd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-writingmode-lr.tentative.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/600">
+<link rel=mismatch href="selectlist-writingmode-tb-ref.html">
+
+<selectlist style="writing-mode: vertical-lr">
+ <option>hello</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-writingmode-rl.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-writingmode-rl.tentative.html
new file mode 100644
index 0000000000..dc74203e69
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-writingmode-rl.tentative.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/600">
+<link rel=mismatch href="selectlist-writingmode-tb-ref.html">
+
+<selectlist style="writing-mode: vertical-rl">
+ <option>hello</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-writingmode-tb-ref.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-writingmode-tb-ref.html
new file mode 100644
index 0000000000..db922f5f9f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/selectlist-writingmode-tb-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<selectlist style="writing-mode: horizontal-tb">
+ <option>hello</option>
+</selectlist>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/support/back.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/support/back.html
new file mode 100644
index 0000000000..9cc5a1d603
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/support/back.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<script>history.back()</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/support/fake-selectlist.js b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/support/fake-selectlist.js
new file mode 100644
index 0000000000..77b3f74921
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/support/fake-selectlist.js
@@ -0,0 +1,112 @@
+function replaceChildElement(newChild, oldChild) {
+ oldChild.parentElement.replaceChild(newChild, oldChild);
+}
+
+function createFakeSelectlist(selectedValueText, includeListbox) {
+ const selectlist = document.createElement('div');
+ selectlist.classList.add('fake-selectlist');
+ selectlist.innerHTML = `
+ <button class="fake-selectlist-internal-selectlist-button">
+ <div class="fake-selectlist-selected-value"></div>
+ <div class="fake-selectlist-internal-selectlist-button-icon"></div>
+ </button>
+ `;
+ if (includeListbox) {
+ const listbox = document.createElement('div');
+ listbox.classList.add('fake-selectlist-listbox');
+ listbox.anchorElement = selectlist;
+ selectlist.appendChild(listbox);
+ }
+ selectlist.appendChild(createFakeSelectlistStyles());
+
+ const selectedvalue = selectlist.querySelector('.fake-selectlist-selected-value');
+ if (selectedValueText) {
+ selectedvalue.textContent = selectedValueText;
+ }
+
+ return selectlist;
+}
+
+function createFakeSelectlistStyles() {
+ const style = document.createElement('style');
+ style.textContent = `
+ .fake-selectlist {
+ display: inline-flex;
+ font-family: sans-serif;
+ font-size: 0.875em;
+ user-select: none;
+ }
+
+ .fake-selectlist-internal-selectlist-button {
+ color: fieldtext;
+ background-color: field;
+ appearance: none;
+ cursor: default;
+ font-size: inherit;
+ text-align: inherit;
+ display: inline-flex;
+ flex-grow: 1;
+ flex-shrink: 1;
+ align-items: center;
+ overflow-x: hidden;
+ overflow-y: hidden;
+ padding: 0.25em;
+ border-width: 1px;
+ border-style: solid;
+ border-color: buttonborder;
+ border-image: initial;
+ border-radius: 0.25em;
+ }
+
+ .fake-selectlist-selected-value {
+ color: FieldText;
+ flex-grow:1;
+ }
+
+ .fake-selectlist-internal-selectlist-button-icon {
+ background-image: url(support/selectlist_button_icon.svg);
+ background-origin: content-box;
+ background-repeat: no-repeat;
+ background-size: contain;
+ height: 1.0em;
+ margin-inline-start: 4px;
+ opacity: 1;
+ outline: none;
+ padding-bottom: 2px;
+ padding-inline-start: 3px;
+ padding-inline-end: 3px;
+ padding-top: 2px;
+ width: 1.2em;
+ }
+
+ .fake-selectlist-listbox {
+ font-family: sans-serif;
+ box-shadow: rgba(0, 0, 0, 0.13) 0px 12.8px 28.8px, rgba(0, 0, 0, 0.11) 0px 0px 9.2px;
+ box-sizing: border-box;
+ background-color: canvas;
+ min-inline-size: anchor-size(self-inline);
+ min-block-size: 1lh;
+ position: fixed;
+ width: fit-content;
+ height: fit-content;
+ color: canvastext;
+ overflow: auto;
+ border-width: initial;
+ border-style: solid;
+ border-color: initial;
+ border-image: initial;
+ border-radius: 0.25em;
+ padding: 0.25em;
+ margin: 0px;
+ inset: auto;
+
+ top: anchor(bottom);
+ }
+
+ .fake-selectlist option {
+ font-size: 0.875em;
+ padding: 0.25em;
+ }
+ `;
+ return style;
+}
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/support/selectlist_button_icon.svg b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/support/selectlist_button_icon.svg
new file mode 100644
index 0000000000..1a6c0193e8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/support/selectlist_button_icon.svg
@@ -0,0 +1,3 @@
+<svg width="20" height="14" viewBox="0 0 20 16" fill="none" xmlns="http://www.w3.org/2000/svg">\
+ <path d="M4 6 L10 12 L 16 6" stroke="WindowText" stroke-width="3" stroke-linejoin="round"/>\
+</svg> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/tab-closes-listbox.tentative.html b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/tab-closes-listbox.tentative.html
new file mode 100644
index 0000000000..1706ed34cc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-selectlist-element/tab-closes-listbox.tentative.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/openui/open-ui/issues/599">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1359089">
+<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>
+
+<selectlist id=defaultlistbox>
+ <option>one</option>
+ <option>two</option>
+</selectlist>
+
+<selectlist id=customlistbox>
+ <listbox>
+ <option>one</option>
+ <option>two</option>
+ </listbox>
+</selectlist>
+
+<script>
+const tabKey = '\uE004';
+
+document.querySelectorAll('selectlist').forEach(selectlist => {
+ promise_test(async () => {
+ selectlist.focus();
+ await test_driver.send_keys(selectlist, ' ');
+ assert_true(selectlist.open, 'Listbox should be open after pressing space.');
+
+ selectlist.addEventListener('keydown', event => {
+ if (event.key === 'Tab') {
+ event.preventDefault();
+ }
+ }, {once: true});
+ await test_driver.send_keys(document.activeElement, tabKey);
+ assert_true(selectlist.open, 'Listbox should stay open when the tab keydown is preventDefaulted.');
+
+ await test_driver.send_keys(document.activeElement, tabKey);
+ assert_false(selectlist.open, 'Listbox should close after pressing the tab key.');
+ }, `${selectlist.id}: Pressing tab should close the listbox.`);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/change-to-empty-value.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/change-to-empty-value.html
new file mode 100644
index 0000000000..b3aa7b416d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/change-to-empty-value.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Change event when clearing a textarea</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=1881457">
+<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>
+<textarea>abc</textarea>
+<script>
+promise_test(async function() {
+ let textarea = document.querySelector("textarea");
+ let changeFired = false;
+ textarea.addEventListener("change", () => {
+ changeFired = true;
+ }, { once: true });
+
+ textarea.focus();
+ assert_equals(document.activeElement, textarea, "Should focus textarea");
+ assert_false(changeFired, "Shouldn't have fired change event after focus");
+ textarea.select();
+ const kBackspaceKey = "\uE003";
+ await test_driver.send_keys(textarea, kBackspaceKey)
+ assert_false(changeFired, "Shouldn't have fired change event after select");
+ textarea.blur();
+ assert_true(changeFired, "Should've have fired change event after blur");
+ assert_equals(textarea.value, "", "Should've have cleared the value");
+});
+</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-splittext-with-range-crash.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-splittext-with-range-crash.html
new file mode 100644
index 0000000000..9d66a4ae05
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-splittext-with-range-crash.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<script>
+addEventListener("load", () => {
+ const textarea = document.querySelector("textarea");
+ const ul = document.createElement('ul');
+
+ const textNodeInTextarea = document.createTextNode("");
+ textarea.appendChild(textNodeInTextarea);
+ document.documentElement.getBoundingClientRect();
+
+ textarea.appendChild(ul);
+ const range = document.createRange();
+ range.selectNode(ul);
+
+ textNodeInTextarea.data = "ab";
+ textNodeInTextarea.splitText(1);
+});
+</script>
+</head>
+<body><textarea></textarea></body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-splittext-with-range-simple-crash.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-splittext-with-range-simple-crash.html
new file mode 100644
index 0000000000..d3c1e7ce94
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-splittext-with-range-simple-crash.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ const textarea = document.querySelector("textarea");
+ getSelection().selectAllChildren(textarea);
+ textarea.firstChild.splitText(0);
+});
+</script>
+</head>
+<body><textarea>abcd</textarea></body>
+</html>
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/textarea-validity-valueMissing-inside-datalist.html b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-validity-valueMissing-inside-datalist.html
new file mode 100644
index 0000000000..f0ce0028ee
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-textarea-element/textarea-validity-valueMissing-inside-datalist.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>textarea element with "required" attribute and empty value is considered "suffering from being missing" even if inside datalist element</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:suffering-from-being-missing">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<datalist>
+ <textarea required></textarea>
+</datalist>
+
+<script>
+test(() => {
+ const textarea = document.querySelector("textarea");
+
+ assert_true(textarea.validity.valueMissing);
+ assert_false(textarea.validity.valid);
+});
+</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.");
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/META.yml b/testing/web-platform/tests/html/semantics/grouping-content/META.yml
new file mode 100644
index 0000000000..0f3cf59653
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/META.yml
@@ -0,0 +1,2 @@
+suggested_reviewers:
+ - domenic
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-dd-element/grouping-dd.html b/testing/web-platform/tests/html/semantics/grouping-content/the-dd-element/grouping-dd.html
new file mode 100644
index 0000000000..022e555bd2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-dd-element/grouping-dd.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>the dd element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-dd-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ "use strict";
+
+ // check that prototype matches spec's DOM interface
+ test(function () {
+ var testElement = document.createElement("dd");
+ assert_equals(Object.getPrototypeOf(testElement), HTMLElement.prototype, "HTMLElement.prototype should be used for dd");
+ }, "The prototype for dd is HTMLElement.prototype");
+
+ </script>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test validates the dd element.</p>
+
+ <div id="log"></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-div-element/grouping-div.html b/testing/web-platform/tests/html/semantics/grouping-content/the-div-element/grouping-div.html
new file mode 100644
index 0000000000..ffde6eb53f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-div-element/grouping-div.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>The div element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-div-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ "use strict";
+
+ // check that prototype matches spec's DOM interface
+ test(function () {
+ var testElement = document.createElement("div");
+ assert_equals(Object.getPrototypeOf(testElement), HTMLDivElement.prototype, "HTMLDivElement.prototype should be used for div");
+ }, "The prototype for div is HTMLDivElement.prototype");
+
+ </script>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test validates the div element.</p>
+
+ <div id="log"></div>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-dl-element/grouping-dl.html b/testing/web-platform/tests/html/semantics/grouping-content/the-dl-element/grouping-dl.html
new file mode 100644
index 0000000000..2394d6a929
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-dl-element/grouping-dl.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>the dl element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-dl-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ "use strict";
+
+ // check that prototype matches spec's DOM interface
+ test(function () {
+ var testElement = document.createElement("dl");
+ assert_equals(Object.getPrototypeOf(testElement), HTMLDListElement.prototype, "HTMLDListElement.prototype should be used for dl");
+ }, "The prototype for dl is HTMLDListElement.prototype");
+
+ // Not checking: effects of markup on defining groups and the name-pair values within those groups
+
+ </script>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test validates the dl element.</p>
+
+ <div id="log"></div>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-dt-element/grouping-dt.html b/testing/web-platform/tests/html/semantics/grouping-content/the-dt-element/grouping-dt.html
new file mode 100644
index 0000000000..1dbb4384dd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-dt-element/grouping-dt.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>the dl element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-dt-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ "use strict";
+
+ // check that prototype matches spec's DOM interface
+ test(function () {
+ var testElement = document.createElement("dt");
+ assert_equals(Object.getPrototypeOf(testElement), HTMLElement.prototype, "HTMLElement.prototype should be used for dt");
+ }, "The prototype for dt is HTMLElement.prototype");
+
+ </script>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test validates the dt element.</p>
+
+ <div id="log"></div>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-figcaption-element/grouping-figcaption.html b/testing/web-platform/tests/html/semantics/grouping-content/the-figcaption-element/grouping-figcaption.html
new file mode 100644
index 0000000000..68e7a516b4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-figcaption-element/grouping-figcaption.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>the figcaption element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-figcaption-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ "use strict";
+
+ // check that prototype matches spec's DOM interface
+ test(function () {
+ var testElement = document.createElement("figcaption");
+ assert_equals(Object.getPrototypeOf(testElement), HTMLElement.prototype, "HTMLElement.prototype should be used for figcaption");
+ }, "The prototype for figcaption is HTMLElement.prototype");
+
+ </script>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test validates the figcaption element.</p>
+
+ <div id="log"></div>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-figure-element/grouping-figure.html b/testing/web-platform/tests/html/semantics/grouping-content/the-figure-element/grouping-figure.html
new file mode 100644
index 0000000000..31c156ce38
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-figure-element/grouping-figure.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>the figure element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-figure-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ "use strict";
+
+ // check that prototype matches spec's DOM interface
+ test(function () {
+ var testElement = document.createElement("figure");
+ assert_equals(Object.getPrototypeOf(testElement), HTMLElement.prototype, "HTMLElement.prototype should be used for figure");
+ }, "The prototype for figure is HTMLElement.prototype");
+
+ // Not checking: Sectioning root
+
+ </script>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test validates the figure element.</p>
+
+ <div id="log"></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-hr-element/grouping-hr.html b/testing/web-platform/tests/html/semantics/grouping-content/the-hr-element/grouping-hr.html
new file mode 100644
index 0000000000..eeadd97d44
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-hr-element/grouping-hr.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>the hr element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-hr-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ "use strict";
+
+ // check that prototype matches spec's DOM interface
+ test(function () {
+ var testElement = document.createElement("hr");
+ assert_equals(Object.getPrototypeOf(testElement), HTMLHRElement.prototype, "HTMLHRElement.prototype should be used for hr");
+ }, "The prototype for hr is HTMLHRElement.prototype");
+
+ // Not checking: "The hr element does not affect the document's outline."
+
+ </script>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test validates the hr element.</p>
+
+ <div id="log"></div>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-novalue-manual.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-novalue-manual.html
new file mode 100644
index 0000000000..346ed56629
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-novalue-manual.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Body Element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-body-element">
+ <meta name="flags" content="interact" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <h1>Validation of li characteristic requiring manual testing</h1>
+
+ <p>The spec states: "If the parent element is an ol element, then the li element has an ordinal value."</p>
+ <p>This manual test is needed to verify that NON-ol element parents do NOT result in an ordinal value.</p>
+ <p>It needs to be manual because the ordinal value assigned to each list element by the user agent is NOT available programmatically. Values which are set either via markup or IDL are available programmatically, but not the calculated values for all the other list items.</p>
+ <p>And, as we cannot be sure how a mistakenly assigned value would be rendered, this test cannot be a reftest.</p>
+ <p>So, please use the buttons to answer the following questions:</p>
+
+ <table>
+ <thead>
+ <tr>
+ <th>HTML Markup</th>
+ <th>Do you see any sort of sequencing information?</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <menu>
+ <li>Menu Item</li>
+ <li>Menu Item</li>
+ </menu>
+ </td>
+ <td>
+ <input type="button" id="btnMenuYes" value="Yes" />
+ <input type="button" id="btnMenuNo" value="No" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <menu type="toolbar">
+ <li>
+ <menu label="ToolbarLabel">
+ <li><a>Toolbar Menu Item</a></li>
+ <li><a>Toolbar Menu Item</a></li>
+ </menu>
+ </li>
+ <li>
+ <menu label="ToolbarLabel">
+ <li><a>Toolbar Menu Item</a></li>
+ <li><a>Toolbar Menu Item</a></li>
+ </menu>
+ </li>
+ </menu>
+ </td>
+ <td>
+ <input type="button" id="btnToolbarYes" value="Yes" />
+ <input type="button" id="btnToolbarNo" value="No" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <ul>
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ul>
+ </td>
+ <td>
+ <input type="button" id="btnULYes" value="Yes" />
+ <input type="button" id="btnULNo" value="No" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <div id="log"></div>
+
+ <script>
+ "use strict";
+
+ var testMenu = {}, testToolbar = {}, testUL = {};
+
+ // need to be able to wait for user to push button
+ setup(function () {
+ btnMenuYes.disabled = false;
+ btnMenuNo.disabled = false;
+ btnToolbarYes.disabled = false;
+ btnToolbarNo.disabled = false;
+ btnULYes.disabled = false;
+ btnULNo.disabled = false;
+ },
+ { explicit_timeout: true }
+ );
+
+ // register async tests
+ testMenu = async_test("Check that menu element does not result in values for list items.");
+ testToolbar = async_test("Check that toolbar type menu element does not result in values for list items.");
+ testUL = async_test("Check that unordered list element does not result in values for list items.");
+
+ // run async tests after buttons are clicked - MENU test
+ document.getElementById("btnMenuNo").onclick = testMenu.step_func(function (event) {
+ assert_true(true, "No sequencing applied to list elements inside menu.");
+ testMenu.done();
+ btnMenuYes.disabled = true;
+ btnMenuNo.disabled = true;
+ });
+ document.getElementById("btnMenuYes").onclick = testMenu.step_func(function (event) {
+ assert_true(false, "No sequencing applied to list elements inside menu.");
+ testMenu.done();
+ btnMenuYes.disabled = true;
+ btnMenuNo.disabled = true;
+ });
+
+ // run async tests after buttons are clicked -TOOLBAR test
+ document.getElementById("btnToolbarNo").onclick = testToolbar.step_func(function (event) {
+ assert_true(true, "No sequencing applied to list elements inside toolbar menu.");
+ testToolbar.done();
+ btnToolbarYes.disabled = true;
+ btnToolbarNo.disabled = true;
+ });
+ document.getElementById("btnToolbarYes").onclick = testToolbar.step_func(function (event) {
+ assert_true(false, "No sequencing applied to list elements inside toolbar menu.");
+ testToolbar.done();
+ btnToolbarYes.disabled = true;
+ btnToolbarNo.disabled = true;
+ });
+
+ // run async tests after buttons are clicked - UL test
+ document.getElementById("btnULNo").onclick = testUL.step_func(function (event) {
+ assert_true(true, "No sequencing applied to list elements inside UL.");
+ testUL.done();
+ btnULYes.disabled = true;
+ btnULNo.disabled = true;
+ });
+ document.getElementById("btnULYes").onclick = testUL.step_func(function (event) {
+ assert_true(false, "No sequencing applied to list elements inside UL.");
+ testUL.done();
+ btnULYes.disabled = true;
+ btnULNo.disabled = true;
+ });
+
+
+ </script>
+
+</body>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-001-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-001-ref.html
new file mode 100644
index 0000000000..23b6cdabe8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-001-ref.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>li element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-li-element">
+ <meta name="assert" content="The value attribute has no effect when applied to a li element whose parent is a non-ol element." />
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test continues to validate the li element.</p>
+
+ <p>This reftest verifies that the value attribute has no effect when applied to a list item NOT having an ol parent and not marked as display: list-item.</p>
+ <p>A reftest is necessary because the values of li elements as calculated by the user agent are NOT available programatically. Only explicitly-set values are then available programatically.</p>
+ <p>This reftest passes if you see NO sequencing information on any of the items below.</p>
+
+ <p>Unordered List</p>
+ <ul>
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ul>
+
+ <menu>
+ <li>Menu Item</li>
+ <li>Menu Item</li>
+ </menu>
+
+ <menu type="toolbar">
+ <li>
+ <menu label="ToolbarLabel">
+ <li><a>Toolbar Menu Item</a></li>
+ <li><a>Toolbar Menu Item</a></li>
+ </menu>
+ </li>
+ <li>
+ <menu label="ToolbarLabel">
+ <li><a>Toolbar Menu Item</a></li>
+ <li><a>Toolbar Menu Item</a></li>
+ </menu>
+ </li>
+ </menu>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-001.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-001.html
new file mode 100644
index 0000000000..86200edadf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-001.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>li element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-li-element">
+ <link rel="match" href="grouping-li-reftest-001-ref.html" />
+ <meta name="assert" content="The value attribute has no effect when applied to a li element whose parent is a non-ol element." />
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test continues to validate the li element.</p>
+
+ <p>This reftest verifies that the value attribute has no effect when applied to a list item NOT having an ol parent and not marked as display: list-item.</p>
+ <p>A reftest is necessary because the values of li elements as calculated by the user agent are NOT available programatically. Only explicitly-set values are then available programatically.</p>
+ <p>This reftest passes if you see NO sequencing information on any of the items below.</p>
+
+ <p>Unordered List</p>
+ <ul>
+ <li value="2">list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ul>
+
+ <menu>
+ <li>Menu Item</li>
+ <li value="3">Menu Item</li>
+ </menu>
+
+ <menu type="toolbar">
+ <li value="4">
+ <menu label="ToolbarLabel">
+ <li value="5"><a>Toolbar Menu Item</a></li>
+ <li><a>Toolbar Menu Item</a></li>
+ </menu>
+ </li>
+ <li value="6">
+ <menu label="ToolbarLabel">
+ <li value="10"><a>Toolbar Menu Item</a></li>
+ <li><a>Toolbar Menu Item</a></li>
+ </menu>
+ </li>
+ </menu>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-002-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-002-ref.html
new file mode 100644
index 0000000000..1e453e1afb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-002-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>li element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-li-element">
+ <meta name="assert" content="li elements with ol parents have ordinal values." />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span span p {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ </style>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test continues to validate the li element.</p>
+
+ <p>This reftest verifies that the value attribute has an effect when applied to a list item with an ol parent.</p>
+ <p>A reftest is necessary because the values of li elements as calculated by the user agent are NOT available programatically. Only explicitly-set values are then available programatically.</p>
+ <p>This reftest passes if you see the numbers 1. 2. 3. below the words "Ordered List"</p>
+
+ <span>
+ <p>Ordered List</p>
+ <span>
+ <p>1.</p>
+ <p>2.</p>
+ <p>3.</p>
+ </span>
+ </span>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-002.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-002.html
new file mode 100644
index 0000000000..3c0a464255
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-002.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>li element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-li-element">
+ <link rel="match" href="grouping-li-reftest-002-ref.html" />
+ <meta name="assert" content="li elements with ol parents have ordinal values." />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span li {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ span ol {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace;
+ list-style-position: inside; list-style-type: decimal; }
+ </style>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test continues to validate the li element.</p>
+
+ <p>This reftest verifies that the value attribute has an effect when applied to a list item with an ol parent.</p>
+ <p>A reftest is necessary because the values of li elements as calculated by the user agent are NOT available programatically. Only explicitly-set values are then available programatically.</p>
+ <p>This reftest passes if you see the numbers 1. 2. 3. below the words "Ordered List"</p>
+
+ <span>
+ <p>Ordered List</p>
+ <ol>
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+ </span>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-003-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-003-ref.html
new file mode 100644
index 0000000000..dbe4c05bad
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-003-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <meta charset="utf-8">
+ <title>HTML LI element: dynamic update test for LI @value</title>
+ <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.org">
+</head>
+<body onload="document.getElementById('item').removeAttribute('value');">
+<ol><li id="item" value="3"></li></ol>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-003.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-003.html
new file mode 100644
index 0000000000..bad3819604
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-003.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <meta charset="utf-8">
+ <title>HTML LI element: dynamic update test for LI @value</title>
+ <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/grouping-content.html#the-li-element">
+ <link rel="help" href="https://drafts.csswg.org/css-lists/#propdef-counter-set">
+ <link rel="match" href="grouping-li-reftest-003-ref.html">
+</head>
+<body onload="document.getElementById('item').removeAttribute('value');">
+<noscript>Test not run - javascript required.</noscript>
+<ol><li id="item" value="3"></li></ol>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-004-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-004-ref.html
new file mode 100644
index 0000000000..2d9b19e043
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-004-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <meta charset="utf-8">
+ <title>HTML LI element: implied scope</title>
+ <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.org">
+ <style>
+ body { margin-left: 40px }
+ li { list-style-type: decimal }
+ </style>
+</head>
+<body>
+<li start="1"></li>
+<li start="2"></li>
+<li start="3"></li>
+<li start="4"></li>
+<div><li></li></div>
+<div><div><li></li></div></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-004.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-004.html
new file mode 100644
index 0000000000..2a7647fc2a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-004.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <meta charset="utf-8">
+ <title>HTML LI element: implied scope</title>
+ <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/grouping-content.html#the-li-element">
+ <link rel="help" href="https://drafts.csswg.org/css-lists/#propdef-counter-reset">
+ <link rel="match" href="grouping-li-reftest-004-ref.html">
+ <style>
+ body { margin-left: 40px }
+ li { list-style-type: decimal }
+ </style>
+</head>
+<body>
+<li></li>
+<li></li>
+<li></li>
+<li></li>
+<div><li></li></div>
+<div><div><li></li></div></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-005-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-005-ref.html
new file mode 100644
index 0000000000..6ec02e5433
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-005-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <meta charset="utf-8">
+ <title>HTML LI element: explicit scope</title>
+ <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.org">
+</head>
+<body>
+<ol>
+<li start="1"></li>
+<li start="2"></li>
+<li start="3"></li>
+<li start="4"></li>
+<div><li start="5"></li></div>
+<div><li start="6"></li></div>
+<div><div><li start="7"></li></div></div>
+</ol>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-005.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-005.html
new file mode 100644
index 0000000000..2fa4e5dbe3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-005.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <meta charset="utf-8">
+ <title>HTML LI element: explicit scope</title>
+ <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/grouping-content.html#the-li-element">
+ <link rel="help" href="https://drafts.csswg.org/css-lists/#propdef-counter-reset">
+ <link rel="match" href="grouping-li-reftest-005-ref.html">
+</head>
+<body>
+<ol>
+<li></li>
+<li></li>
+<li></li>
+<li></li>
+<div><li></li></div>
+<div><li></li></div>
+<div><div><li></li></div></div>
+</ol>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-006.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-006.html
new file mode 100644
index 0000000000..d6a260bbc0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-006.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <meta charset="utf-8">
+ <title>HTML LI element: implied scope</title>
+ <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/grouping-content.html#the-li-element">
+ <link rel="help" href="https://drafts.csswg.org/css-lists/#propdef-counter-reset">
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1745506">
+ <link rel="match" href="grouping-li-reftest-004-ref.html">
+ <style>
+ body { margin-left: 40px }
+ ol { margin: 0; padding: 0; }
+ li { list-style-type: decimal }
+ </style>
+</head>
+<body>
+<div><ol start=99></ol></div> <!-- this scope shouldn't affect the LIs below -->
+<li></li>
+<li></li>
+<li></li>
+<li></li>
+<div><li></li></div>
+<div><div><li></li></div></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-007.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-007.html
new file mode 100644
index 0000000000..8eda7057da
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-007.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <meta charset="utf-8">
+ <title>HTML LI element: implied scope</title>
+ <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/grouping-content.html#the-li-element">
+ <link rel="help" href="https://drafts.csswg.org/css-lists/#propdef-counter-reset">
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1745506">
+ <link rel="match" href="grouping-li-reftest-004-ref.html">
+ <style>
+ body { margin-left: 40px }
+ ol { margin: 0; padding: 0 }
+ li { list-style-type: decimal }
+ </style>
+</head>
+<body>
+<div><ol start=99></ol></div> <!-- this scope shouldn't affect the LIs below -->
+<ol style="counter-reset: item"> <!-- this counter shouldn't affect the LIs below -->
+<li></li>
+<li></li>
+<li></li>
+<li></li>
+<div><li></li></div>
+<div><div><li></li></div></div>
+</ol>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-display-list-item-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-display-list-item-ref.html
new file mode 100644
index 0000000000..fb142f84b8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-display-list-item-ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>display: list-item on non-&lt;li> elements</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<style>
+ .list-item {
+ display: list-item;
+ list-style-type: decimal;
+ }
+
+ .list-item[hidden] {
+ display: none;
+ }
+</style>
+
+<p>This test matches if both lists display similar to the following:</p>
+
+<pre>1. A
+2. B
+ D
+3. E</pre>
+
+<hr>
+
+<ol>
+ <li value="1">A</li>
+ <li value="2">B</li>
+ <span>D</span>
+ <li value="3">E</li>
+</ol>
+
+<ol>
+ <li value="1">A</li>
+ <li value="2">B</li>
+ <span>D</span>
+ <li value="3">E</li>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-display-list-item.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-display-list-item.html
new file mode 100644
index 0000000000..ce63cf7c7c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-display-list-item.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>display: list-item on non-&lt;li> elements</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<link rel="match" href="grouping-li-reftest-display-list-item-ref.html">
+
+<style>
+ .list-item {
+ display: list-item;
+ list-style-type: decimal;
+ }
+
+ .list-item[hidden] {
+ display: none;
+ }
+</style>
+
+<p>This test matches if both lists display similar to the following:</p>
+
+<pre>1. A
+2. B
+ D
+3. E</pre>
+
+<hr>
+
+<ul>
+ <span class="list-item">A</span>
+ <span class="list-item">B</span>
+ <span class="list-item" hidden>C</span>
+ <span>D</span>
+ <span class="list-item">E</span>
+</ul>
+
+<ol>
+ <div class="list-item">A</div>
+ <div class="list-item">B</div>
+ <div class="list-item" hidden>C</div>
+ <div>D</div>
+ <div class="list-item">E</div>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu-ref.html
new file mode 100644
index 0000000000..2b1ea76656
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu-ref.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>list owner is calculated to be narest ancestor menu if it exists</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. C
+4. D
+5. E
+ 1. F
+ 2. G
+6. H
+ 1. I
+ 2. J
+ 1. K
+ 2. L</pre>
+
+<hr>
+
+<ol>
+ <li value="1">A</li>
+ <li value="2">B</li>
+ <li value="3">C</li>
+ <li value="4">D</li>
+ <li value="5">E</li>
+ <ol>
+ <li value="1">F</li>
+ <li value="2">G</li>
+ </ol>
+ <li value="6">H</li>
+ <ol>
+ <li value="1">I</li>
+ <li value="2">
+ J
+ <ol>
+ <li value="1">K</li>
+ <li value="2">L</li>
+ </ol>
+ </li>
+ </ol>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu.html
new file mode 100644
index 0000000000..86afa5d206
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>list owner is calculated to be narest ancestor menu if it exists</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<link rel="match" href="grouping-li-reftest-list-owner-menu-ref.html">
+
+<style>
+ li {
+ list-style-type: decimal;
+ }
+</style>
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. C
+4. D
+5. E
+ 1. F
+ 2. G
+6. H
+ 1. I
+ 2. J
+ 1. K
+ 2. L</pre>
+
+<hr>
+
+<menu>
+ <li>A</li>
+ <li>B</li>
+ <div>
+ <li>C</li>
+ <span>
+ <li>D</li>
+ <li>E</li>
+ </span>
+ <menu>
+ <li>F</li>
+ <li>G</li>
+ </menu>
+ </div>
+ <li>H</li>
+ <menu>
+ <li>I</li>
+ <li>
+ J
+ <menu>
+ <li>K</li>
+ <li>L</li>
+ </menu>
+ </li>
+ </menu>
+</menu>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-mixed-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-mixed-ref.html
new file mode 100644
index 0000000000..b72768aefc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-mixed-ref.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>list owner is calculated to be nearest ancestor ul or ul (but not dir) if it exists</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<style>
+ li {
+ list-style-type: decimal;
+ }
+</style>
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. C
+4. D
+5. E
+ 1. F
+ 2. G
+6. H
+ 1. I
+ 2. J
+ 3. K
+ 4. L</pre>
+
+<hr>
+
+<ol>
+ <li value="1">A</li>
+ <li value="2">B</li>
+ <li value="3">C</li>
+ <li value="4">D</li>
+ <li value="5">E</li>
+ <ol>
+ <li value="1">F</li>
+ <li value="2">G</li>
+ </ol>
+ <li value="6">H</li>
+ <ol>
+ <li value="1">I</li>
+ <li value="2">
+ J
+ <dir>
+ <li value="3">K</li>
+ <li value="4">L</li>
+ </dir>
+ </li>
+ </ol>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-mixed.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-mixed.html
new file mode 100644
index 0000000000..82e39f995e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-mixed.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>list owner is calculated to be nearest ancestor ul or ul (but not dir) if it exists</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<link rel="match" href="grouping-li-reftest-list-owner-mixed-ref.html">
+
+<style>
+ li {
+ list-style-type: decimal;
+ }
+
+ .list-item {
+ display: list-item;
+ list-style-type: decimal;
+ }
+
+</style>
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. C
+4. D
+5. E
+ 1. F
+ 2. G
+6. H
+ 1. I
+ 2. J
+ 3. K
+ 4. L</pre>
+
+<hr>
+
+<ul>
+ <li>A</li>
+ <li>B</li>
+ <div>
+ <li>C</li>
+ <span>
+ <li>D</li>
+ <li>E</li>
+ </span>
+ <ol>
+ <li>F</li>
+ <span class="list-item">G</span>
+ </ol>
+ </div>
+ <li>H</li>
+ <ol>
+ <li>I</li>
+ <li>
+ J
+ <dir>
+ <li>K</li>
+ <li>L</li>
+ </dir>
+ </li>
+ </ol>
+</ul>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-not-dir-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-not-dir-ref.html
new file mode 100644
index 0000000000..fad00fa3bd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-not-dir-ref.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The dir element is not treated specially when calculating list owners</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<style>
+ li {
+ list-style-type: decimal;
+ }
+</style>
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. C
+4. D
+5. E
+ 6. F
+ 7. G
+8. H
+ 9. I
+ 10. J
+ 11. K
+ 12. L</pre>
+
+<hr>
+
+<ol>
+ <li value="1">A</li>
+ <li value="2">B</li>
+ <li value="3">C</li>
+ <li value="4">D</li>
+ <li value="5">E</li>
+ <dir>
+ <li value="6">F</li>
+ <li value="7">G</li>
+ </dir>
+ <li value="8">H</li>
+ <dir>
+ <li value="9">I</li>
+ <li value="10">
+ J
+ <dir>
+ <li value="11">K</li>
+ <li value="12">L</li>
+ </dir>
+ </li>
+ </dir>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-not-dir.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-not-dir.html
new file mode 100644
index 0000000000..747d90738b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-not-dir.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The dir element is not treated specially when calculating list owners</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<link rel="match" href="grouping-li-reftest-list-owner-not-dir-ref.html">
+
+<style>
+ li {
+ list-style-type: decimal;
+ }
+</style>
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. C
+4. D
+5. E
+ 6. F
+ 7. G
+8. H
+ 9. I
+ 10. J
+ 11. K
+ 12. L</pre>
+
+<hr>
+
+<ol>
+ <li>A</li>
+ <li>B</li>
+ <div>
+ <li>C</li>
+ <span>
+ <li>D</li>
+ <li>E</li>
+ </span>
+ <dir>
+ <li>F</li>
+ <li>G</li>
+ </dir>
+ </div>
+ <li>H</li>
+ <dir>
+ <li>I</li>
+ <li>
+ J
+ <dir>
+ <li>K</li>
+ <li>L</li>
+ </dir>
+ </li>
+ </dir>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ol-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ol-ref.html
new file mode 100644
index 0000000000..96cc9c3600
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ol-ref.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>list owner is calculated to be narest ancestor ol if it exists</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. C
+4. D
+5. E
+ 1. F
+ 2. G
+6. H
+ 1. I
+ 2. J
+ 1. K
+ 2. L</pre>
+
+<hr>
+
+<ol>
+ <li value="1">A</li>
+ <li value="2">B</li>
+ <li value="3">C</li>
+ <li value="4">D</li>
+ <li value="5">E</li>
+ <ol>
+ <li value="1">F</li>
+ <li value="2">G</li>
+ </ol>
+ <li value="6">H</li>
+ <ol>
+ <li value="1">I</li>
+ <li value="2">
+ J
+ <ol>
+ <li value="1">K</li>
+ <li value="2">L</li>
+ </ol>
+ </li>
+ </ol>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ol.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ol.html
new file mode 100644
index 0000000000..fcb93cfbb5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ol.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>list owner is calculated to be narest ancestor ol if it exists</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<link rel="match" href="grouping-li-reftest-list-owner-ol-ref.html">
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. C
+4. D
+5. E
+ 1. F
+ 2. G
+6. H
+ 1. I
+ 2. J
+ 1. K
+ 2. L</pre>
+
+<hr>
+
+<ol>
+ <li>A</li>
+ <li>B</li>
+ <div>
+ <li>C</li>
+ <span>
+ <li>D</li>
+ <li>E</li>
+ </span>
+ <ol>
+ <li>F</li>
+ <li>G</li>
+ </ol>
+ </div>
+ <li>H</li>
+ <ol>
+ <li>I</li>
+ <li>
+ J
+ <ol>
+ <li>K</li>
+ <li>L</li>
+ </ol>
+ </li>
+ </ol>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-parent-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-parent-ref.html
new file mode 100644
index 0000000000..03a0570ba4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-parent-ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>list owner is calculated to be the parent if there is no ancestor ul/ol/menu</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<style>
+ li {
+ list-style-type: decimal;
+ list-style-position: inside;
+ }
+
+ ol {
+ padding: 50px;
+ margin: 0;
+ }
+</style>
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+1. C
+1. D
+ 1. E
+3. F</pre>
+
+<hr>
+
+<ol>
+ <li value="1">A</li>
+ <li value="2">B</li>
+ <li value="1">C</li>
+ <li value="1">D</li>
+ <blockquote>
+ <li value="1">E</li>
+ </blockquote>
+ <li value="3">F</li>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-parent.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-parent.html
new file mode 100644
index 0000000000..0345add996
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-parent.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>list owner is calculated to be the parent if there is no ancestor ul/ol/menu</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<link rel="match" href="grouping-li-reftest-list-owner-parent-ref.html">
+
+<style>
+ li {
+ list-style-type: decimal;
+ list-style-position: inside;
+ }
+
+ .container {
+ padding: 50px;
+ }
+</style>
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+1. C
+1. D
+ 1. E
+3. F</pre>
+
+<hr>
+
+<div class="container">
+ <li>A</li>
+ <li>B</li>
+ <div>
+ <li>C</li>
+ <span>
+ <li>D</li>
+ </span>
+ </div>
+ <blockquote>
+ <li>E</li>
+ </blockquote>
+ <li>F</li>
+</div>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-skip-no-boxes-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-skip-no-boxes-ref.html
new file mode 100644
index 0000000000..e758f52be9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-skip-no-boxes-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>list owner calculation skips elements that do not generate layout boxes</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. C
+4. D
+5. E
+6. F
+7. G
+8. H
+9. I
+10. J
+ 1. K
+ 2. L</pre>
+
+<hr>
+
+<ol>
+ <li value="1">A</li>
+ <li value="2">B</li>
+ <li value="3">C</li>
+ <li value="4">D</li>
+ <li value="5">E</li>
+ <li value="6">F</li>
+ <li value="7">G</li>
+ <li value="8">H</li>
+ <li value="9">I</li>
+ <li value="10">
+ J
+ <ol>
+ <li value="1">K</li>
+ <li value="2">L</li>
+ </ol>
+ </li>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-skip-no-boxes.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-skip-no-boxes.html
new file mode 100644
index 0000000000..defdcb7000
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-skip-no-boxes.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>list owner calculation skips elements that do not generate layout boxes</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<link rel="match" href="grouping-li-reftest-list-owner-skip-no-boxes-ref.html">
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. C
+4. D
+5. E
+6. F
+7. G
+8. H
+9. I
+10. J
+ 1. K
+ 2. L</pre>
+
+<hr>
+
+<ol>
+ <li>A</li>
+ <li>B</li>
+ <div>
+ <li>C</li>
+ <span>
+ <li>D</li>
+ <li>E</li>
+ </span>
+ <ol style="display: contents;">
+ <li>F</li>
+ <li>G</li>
+ </ol>
+ </div>
+ <li>H</li>
+ <ol style="display: contents;">
+ <li>I</li>
+ <li>
+ J
+ <ol>
+ <li>K</li>
+ <li>L</li>
+ </ol>
+ </li>
+ </ol>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ul-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ul-ref.html
new file mode 100644
index 0000000000..22ee9f437f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ul-ref.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>list owner is calculated to be nearest ancestor ul if it exists</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. C
+4. D
+5. E
+ 1. F
+ 2. G
+6. H
+ 1. I
+ 2. J
+ 1. K
+ 2. L</pre>
+
+<hr>
+
+<ol>
+ <li value="1">A</li>
+ <li value="2">B</li>
+ <li value="3">C</li>
+ <li value="4">D</li>
+ <li value="5">E</li>
+ <ol>
+ <li value="1">F</li>
+ <li value="2">G</li>
+ </ol>
+ <li value="6">H</li>
+ <ol>
+ <li value="1">I</li>
+ <li value="2">
+ J
+ <ol>
+ <li value="1">K</li>
+ <li value="2">L</li>
+ </ol>
+ </li>
+ </ol>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ul.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ul.html
new file mode 100644
index 0000000000..8f1c7e3766
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-ul.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>list owner is calculated to be nearest ancestor ul if it exists</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<link rel="match" href="grouping-li-reftest-list-owner-ul-ref.html">
+
+<style>
+ li {
+ list-style-type: decimal;
+ }
+</style>
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. C
+4. D
+5. E
+ 1. F
+ 2. G
+6. H
+ 1. I
+ 2. J
+ 1. K
+ 2. L</pre>
+
+<hr>
+
+<ul>
+ <li>A</li>
+ <li>B</li>
+ <div>
+ <li>C</li>
+ <span>
+ <li>D</li>
+ <li>E</li>
+ </span>
+ <ul>
+ <li>F</li>
+ <li>G</li>
+ </ul>
+ </div>
+ <li>H</li>
+ <ul>
+ <li>I</li>
+ <li>
+ J
+ <ul>
+ <li>K</li>
+ <li>L</li>
+ </ul>
+ </li>
+ </ul>
+</ul>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-not-being-rendered-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-not-being-rendered-ref.html
new file mode 100644
index 0000000000..9a018cfaf3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-not-being-rendered-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>List items that are not being rendered do not participate in numbering</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. D
+4. E
+5. G</pre>
+
+<hr>
+
+<ol>
+ <li value="1">A</li>
+ <li value="2">B</li>
+ <li value="3">D</li>
+ <li value="4">E</li>
+ <li value="5">G</li>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-not-being-rendered.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-not-being-rendered.html
new file mode 100644
index 0000000000..da476c4bff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-not-being-rendered.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>List items that are not being rendered do not participate in numbering</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+
+<link rel="match" href="grouping-li-reftest-not-being-rendered-ref.html">
+
+<p>This test matches if the list displays similar to the following</p>
+
+<pre>1. A
+2. B
+3. D
+4. E
+5. G</pre>
+
+<hr>
+
+<ol>
+ <li>A</li>
+ <li>B</li>
+ <li hidden>C</li>
+ <li>D</li>
+ <div>
+ <li>E</li>
+ <li hidden>F</li>
+ <li>G</li>
+ </div>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li.html b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li.html
new file mode 100644
index 0000000000..fa342b3e28
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-li-element/grouping-li.html
@@ -0,0 +1,193 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>li element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-li-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test validates the li element.</p>
+
+ <div id="log"></div>
+
+ <span>
+ <menu id="listmenu">
+ <li>Command</li>
+ <li value="3">Command</li>
+ </menu>
+
+ <menu type="toolbar" id="toolbarmenu">
+ <li>
+ <menu label="File">
+ <button type="button">New...</button>
+ <button type="button">Open...</button>
+ </menu>
+ </li>
+ <li value="10">
+ <menu label="Help">
+ <li value = "2"><a href="help.html">Help Me</a></li>
+ <li><a href="about.html">About</a></li>
+ </menu>
+ </li>
+ </menu>
+
+ <p>Unordered List</p>
+ <ul id="unordered">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ul>
+ </span>
+
+ <p>Ordered List</p>
+ <ol id="basic">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="start2">
+ <li value="2">list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="negative">
+ <li value="-10">list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="posFloatDown">
+ <li value="4.03">list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="negFloatDown">
+ <li value="-4.03">list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="posFloatUp">
+ <li value="4.9">list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="negFloatUp">
+ <li value="-4.9">list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="exponent">
+ <li value="7e2">list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="decimal">
+ <li value=".5">list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="letter">
+ <li value="A">list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <script>
+ "use strict";
+
+ // "The [value] attribute has no default value" so, per https://html.spec.whatwg.org/multipage/#reflect,
+ // the default when unspecified is 0
+ test(function() {
+ var testList = document.querySelectorAll("#unordered li, #basic li");
+ for (var i = 0; i < testList.length; i++) {
+ assert_equals(testList[i].value, 0, "Default (unspecified) value of value is 0.");
+ }
+ }, "Default (unspecified) value of value is 0.");
+
+ // "If the value attribute is present, user agents must parse it as an integer, in order to determine the attribute's value.
+ // If the attribute's value cannot be converted to a number, the attribute must be treated as if it was absent."
+ // Per https://html.spec.whatwg.org/multipage/#collect-a-sequence-of-characters,
+ // an integer is parsed by collecting as many digits as possible and then aborting at the first
+ // non-digit character after the first digit (otherwise, with no beginning digit, it's just an error)
+ // and: "The value IDL attribute must reflect the value of the value content attribute."
+
+ // start2's first element has value of 2
+ test(function() {
+ var testLI = document.getElementById("start2").children[0];
+ assert_equals(testLI.value, 2, "value of 2 -> value of 2");
+ }, ".value property reflects content attribute - and both parse value of '2' correctly.");
+
+ // negative's first element has value of -10
+ test(function() {
+ var testLI = document.getElementById("negative").children[0];
+ assert_equals(testLI.value, -10, "value of -10 -> value of -10");
+ }, "IDL and content attribute parse value of '-10' correctly.");
+
+ // posFloatDown's first element has value of 4.03 which should return 4
+ test(function() {
+ var testLI = document.getElementById("posFloatDown").children[0];
+ assert_equals(testLI.value, 4, "value of 4.03 -> 4");
+ }, "IDL and content attribute parse value of '4.03' correctly.");
+
+ // negFloatDown's first element has value of -4.03 which should return -4
+ test(function() {
+ var testLI = document.getElementById("negFloatDown").children[0];
+ assert_equals(testLI.value, -4, "value of -4.03 -> -4");
+ }, "IDL and content attribute parse value of '-4.03' correctly.");
+
+ // posFloatUp's first element has value of 4.9 which should return 4
+ test(function() {
+ var testLI = document.getElementById("posFloatUp").children[0];
+ assert_equals(testLI.value, 4, "value of 4.9 -> 4");
+ }, "IDL and content attribute parse value of '4.9' correctly.");
+
+ // negFloatUp's first element has value of -4.9 which should return -4
+ test(function() {
+ var testLI = document.getElementById("negFloatUp").children[0];
+ assert_equals(testLI.value, -4, "value of -4.9 -> -4");
+ }, "IDL and content attribute parse value of '-4.9' correctly.");
+
+ // exponent's first element has value of 7e2 which should return 7
+ test(function() {
+ var testLI = document.getElementById("exponent").children[0];
+ assert_equals(testLI.value, 7, "value of 7e2 -> 7");
+ }, "IDL and content attribute parse value of '7e2' correctly.");
+
+ // decimal's first element has value of .5 which should return 0
+ test(function() {
+ var testLI = document.getElementById("decimal").children[0];
+ assert_equals(testLI.value, 0, "value of .5 -> 0 (default)");
+ }, "IDL and content attribute parse value of '.5' correctly.");
+
+ // letter's first element has value of A which should return 0
+ test(function() {
+ var testLI = document.getElementById("letter").children[0];
+ assert_equals(testLI.value, 0, "value of A -> 0 (default)");
+ }, "IDL and content attribute parse value of 'A' correctly.");
+
+ // SHOULD I TEST MORE NON-ASCII-DIGIT ENTRIES?
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-rev-reftest-001-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-rev-reftest-001-ref.html
new file mode 100644
index 0000000000..8da32887f8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-rev-reftest-001-ref.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ol element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <meta name="assert" content="OL's reversed attribute creates a descending list." />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span span p {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ </style>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test continues to validate the ol element.</p>
+
+ <p>These reftests are necessary because the values of the ol's li children as calculated by the user agent are NOT available programatically. Only explicitly-set values are available programatically. Therefore, we need to check actual rendering against expected rendering.</p>
+
+ <p><strong>This reftest passes if you see an ascending list followed by two descending lists.</strong></p>
+ <p>(Note: each list item has no content; only the sequencing should appear.)</p>
+
+ <span>
+
+ <p>Ordered List</p>
+ <span>
+ <p>1.</p>
+ <p>2.</p>
+ <p>3.</p>
+ </span>
+
+ <p>Ordered List - reversed via content attribute</p>
+ <span>
+ <p>3.</p>
+ <p>2.</p>
+ <p>1.</p>
+ </span>
+
+ <p>Ordered List - reversed via IDL</p>
+ <span>
+ <p>3.</p>
+ <p>2.</p>
+ <p>1.</p>
+ </span>
+
+ </span>
+
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-rev-reftest-001.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-rev-reftest-001.html
new file mode 100644
index 0000000000..7c502e3835
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-rev-reftest-001.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ol element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <link rel="match" href="grouping-ol-rev-reftest-001-ref.html" />
+ <meta name="assert" content="OL's reversed attribute creates a descending list." />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span li {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ span ol {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace;
+ list-style-position: inside; list-style-type: decimal; }
+ </style>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test continues to validate the ol element.</p>
+
+ <p>These reftests are necessary because the values of the ol's li children as calculated by the user agent are NOT available programatically. Only explicitly-set values are available programatically. Therefore, we need to check actual rendering against expected rendering.</p>
+
+ <p><strong>This reftest passes if you see an ascending list followed by two descending lists.</strong></p>
+ <p>(Note: each list item has no content; only the sequencing should appear.)</p>
+
+ <span>
+
+ <p>Ordered List</p>
+ <ol>
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>Ordered List - reversed via content attribute</p>
+ <ol reversed="reversed">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>Ordered List - reversed via IDL</p>
+ <ol id="reverse_me">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ </span>
+
+ <script>
+ document.getElementById("reverse_me").reversed = true;
+ </script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-001-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-001-ref.html
new file mode 100644
index 0000000000..0f651183a8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-001-ref.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ol element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <meta name="assert" content="Sequences produced by calculated values for LI elements within OL match spec's expectations. (part one)" />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span span p {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ </style>
+</head>
+<body>
+ <p>This test continues to validate the ol element. This reftest is necessary because the values of the ol's li children as calculated and displayed by the user agent are NOT systematically available programatically. Only explicitly-set values are available programatically. Therefore, we need to check actual rendering against expected rendering.</p>
+
+ <p><strong>This reftest passes if each list's items are numbered identically to the horizontal sequence immediately above those list items.</strong></p>
+ <p>(Note: each list item has no content; only the sequencing should appear.)</p>
+
+ <span>
+
+ <p>2, 3, 4 (ol has start attribute of 2)</p>
+ <span>
+ <p>2.</p>
+ <p>3.</p>
+ <p>4.</p>
+ </span>
+
+ <p>-9, -8, -7 (ol has start attribute of -9)</p>
+ <span>
+ <p>-9.</p>
+ <p>-8.</p>
+ <p>-7.</p>
+ </span>
+
+ <p>1000, 1001, 1002 (list's start attribute of 1000 provided by JavaScript)</p>
+ <span>
+ <p>1000.</p>
+ <p>1001.</p>
+ <p>1002.</p>
+ </span>
+
+ <p>2, 1, 9 (each list item has a specified value attribute, list has a start attribute of 1000)</p>
+ <span>
+ <p>2.</p>
+ <p>1.</p>
+ <p>9.</p>
+ </span>
+
+ </span>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-001.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-001.html
new file mode 100644
index 0000000000..b6de3dbb4b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-001.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ol element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <link rel="match" href="grouping-ol-start-reftest-001-ref.html" />
+ <meta name="assert" content="Sequences produced by calculated values for LI elements within OL match spec's expectations (part one)." />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span li {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ span ol {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace;
+ list-style-position: inside; list-style-type: decimal; }
+ </style>
+</head>
+<body>
+ <p>This test continues to validate the ol element. This reftest is necessary because the values of the ol's li children as calculated and displayed by the user agent are NOT systematically available programatically. Only explicitly-set values are available programatically. Therefore, we need to check actual rendering against expected rendering.</p>
+
+ <p><strong>This reftest passes if each list's items are numbered identically to the horizontal sequence immediately above those list items.</strong></p>
+ <p>(Note: each list item has no content; only the sequencing should appear.)</p>
+
+ <span>
+
+ <p>2, 3, 4 (ol has start attribute of 2)</p>
+ <ol start="2">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>-9, -8, -7 (ol has start attribute of -9)</p>
+ <ol start="-9">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>1000, 1001, 1002 (list's start attribute of 1000 provided by JavaScript)</p>
+ <ol id="start_me">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>2, 1, 9 (each list item has a specified value attribute, list has a start attribute of 1000)</p>
+ <ol istart="1000">
+ <li value="2"></li>
+ <li value="1"></li>
+ <li value="9"></li>
+ </ol>
+
+ </span>
+
+ <script>
+ document.getElementById("start_me").start = 1000;
+ </script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-002-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-002-ref.html
new file mode 100644
index 0000000000..2dbdf4aa74
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-002-ref.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ol element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <meta name="assert" content="Sequences produced by calculated values for LI elements within OL match spec's expectations. (part two)" />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span span p {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ </style>
+</head>
+<body>
+ <p>This test continues to validate the ol element. This reftest is necessary because the values of the ol's li children as calculated and displayed by the user agent are NOT systematically available programatically. Only explicitly-set values are available programatically. Therefore, we need to check actual rendering against expected rendering.</p>
+
+ <p><strong>This reftest passes if each list's items are numbered identically to the horizontal sequence immediately above those list items.</strong></p>
+ <p>(Note: each list item has no content; only the sequencing should appear.)</p>
+
+ <span>
+
+ <p>4, 5, 6 (ol has start attribute of 2 which is overridden by first list item's value attribute of 4)</p>
+ <span>
+ <p>4.</p>
+ <p>5.</p>
+ <p>6.</p>
+ </span>
+
+ <p>4, 5, 6 (ol has start attribute of -10 which is overridden by first list item's value attribute of 4)</p>
+ <span>
+ <p>4.</p>
+ <p>5.</p>
+ <p>6.</p>
+ </span>
+
+ <p>1, 5, 6 (2nd list item has value attribute of 5)</p>
+ <span>
+ <p>1.</p>
+ <p>5.</p>
+ <p>6.</p>
+ </span>
+
+ <p>-1, -5, -4 (list has a start attribute of -1, and 2nd list item has value attribute of -5)</p>
+ <span>
+ <p>-1.</p>
+ <p>-5.</p>
+ <p>-4.</p>
+ </span>
+
+ </span>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-002.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-002.html
new file mode 100644
index 0000000000..9b21c79496
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-start-reftest-002.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ol element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <link rel="match" href="grouping-ol-start-reftest-002-ref.html" />
+ <meta name="assert" content="Sequences produced by calculated values for LI elements within OL match spec's expectations. (part two)" />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span li {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ span ol {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace;
+ list-style-position: inside; list-style-type: decimal; }
+ </style>
+</head>
+<body>
+ <p>This test continues to validate the ol element. This reftest is necessary because the values of the ol's li children as calculated and displayed by the user agent are NOT systematically available programatically. Only explicitly-set values are available programatically. Therefore, we need to check actual rendering against expected rendering.</p>
+
+ <p><strong>This reftest passes if each list's items are numbered identically to the horizontal sequence immediately above those list items.</strong></p>
+ <p>(Note: each list item has no content; only the sequencing should appear.)</p>
+
+ <span>
+
+ <p>4, 5, 6 (ol has start attribute of 2 which is overridden by first list item's value attribute of 4)</p>
+ <ol start="2">
+ <li value="4"></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>4, 5, 6 (ol has start attribute of -10 which is overridden by first list item's value attribute of 4)</p>
+ <ol start="-10">
+ <li value="4"></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>1, 5, 6 (2nd list item has value attribute of 5)</p>
+ <ol>
+ <li></li>
+ <li value="5"></li>
+ <li></li>
+ </ol>
+
+ <p>-1, -5, -4 (list has a start attribute of -1, and 2nd list item has value attribute of -5)</p>
+ <ol start="-1">
+ <li></li>
+ <li value="-5"></li>
+ <li></li>
+ </ol>
+
+ </span>
+
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-001-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-001-ref.html
new file mode 100644
index 0000000000..391859efc6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-001-ref.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ol element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <meta name="assert" content="OL's type attribute defaults to decimal state." />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span span p {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ </style>
+</head>
+<body>
+ <p>This test continues to validate the ol element. This reftest is necessary because the values of the ol's li children as calculated and displayed by the user agent are NOT systematically available programatically. Only explicitly-set values are available programatically. Therefore, we need to check actual rendering against expected rendering.</p>
+
+ <p><strong>This reftest passes if each list's items are labelled identically to the horizontal sequence immediately above those list items:</strong></p>
+ <p>(Note: each list item has no content; only the sequencing should appear.)</p>
+
+ <span>
+ <p>1, 2, 3 (default value for unspecified type attribute is 'decimal')</p>
+ <span>
+ <p>1.</p>
+ <p>2.</p>
+ <p>3.</p>
+ </span>
+
+ <p>1, 2, 3 (default value for type attribute not listed in spec table is 'decimal' (type = "!"))</p>
+ <span>
+ <p>1.</p>
+ <p>2.</p>
+ <p>3.</p>
+ </span>
+
+ <p>1, 2, 3 (default value for type attribute not listed in spec table is 'decimal' (type = "2"))</p>
+ <span>
+ <p>1.</p>
+ <p>2.</p>
+ <p>3.</p>
+ </span>
+
+ <p>1, 2, 3 (default value for type attribute not listed in spec table is 'decimal' (type = "b"))</p>
+ <span>
+ <p>1.</p>
+ <p>2.</p>
+ <p>3.</p>
+ </span>
+
+ </span>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-001.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-001.html
new file mode 100644
index 0000000000..b7dc22d779
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-001.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ol element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <link rel="match" href="grouping-ol-type-reftest-001-ref.html" />
+ <meta name="assert" content="OL's type attribute defaults to decimal state." />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span li {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ span ol {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace; list-style-position: inside; }
+ </style>
+</head>
+<body>
+ <p>This test continues to validate the ol element. This reftest is necessary because the values of the ol's li children as calculated and displayed by the user agent are NOT systematically available programatically. Only explicitly-set values are available programatically. Therefore, we need to check actual rendering against expected rendering.</p>
+
+ <p><strong>This reftest passes if each list's items are labelled identically to the horizontal sequence immediately above those list items:</strong></p>
+ <p>(Note: each list item has no content; only the sequencing should appear.)</p>
+
+ <span>
+
+ <p>1, 2, 3 (default value for unspecified type attribute is 'decimal')</p>
+ <ol>
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>1, 2, 3 (default value for type attribute not listed in spec table is 'decimal' (type = "!"))</p>
+ <ol type="!">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>1, 2, 3 (default value for type attribute not listed in spec table is 'decimal' (type = "2"))</p>
+ <ol type="2">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>1, 2, 3 (default value for type attribute not listed in spec table is 'decimal' (type = "b"))</p>
+ <ol type="b">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ </span>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-002-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-002-ref.html
new file mode 100644
index 0000000000..8d8012e8ed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-002-ref.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ol element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <meta name="assert" content="List items are rendered consistently with the state of the type attribute." />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span span p {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ </style>
+</head>
+<body>
+ <p>This test continues to validate the ol element. This reftest is necessary because the values of the ol's li children as calculated and displayed by the user agent are NOT systematically available programatically. Only explicitly-set values are available programatically. Therefore, we need to check actual rendering against expected rendering.</p>
+
+ <p><strong>This reftest passes if each list's items are labelled identically to the horizontal sequence immediately above those list items:</strong></p>
+ <p>(Note: each list item has no content; only the sequencing should appear.)</p>
+
+ <span>
+ <p>1, 2, 3 (type attribute of "1" results in decimal type)</p>
+ <span>
+ <p>1.</p>
+ <p>2.</p>
+ <p>3.</p>
+ </span>
+
+ <p>aa, ab, ac (type attribute of "a" results in lower case latin alphabet, start = 27)</p>
+ <span>
+ <p>aa.</p>
+ <p>ab.</p>
+ <p>ac.</p>
+ </span>
+
+ <p>AA, AB, AC (type attribute of "A" results in upper case latin alphabet, start = 27)</p>
+ <span>
+ <p>AA.</p>
+ <p>AB.</p>
+ <p>AC.</p>
+ </span>
+
+ <p>i, v, c (type attribute of "i" results in lower case roman alphabet, list values = 1, 5, 100)</p>
+ <span>
+ <p>i.</p>
+ <p>v.</p>
+ <p>c.</p>
+ </span>
+
+ <p>I, V, C (type attribute of "I" results in upper case roman alphabet, list values = 1, 5, 100)</p>
+ <span>
+ <p>I.</p>
+ <p>V.</p>
+ <p>C.</p>
+ </span>
+
+ </span>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-002.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-002.html
new file mode 100644
index 0000000000..db55fd4bc4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-002.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ol element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <link rel="match" href="grouping-ol-type-reftest-002-ref.html" />
+ <meta name="assert" content="List items are rendered consistently with the state of the type attribute." />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span li {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ span ol {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace; list-style-position: inside;}
+ </style>
+</head>
+<body>
+ <p>This test continues to validate the ol element. This reftest is necessary because the values of the ol's li children as calculated and displayed by the user agent are NOT systematically available programatically. Only explicitly-set values are available programatically. Therefore, we need to check actual rendering against expected rendering.</p>
+
+ <p><strong>This reftest passes if each list's items are labelled identically to the horizontal sequence immediately above those list items:</strong></p>
+ <p>(Note: each list item has no content; only the sequencing should appear.)</p>
+
+ <span>
+
+ <p>1, 2, 3 (type attribute of "1" results in decimal type)</p>
+ <ol type="1">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>aa, ab, ac (type attribute of "a" results in lower case latin alphabet, start = 27)</p>
+ <ol type="a" start="27">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>AA, AB, AC (type attribute of "A" results in upper case latin alphabet, start = 27)</p>
+ <ol type="A" start="27">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>i, v, c (type attribute of "i" results in lower case roman alphabet, list values = 1, 5, 100)</p>
+ <ol type="i">
+ <li value="1"></li>
+ <li value="5"></li>
+ <li value="100"></li>
+ </ol>
+
+ <p>I, V, C (type attribute of "I" results in upper case roman alphabet, list values = 1, 5, 100)</p>
+ <ol type="I">
+ <li value="1"></li>
+ <li value="5"></li>
+ <li value="100"></li>
+ </ol>
+
+ </span>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-003-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-003-ref.html
new file mode 100644
index 0000000000..6c836cf367
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-003-ref.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ol element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <meta name="assert" content="OL's type attribute defaults to decimal state." />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span span p {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ </style>
+</head>
+<body>
+ <p>This test continues to validate the ol element. This reftest is necessary because the values of the ol's li children as calculated and displayed by the user agent are NOT systematically available programatically. Only explicitly-set values are available programatically. Therefore, we need to check actual rendering against expected rendering.</p>
+
+ <p><strong>This reftest passes if each list's items are labelled identically to the horizontal sequence immediately above those list items:</strong></p>
+ <p>(Note: each list item has no content; only the sequencing should appear.)</p>
+
+ <span>
+ <p>-3, -2, -1 (type is "a", start is -3)</p>
+ <span>
+ <p>-3.</p>
+ <p>-2.</p>
+ <p>-1.</p>
+ </span>
+
+ <p>0, a (type is "a", start is 0)</p>
+ <span>
+ <p>0.</p>
+ <p>a.</p>
+ </span>
+
+ <p>-3, -2, -1 (type is "A", start is -3)</p>
+ <span>
+ <p>-3.</p>
+ <p>-2.</p>
+ <p>-1.</p>
+ </span>
+
+ <p>0, A (type is "A", start is 0)</p>
+ <span>
+ <p>0.</p>
+ <p>A.</p>
+ </span>
+
+ <p>-3, -2, -1 (type is "i", start is -3)</p>
+ <span>
+ <p>-3.</p>
+ <p>-2.</p>
+ <p>-1.</p>
+ </span>
+
+ <p>0, i (type is "i", start is 0)</p>
+ <span>
+ <p>0.</p>
+ <p>i.</p>
+ </span>
+
+ <p>-3, -2, -1 (type is "I", start is -3)</p>
+ <span>
+ <p>-3.</p>
+ <p>-2.</p>
+ <p>-1.</p>
+ </span>
+
+ <p>0, I (type is "I", start is 0)</p>
+ <span>
+ <p>0.</p>
+ <p>I.</p>
+ </span>
+
+ </span>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-003.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-003.html
new file mode 100644
index 0000000000..654287298f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol-type-reftest-003.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ol element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <link rel="match" href="grouping-ol-type-reftest-003-ref.html" />
+ <meta name="assert" content="OL's type attribute defaults to decimal state." />
+ <style type="text/css">
+ span p {display:list-item; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0;}
+ span li {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-top: 0; padding-bottom: 0; font-family: monospace;}
+ span ol {margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 5em; padding-top: 0; padding-bottom: 0; font-family: monospace; list-style-position: inside;}
+ </style>
+</head>
+<body>
+ <p>This test continues to validate the ol element. This reftest is necessary because the values of the ol's li children as calculated and displayed by the user agent are NOT systematically available programatically. Only explicitly-set values are available programatically. Therefore, we need to check actual rendering against expected rendering.</p>
+
+ <p><strong>This reftest passes if each list's items are labelled identically to the horizontal sequence immediately above those list items:</strong></p>
+ <p>(Note: each list item has no content; only the sequencing should appear.)</p>
+
+ <span>
+
+ <p>-3, -2, -1 (type is "a", start is -3)</p>
+ <ol type="a" start="-3">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>0, a (type is "a", start is 0)</p>
+ <ol type="a" start="0">
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>-3, -2, -1 (type is "A", start is -3)</p>
+ <ol type="A" start="-3">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>0, A (type is "A", start is 0)</p>
+ <ol type="A" start="0">
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>-3, -2, -1 (type is "i", start is -3)</p>
+ <ol type="i" start="-3">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>0, i (type is "i", start is 0)</p>
+ <ol type="i" start="0">
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>-3, -2, -1 (type is "I", start is -3)</p>
+ <ol type="I" start="-3">
+ <li></li>
+ <li></li>
+ <li></li>
+ </ol>
+
+ <p>0, I (type is "I", start is 0)</p>
+ <ol type="I" start="0">
+ <li></li>
+ <li></li>
+ </ol>
+
+ </span>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol.html
new file mode 100644
index 0000000000..80fa734a92
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/grouping-ol.html
@@ -0,0 +1,299 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>the ol element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test validates the ol element.</p>
+
+ <div id="log"></div>
+
+ <p>Ordered List</p>
+ <ol id="basic">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="allAtts" reversed start="3" type="A">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="justRev" reversed>
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="basicRevGoodName" reversed="reversed">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="basicRevNameWithSpace" reversed=" reversed ">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="basicRevEmpty" reversed="" start="A">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="basicRevTrue" reversed="true">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="basicRevFalse" reversed="false">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="start2" start="2">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="negative" start="-10">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="posFloatDown" start="4.03">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="negFloatDown" start="-4.03">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="posFloatUp" start="4.9">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="negFloatUp" start="-4.9">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="exponent" start="7e2">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="decimal" start=".5">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="letter" start="A">
+ <li>list item</li>
+ <li>list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="middle50">
+ <li>list item</li>
+ <li value="50">list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="middleneg50">
+ <li>list item</li>
+ <li value="-50">list item</li>
+ <li>list item</li>
+ </ol>
+
+ <p>Ordered List</p>
+ <ol id="lots" reversed="reversed">
+ <li value="10">list item</li>
+ <li value="20">list item</li>
+<a></a><abbr></abbr><address></address><area></area><article></article><aside></aside><audio></audio><b></b><base></base><bdi></bdi><bdo></bdo><blockquote></blockquote><body></body><br></br><button></button><canvas></canvas><caption></caption><cite></cite><code></code><col></col><colgroup></colgroup><command></command><datalist></datalist><dd></dd><del></del><details></details><dfn></dfn><dialog></dialog><div></div><dl></dl><dt></dt><em></em><embed></embed><fieldset></fieldset><figcaption></figcaption><figure></figure><footer></footer><form></form><h1></h1><h2></h2><h3></h3><h4></h4><h5></h5><h6></h6><head></head><header></header><hgroup></hgroup><hr></hr><html></html><i></i><iframe></iframe><img></img><input></input><ins></ins><kbd></kbd><keygen></keygen><label></label><legend></legend><link></link><map></map><mark></mark><menu></menu><meta></meta><meter></meter><nav></nav><noscript></noscript><object></object><ol><li></li><li></li></ol><optgroup></optgroup><option></option><output></output><p></p><param></param><pre></pre><progress></progress><q></q><rp></rp><rt></rt><ruby></ruby><s></s><samp></samp><script></script><section></section><select></select><small></small><source></source><span></span><strong></strong><style></style><sub></sub><summary></summary><sup></sup><table></table><tbody></tbody><td></td><textarea></textarea><tfoot></tfoot><th></th><thead></thead><time></time><title></title><tr></tr><track></track><u></u><ul><li></li><li></li></ul><var></var><video></video><wbr></wbr>
+ <li value="-99">list item</li>
+ </ol>
+
+ <script>
+ "use strict";
+
+ var testList;
+
+ // check that prototype matches spec's DOM interface
+ test(function () {
+ testList = document.getElementById("basic");
+ assert_equals(Object.getPrototypeOf(testList), HTMLOListElement.prototype, "HTMLOListElement.prototype should be used for OL");
+ }, "The prototype for OL is HTMLOListElement.prototype");
+
+ // check that "own" properties reversed, start, and type are present
+ test(function () {
+ testList = document.getElementById("basic");
+ assert_idl_attribute(testList, "reversed");
+ }, "'reversed' property should be defined on OL.");
+
+ test(function () {
+ testList = document.getElementById("basic");
+ assert_idl_attribute(testList, "start");
+ }, "'start' property should be defined on OL.");
+
+ test(function () {
+ testList = document.getElementById("basic");
+ assert_idl_attribute(testList, "type");
+ }, "'type' property should be defined on OL.");
+
+ // "The reversed, start, and type IDL attributes must reflect the respective content attributes of the same name."
+ test(function () {
+ testList = document.getElementById("allAtts");
+ assert_true(testList.reversed);
+ }, "OL's 'reversed' IDL property reflects content attribute.");
+
+ test(function () {
+ testList = document.getElementById("allAtts");
+ assert_equals(testList.start, 3);
+ }, "OL's 'start' IDL property reflects content attribute.");
+
+ test(function () {
+ testList = document.getElementById("allAtts");
+ assert_equals(testList.type, "A");
+ }, "OL's 'type' IDL property reflects content attribute.");
+
+
+ // "The reversed attribute is a boolean attribute."
+
+ // check lists for which reversed value should be false
+ test(function() {
+ assert_false(document.getElementById("basic").reversed, "IDL 'reversed' attribute value false when content attribute absent");
+ }, "IDL 'reversed' attribute value false when content attribute absent");
+
+ // check lists for which reversed value should be true
+ test(function() {
+ assert_true(document.getElementById("justRev").reversed);
+ assert_true(document.getElementById("basicRevGoodName").reversed);
+ assert_true(document.getElementById("basicRevNameWithSpace").reversed);
+ assert_true(document.getElementById("basicRevEmpty").reversed);
+ assert_true(document.getElementById("basicRevTrue").reversed);
+ assert_true(document.getElementById("basicRevFalse").reversed);
+ }, "IDL 'reversed' attribute value true when content attribute exists");
+
+ // check that IDL property works to change reversed value
+ test(function() {
+ document.getElementById("justRev").reversed = false;
+ assert_false(document.getElementById("justRev").reversed, "Changing IDL 'reversed' property changes list's reversed property.");
+ }, "Changing IDL 'reversed' property changes list's reversed property.");
+
+
+ // If the start attribute is present, user agents must parse it as an integer, in order to determine the attribute's value.
+ // The default value, used if the attribute is missing or
+ // if the value cannot be converted to a number according to the referenced algorithm,
+ // is 1 if the element has no reversed attribute, and
+ // is the number of child li elements otherwise."
+ // "The start IDL attribute has the same default as its content attribute."
+
+ test(function() {
+ assert_equals(document.getElementById("basic").start, 1);
+ }, "Default start value for non-reversed list should be 1");
+
+ test(function() {
+ assert_equals(document.getElementById("decimal").start, 1);
+ }, "IDL and content attribute parse start of '.5' correctly.");
+
+ test(function() {
+ assert_equals(document.getElementById("letter").start, 1);
+ }, "IDL and content attribute parse start of 'A' correctly.");
+
+ test(function() {
+ assert_equals(document.getElementById("basicRevGoodName").start, 1);
+ }, "Default start value (if none provided) for reversed list = 1.");
+
+ test(function() {
+ assert_equals(document.getElementById("basicRevEmpty").start, 1);
+ }, "Default start value (if failed to parse) for reversed list = 1.");
+
+ test(function() {
+ assert_equals(document.getElementById("lots").start, 1);
+ }, "Default start value for reversed list = 1 (even with tons of other child elements).");
+
+ test(function() {
+ var myList = document.getElementById("basicRevGoodName"), myLI = document.createElement("li");
+ myList.appendChild(myLI);
+ assert_equals(document.getElementById("basicRevGoodName").start, 1);
+ }, "Adding child element to reversed list does not change start value");
+
+ test(function() {
+ var myList = document.getElementById("basicRevTrue");
+ myList.removeChild(myList.children[0]);
+ assert_equals(document.getElementById("basicRevTrue").start, 1);
+ }, "Deleting child element from reversed list does not change start value");
+
+ test(function() {
+ assert_equals(document.getElementById("start2").start, 2);
+ }, "IDL and content attribute parse start of '2' correctly.");
+
+ test(function() {
+ assert_equals(document.getElementById("negative").start, -10);
+ }, "IDL and content attribute parse start of '-10' correctly.");
+
+ test(function() {
+ assert_equals(document.getElementById("posFloatDown").start, 4);
+ }, "IDL and content attribute parse start of '4.03' correctly.");
+
+ test(function() {
+ assert_equals(document.getElementById("negFloatDown").start, -4);
+ }, "IDL and content attribute parse start of '-4.03' correctly.");
+
+ test(function() {
+ assert_equals(document.getElementById("posFloatUp").start, 4);
+ }, "IDL and content attribute parse start of '4.9' correctly.");
+
+ test(function() {
+ assert_equals(document.getElementById("negFloatUp").start, -4);
+ }, "IDL and content attribute parse start of '-4.9' correctly.");
+
+ test(function() {
+ assert_equals(document.getElementById("exponent").start, 7);
+ }, "IDL and content attribute parse start of '7e2' correctly.");
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/ol.start-reflection-1.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/ol.start-reflection-1.html
new file mode 100644
index 0000000000..7f2a00c703
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/ol.start-reflection-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>ol.start - reflection test</title>
+ <link rel="author" title="Shiki Okasaka" href="http://shiki.esrille.com/">
+ <link rel="author" title="Esrille Inc." href="http://www.esrille.com/">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <meta name="assert" content="This test checks that the start IDL attribute reflects the respective content attribute of the same name.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <ol id="ol">
+ <li>One</li>
+ <li>Two</li>
+ <li>Three</li>
+ </ol>
+ <div id="log"></div>
+ <script>
+test(function() {
+ assert_equals(document.getElementById('ol').start, 1);
+})
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/ol.start-reflection-2.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/ol.start-reflection-2.html
new file mode 100644
index 0000000000..c7c9aeab48
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/ol.start-reflection-2.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>ol.start - reflection test</title>
+ <link rel="author" title="Shiki Okasaka" href="http://shiki.esrille.com/">
+ <link rel="author" title="Esrille Inc." href="http://www.esrille.com/">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ol-element">
+ <meta name="assert" content="This test checks that the start IDL attribute reflects the respective content attribute of the same name.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <ol id='ol' reversed>
+ <li>Three</li>
+ <li>Two</li>
+ <li>One</li>
+ </ol>
+ <div id='log'></div>
+ <script>
+test(function() {
+ assert_equals(document.getElementById('ol').start, 1);
+})
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1-ref.html
new file mode 100644
index 0000000000..f8cac3c702
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<ol>
+ <li value="3">Three</li>
+ <li value="2">Two</li>
+ <li value="1">One</li>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1a.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1a.html
new file mode 100644
index 0000000000..202315b1c6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1a.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>`reversed` should reverse the numbering correctly</title>
+<link rel=match href="reversed-1-ref.html">
+<link rel=help href="https://html.spec.whatwg.org/#attr-ol-reversed">
+<ol reversed>
+ <li>Three</li>
+ <li>Two</li>
+ <li>One</li>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1b.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1b.html
new file mode 100644
index 0000000000..4d6202943d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1b.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Dynamically setting `reversed` should update the numbering</title>
+<link rel=match href="reversed-1-ref.html">
+<link rel=help href="https://html.spec.whatwg.org/#attr-ol-reversed">
+<ol id="x">
+ <li>Three</li>
+ <li>Two</li>
+ <li>One</li>
+</ol>
+<script>
+ var l = document.getElementById("x");
+ var w = l.offsetWidth;
+ l.setAttribute("reversed", "");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1c.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1c.html
new file mode 100644
index 0000000000..6fad13053f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1c.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Reversed numbering should update on dynamic addition of child nodes</title>
+<link rel=match href="reversed-1-ref.html">
+<link rel=help href="https://html.spec.whatwg.org/#attr-ol-reversed">
+<ol id="x" reversed>
+ <li>Three</li>
+ <li>Two</li>
+</ol>
+<script>
+ var l = document.getElementById("x");
+ var w = l.offsetWidth;
+ var li = document.createElement("li");
+ li.textContent = "One"
+ l.appendChild(li);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1d.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1d.html
new file mode 100644
index 0000000000..a256b6a428
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1d.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Reverse numbering should not be affected by nested div</title>
+<link rel=match href="reversed-1-ref.html">
+<link rel=help href="https://html.spec.whatwg.org/#attr-ol-reversed">
+<ol reversed>
+ <li>Three</li>
+ <div>
+ <li>Two</li>
+ <li>One</li>
+ </div>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1e.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1e.html
new file mode 100644
index 0000000000..48a2799942
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-1e.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Reverse numbering should not count display:none elements</title>
+<link rel=match href="reversed-1-ref.html">
+<link rel=help href="https://html.spec.whatwg.org/#attr-ol-reversed">
+<ol reversed>
+ <li>Three</li>
+ <li style="display:none">Three</li>
+ <li>Two</li>
+ <li>One</li>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-2-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-2-ref.html
new file mode 100644
index 0000000000..4f3ece2be4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-2-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<ol>
+ <li value="5">Five</li>
+ <li value="4">Four</li>
+ <li value="3">Three</li>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-2.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-2.html
new file mode 100644
index 0000000000..0d4948153c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ol-element/reversed-2.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<link rel="match" href="reversed-2-ref.html">
+<ol reversed start="5">
+ <li>Five</li>
+ <li>Four</li>
+ <li>Three</li>
+</ol>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-p-element/grouping-p.html b/testing/web-platform/tests/html/semantics/grouping-content/the-p-element/grouping-p.html
new file mode 100644
index 0000000000..5f15aca31f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-p-element/grouping-p.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>the p element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-p-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ "use strict";
+
+ // check that prototype matches spec's DOM interface
+ test(function () {
+ var testElement = document.createElement("p");
+ assert_equals(Object.getPrototypeOf(testElement), HTMLParagraphElement.prototype, "HTMLParagraphElement.prototype should be used for p");
+ }, "The prototype for p is HTMLParagraphElement.prototype");
+
+ </script>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test validates the p element.</p>
+
+ <div id="log"></div>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/grouping-pre-reftest-001-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/grouping-pre-reftest-001-ref.html
new file mode 100644
index 0000000000..75aa91b302
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/grouping-pre-reftest-001-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>pre element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-pre-element">
+ <meta name="assert" content="Newlines within pre elements separate paragraphs for the purposes of BIDI." />
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test continues to validate the pre element.</p>
+
+ <p>The spec states:</p>
+ <blockquote>"A newline in a pre element should separate paragraphs for the purposes of the Unicode bidirectional algorithm. This requirement may be implemented indirectly through the style layer. For example, an HTML+CSS user agent could implement these requirements by implementing the CSS 'unicode-bidi' property."</blockquote>
+
+ <p>This reftest passes if below you see "ABC ABC" repeated on two separate lines below (4 ABCs total):</p>
+
+ <pre>ABC ABC
+ABC ABC</pre>
+
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/grouping-pre-reftest-001.html b/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/grouping-pre-reftest-001.html
new file mode 100644
index 0000000000..29e582edd5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/grouping-pre-reftest-001.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>pre element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-pre-element">
+ <link rel="match" href="grouping-pre-reftest-001-ref.html" />
+ <meta name="assert" content="Newlines within pre elements separate paragraphs for the purposes of BIDI." />
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test continues to validate the pre element.</p>
+
+ <p>The spec states:</p>
+ <blockquote>"A newline in a pre element should separate paragraphs for the purposes of the Unicode bidirectional algorithm. This requirement may be implemented indirectly through the style layer. For example, an HTML+CSS user agent could implement these requirements by implementing the CSS 'unicode-bidi' property."</blockquote>
+
+ <p>This reftest passes if below you see "ABC ABC" repeated on two separate lines below (4 ABCs total):</p>
+
+ <pre>&#x202E CBA CBA
+ABC ABC</pre>
+
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/grouping-pre.html b/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/grouping-pre.html
new file mode 100644
index 0000000000..07fc631b91
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/grouping-pre.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>the pre element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-pre-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test validates the pre element.</p>
+
+ <div id="log"></div>
+
+ <script>
+ "use strict";
+
+ // check that prototype matches spec's DOM interface
+ test(function () {
+ var testElement = document.createElement("pre");
+ assert_equals(Object.getPrototypeOf(testElement), HTMLPreElement.prototype, "HTMLPreElement.prototype should be used for pre");
+ }, "The prototype for pre is HTMLPreElement.prototype");
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/pre-newline-bidi-ref.html b/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/pre-newline-bidi-ref.html
new file mode 100644
index 0000000000..0f302b5d4b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/pre-newline-bidi-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>HTML Test reference: newline in &lt;pre&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-pre-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">
+ <pre>
+A Hebrew letter and a full stop: &#x05d0;.&lrm;
+&#x05d0; this line begins with a Hebrew letter.
+ </pre>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/pre-newline-bidi.html b/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/pre-newline-bidi.html
new file mode 100644
index 0000000000..23d442f52c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-pre-element/pre-newline-bidi.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: newline in pre separates bidi paragraphs</title>
+ <link rel="match" href="pre-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-pre-element"/>
+ <meta name="assert"
+ content="A newline in a pre element 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">
+ <pre>
+A Hebrew letter and a full stop: &#x05d0;.
+&#x05d0; this line begins with a Hebrew letter.
+ </pre>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/grouping-content/the-ul-element/grouping-ul.html b/testing/web-platform/tests/html/semantics/grouping-content/the-ul-element/grouping-ul.html
new file mode 100644
index 0000000000..6e62343f6b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/grouping-content/the-ul-element/grouping-ul.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>the ul element</title>
+ <link rel="author" title="dzenana" href="mailto:dzenana.trenutak@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-ul-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ "use strict";
+
+ // check that prototype matches spec's DOM interface
+ test(function () {
+ var testElement = document.createElement("ul");
+ assert_equals(Object.getPrototypeOf(testElement), HTMLUListElement.prototype, "HTMLUListElement.prototype should be used for ul");
+ }, "The prototype for ul is HTMLUListElement.prototype");
+
+ </script>
+</head>
+<body>
+ <h1>Description</h1>
+ <p>This test validates the ul element.</p>
+
+ <div id="log"></div>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/META.yml b/testing/web-platform/tests/html/semantics/interactive-elements/META.yml
new file mode 100644
index 0000000000..c1dd8dddf9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/META.yml
@@ -0,0 +1,2 @@
+suggested_reviewers:
+ - foolip
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/commands/common/accesskey.js b/testing/web-platform/tests/html/semantics/interactive-elements/commands/common/accesskey.js
new file mode 100644
index 0000000000..f08761be8c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/commands/common/accesskey.js
@@ -0,0 +1,36 @@
+setup({explicit_done: true, explicit_timeout: true});
+
+const NOTRUN = 3;
+let status = NOTRUN;
+function notrun() {
+ return status === NOTRUN;
+}
+add_completion_callback(tests => {
+ status = tests[0].status;
+});
+
+function pass() {
+ // Wait a couple of frames in case fail() is also called.
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ if (notrun()) {
+ test(() => {});
+ done();
+ }
+ });
+ });
+}
+
+function fail(msg) {
+ if (notrun()) {
+ test(() => { assert_unreached(msg); });
+ done();
+ }
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ const accessKeyElement = document.querySelector('[accesskey]');
+ if (accessKeyElement.accessKeyLabel) {
+ document.querySelector('kbd').textContent = accessKeyElement.accessKeyLabel;
+ }
+});
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/first-input-after-legend-manual.html b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/first-input-after-legend-manual.html
new file mode 100644
index 0000000000..521b4bb975
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/first-input-after-legend-manual.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>First input after the legend</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common/accesskey.js></script>
+<p>Press the access key combination for "a". <kbd></kbd></p>
+<fieldset>
+ <legend accesskey=a>legend</legend>
+ <input onfocus="pass()">
+</fieldset>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/first-input-before-legend-manual.html b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/first-input-before-legend-manual.html
new file mode 100644
index 0000000000..1c40cc7b81
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/first-input-before-legend-manual.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>First input before the legend</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common/accesskey.js></script>
+<p>Press the access key combination for "a". <kbd></kbd></p>
+<fieldset>
+ <input onfocus="pass()">
+ <legend accesskey=a>legend
+ <input onfocus="fail('input in legend was focused')">
+ </legend>
+ <input onfocus="fail('input after legend was focused')">
+</fieldset>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/first-input-inside-legend-manual.html b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/first-input-inside-legend-manual.html
new file mode 100644
index 0000000000..abd3a3b2df
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/first-input-inside-legend-manual.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<title>First input inside the legend</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common/accesskey.js></script>
+<p>Press the access key combination for "a". <kbd></kbd></p>
+<fieldset>
+ <legend accesskey=a>legend
+ <input onfocus="pass()">
+ </legend>
+ <input onfocus="fail('input after legend was focused')">
+</fieldset>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/focusable-legend-manual.html b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/focusable-legend-manual.html
new file mode 100644
index 0000000000..e2880a77bf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/focusable-legend-manual.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Focusable legend</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common/accesskey.js></script>
+<p>Press the access key combination for "a". <kbd></kbd></p>
+<fieldset>
+ <legend tabindex=0 onclick="fail('unexpected click event on legend')"
+ onfocus="fail('legend was focused')" accesskey=a>
+ legend
+ <input onfocus="pass()">
+ </legend>
+ <input onfocus="fail('input after legend was focused')">
+</fieldset>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/focusable-legend-sibling-manual.html b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/focusable-legend-sibling-manual.html
new file mode 100644
index 0000000000..49dcaaf7d5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/focusable-legend-sibling-manual.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<title>Focusable legend sibling</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common/accesskey.js></script>
+<p>Press the access key combination for "a". <kbd></kbd></p>
+<fieldset>
+ <legend accesskey=a>first legend</legend>
+ <legend tabindex=0 onfocus="fail('sibling legend was focused')">second legend</legend>
+</fieldset>
+<script>
+ onkeyup = e => {
+ if (e.keyCode === 65) {
+ pass();
+ }
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/input-outside-fieldset-manual.html b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/input-outside-fieldset-manual.html
new file mode 100644
index 0000000000..dc6af48323
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/input-outside-fieldset-manual.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<title>Input outside fieldset</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common/accesskey.js></script>
+<p>Press the access key combination for "a". <kbd></kbd></p>
+<fieldset>
+ <legend accesskey=a>legend</legend>
+</fieldset>
+<input onfocus="fail('input outside fieldset was focused')">
+<script>
+ onkeyup = e => {
+ if (e.keyCode === 65) {
+ pass();
+ }
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/label-sibling-manual.html b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/label-sibling-manual.html
new file mode 100644
index 0000000000..8a7b20565f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/label-sibling-manual.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>Label sibling</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common/accesskey.js></script>
+<p>Press the access key combination for "a". <kbd></kbd></p>
+<input id=x onfocus="fail('input associated with the label was focused')">
+<fieldset>
+ <legend accesskey=a>legend</legend>
+ <label for=x onclick="fail('label received a click event')">label</label>
+</fieldset>
+<script>
+ onkeyup = e => {
+ if (e.keyCode === 65) {
+ pass();
+ }
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/no-fieldset-parent-manual.html b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/no-fieldset-parent-manual.html
new file mode 100644
index 0000000000..e7abb71454
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/commands/legend/no-fieldset-parent-manual.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>No fieldset parent</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common/accesskey.js></script>
+<p>Press the access key combination for "a". <kbd></kbd></p>
+<legend accesskey=a>
+ legend
+ <input onfocus="fail('input in legend was focused')">
+</legend>
+<input onfocus="fail('input after legend was focused')">
+<script>
+ onkeyup = e => {
+ if (e.keyCode === 65) {
+ pass();
+ }
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/contextmenu-historical.html b/testing/web-platform/tests/html/semantics/interactive-elements/contextmenu-historical.html
new file mode 100644
index 0000000000..f723d3a92a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/contextmenu-historical.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>menu element removed properties</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-menu-element">
+<link rel="help" href="https://github.com/whatwg/html/pull/2742">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<menu type="context" label="label">
+ <menuitem>Text</menuitem>
+ <menuitem type="checkbox" checked>Checked</menuitem>
+ <menuitem disabled>Disabled</menuitem>
+ <menuitem default>Default</menuitem>
+</menu>
+
+<script>
+"use strict";
+
+const menu = document.querySelector("menu");
+const menuitem = document.querySelector("menuitem");
+
+test(() => {
+ assert_false("HTMLMenuItemElement" in window, "the HTMLMenuItemElement interface must not exist");
+ assert_equals(menuitem.constructor, HTMLUnknownElement, "A <menuitem> must be HTMLUnknownElement");
+
+ for (const prop of ["type", "label", "icon", "disabled", "checked", "radiogroup", "default"]) {
+ assert_false(prop in menuitem, `menuitem.${prop} must not be present`);
+ }
+}, "HTMLMenuItemElement must not be not present");
+
+test(() => {
+ const potentialBadLocations = [
+ window,
+ document,
+ HTMLElement.prototype,
+ SVGElement.prototype,
+ Document.prototype,
+ HTMLDocument.prototype,
+ Element.prototype
+ ];
+ for (const location of potentialBadLocations) {
+ assert_false("onshow" in location,
+ `${location.constructor.name} must not have a property "onshow"`);
+ }
+}, `onshow must not be present on the GlobalEventHandlers locations`);
+
+test(() => {
+ assert_false("RelatedEvent" in window);
+}, "RelatedEvent must not be present");
+
+test(() => {
+ assert_false("contextMenu" in HTMLElement.prototype,
+ "HTMLElement's prototype must not have a property \"contextMenu\"");
+ assert_false("contextMenu" in document.createElement("div"),
+ "A div must not have a property \"contextMenu\"");
+}, "el.contextMenu must not be present");
+
+test(() => {
+ assert_false("type" in menu);
+
+ menu.type = "toolbar";
+ assert_equals(menu.getAttribute("type"), "context");
+}, "menu.type must not exist or reflect the content attribute");
+
+test(() => {
+ assert_false("label" in menu);
+
+ menu.label = "new label";
+ assert_equals(menu.getAttribute("label"), "label");
+}, "menu.label must not exist or reflect the content attribute");
+
+test(() => {
+ assert_array_equals(document.querySelectorAll("menuitem:enabled"), []);
+}, ":enabled must not match menuitems");
+
+test(() => {
+ assert_array_equals(document.querySelectorAll("menuitem:disabled"), []);
+}, ":disabled must not match menuitems");
+
+test(() => {
+ assert_array_equals(document.querySelectorAll("menuitem:checked"), []);
+}, ":checked must not match menuitems");
+
+test(() => {
+ try {
+ assert_array_equals(document.querySelectorAll("menuitem:default"), []);
+ } catch (e) {
+ // Not everyone has implemented :default as of the time of this writing.
+ if (e.name !== "SyntaxError") {
+ throw e;
+ }
+ }
+}, ":default must not match menuitems");
+
+test(() => {
+ assert_equals(getComputedStyle(menu).display, "block");
+}, "The user-agent stylesheet must leave type=\"context\" menus as block display like other menus");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-ax-slot-recalc-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-ax-slot-recalc-crash.html
new file mode 100644
index 0000000000..0ecd30dda3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-ax-slot-recalc-crash.html
@@ -0,0 +1,28 @@
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1255406">
+<script type="text/javascript">
+var nodes = Array();
+var text = Array();
+ {
+ nodes[9] = document.createElement('textarea');
+ nodes[11] = document.createElement('legend');
+ nodes[44] = document.createElement('details');
+ document.documentElement.appendChild(nodes[44]);
+ nodes[68] = document.createElement('fieldset');
+ nodes[81] = document.createElement('option');
+
+
+
+ nodes[85] = document.createElement('img');
+ text[42] = document.createTextNode('744879385');
+ nodes[44].appendChild(text[42]);
+ nodes[68].appendChild(nodes[11]);
+ nodes[44].appendChild(nodes[68]);
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ try { nodes[85].appendChild(nodes[68]); } catch(e) {}
+ });
+ });
+ nodes[44].appendChild(nodes[9]);
+ requestAnimationFrame(() => { document.execCommand("SelectAll", false, ""); });
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-details-element-fragment.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-details-element-fragment.html
new file mode 100644
index 0000000000..d3d04f07a1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-details-element-fragment.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<link rel="help" href="https://github.com/whatwg/html/pull/6466">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div style="height:2000px">spacer</div>
+
+<details id=details>
+ <div id=target>target</div>
+</details>
+
+<script>
+async_test(t => {
+ assert_false(details.hasAttribute('open'),
+ `The <details> should be closed at the start of the test.`);
+ assert_equals(window.pageYOffset, 0,
+ `The page should be scrolled to the top at the start of the test.`);
+
+ window.location.hash = '#target';
+
+ requestAnimationFrame(t.step_func_done(() => {
+ assert_true(details.hasAttribute('open'),
+ `<details> should be opened by navigating to an element inside it.`);
+ assert_not_equals(window.pageYOffset, 0,
+ `The page should be scrolled down to the <details> element.`);
+ }));
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-window-find-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-window-find-crash.html
new file mode 100644
index 0000000000..d24b4634cd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-window-find-crash.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1264704">
+
+<script>
+function runTest() {
+ details2.appendChild(child);
+ document.caretRangeFromPoint();
+}
+</script>
+
+<body onload=runTest()>
+
+<details style="writing-mode: vertical-rl">
+ <div id=child>foo</div>
+</details>
+
+<details id=details2 open=true ontoggle="window.find()">
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/closed-details-layout-apis.tentative.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/closed-details-layout-apis.tentative.html
new file mode 100644
index 0000000000..1936cdb67d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/closed-details-layout-apis.tentative.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/pull/6466">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<details id=details>
+ <div style="width:100px; height:100px; background-color:red" id=innerdiv></div>
+</details>
+
+<script>
+test(() => {
+ assert_not_equals(innerdiv.getBoundingClientRect().x, 0, 'x before open');
+ assert_not_equals(innerdiv.getBoundingClientRect().y, 0, 'y before open');
+ assert_not_equals(innerdiv.getBoundingClientRect().width, 0, 'width before open');
+ assert_not_equals(innerdiv.getBoundingClientRect().height, 0, 'height before open');
+ details.open = true;
+ assert_not_equals(innerdiv.getBoundingClientRect().x, 0, 'x after open');
+ assert_not_equals(innerdiv.getBoundingClientRect().y, 0, 'y after open');
+ assert_not_equals(innerdiv.getBoundingClientRect().width, 0, 'width after open');
+ assert_not_equals(innerdiv.getBoundingClientRect().height, 0, 'height after open');
+ details.open = false;
+ assert_not_equals(innerdiv.getBoundingClientRect().x, 0, 'x after close');
+ assert_not_equals(innerdiv.getBoundingClientRect().y, 0, 'y after close');
+ assert_not_equals(innerdiv.getBoundingClientRect().width, 0, 'width after close');
+ assert_not_equals(innerdiv.getBoundingClientRect().height, 0, 'height after close');
+}, `Verifies the layout results of elements inside a closed <details> based on the usage of content-visibility:hidden.`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary-ref.html
new file mode 100644
index 0000000000..14f2be232f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<details>
+ <summary>new summary</summary>
+ details
+</details>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary.html
new file mode 100644
index 0000000000..1b0062e43a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel=match href="details-add-summary-ref.html">
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/pull/6466">
+
+<!-- This test makes sure that new <summary> elements get rendered correctly
+ when added to a <details> element. I ran into it when adding
+ content-visibility:hidden to the second slot of <details>. -->
+
+<script>
+onload = () => {
+ const newsummary = document.createElement('summary');
+ newsummary.textContent = 'new summary';
+ document.getElementById('detailsid').insertBefore(newsummary,
+ document.getElementById('oldsummary'));
+
+ document.documentElement.classList.remove('reftest-wait');
+};
+</script>
+
+<details id=detailsid>
+ <summary id=oldsummary>old summary</summary>
+ details
+</details>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-cq-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-cq-crash.html
new file mode 100644
index 0000000000..393e464c4c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-cq-crash.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1334983">
+
+<canvas>
+ <details>
+ <card card>
+
+<script>
+async function trigger() {
+ document.querySelector("canvas").style.setProperty("container-type", "size");
+ document.querySelector("canvas").style.setProperty("column-span", "all");
+ document.querySelector("card").setAttribute("contenteditable", "true");
+}
+onload = requestAnimationFrame(() => requestAnimationFrame(trigger));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-findstring-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-findstring-crash.html
new file mode 100644
index 0000000000..dc8686b216
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-findstring-crash.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1264507">
+
+<script>
+window.onload = () => {
+ window.getSelection().selectAllChildren(document.body);
+ document.querySelector('object').remove();
+ document.execCommand('FindString',false,0);
+};
+</script>
+
+<details>
+ <object id='id6'></object>
+</details>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-keyboard-activation.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-keyboard-activation.html
new file mode 100644
index 0000000000..a5534e24d1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-keyboard-activation.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Details activation with space bar</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=1726454">
+<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>
+:root {
+ scroll-behavior: instant;
+}
+.spacer {
+ height: 200vh;
+}
+</style>
+<details>
+ <summary>Activate me with the <kbd>Space</kbd> key</summary>
+ <p>Summary</p>
+</details>
+<div class="spacer"></div>
+<script>
+function tick() {
+ return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+}
+
+promise_test(async t => {
+ const details = document.querySelector("details");
+ const summary = details.querySelector("summary");
+
+ summary.focus();
+ assert_equals(document.activeElement, summary, "Summary should be focusable");
+ assert_false(details.open, "Details should be closed");
+
+ const oldScrollY = window.scrollY;
+ assert_equals(oldScrollY, 0, "Should be at top");
+
+ window.addEventListener("scroll", t.unreached_func("Unexpected scroll event"));
+
+ await test_driver.send_keys(summary, " ");
+
+ assert_true(details.open, "Space bar on summary should open details");
+ assert_equals(window.scrollY, oldScrollY, "Scroll position shouldn't change");
+
+ await tick();
+
+ assert_equals(window.scrollY, oldScrollY, "Scroll position shouldn't change");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details.html
new file mode 100644
index 0000000000..5ed14c53af
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<html>
+ <head>
+ <title>HTML details element API</title>
+ <style>#one, #two { visibility: hidden; }</style>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+
+ <!-- Used in parsing tests -->
+ <div id='one'><details></details><details></details></div>
+ <div id='two'><p><details></details></div>
+
+ <script type="text/javascript">
+
+function makeDetails () {
+ return document.createElement('details');
+}
+
+
+// <details>
+test(function () {
+ var times = document.getElementById('one').getElementsByTagName('details');
+ assert_equals( times.length, 2 );
+}, 'HTML parsing should locate 2 details elements in this document');
+
+test(function () {
+ assert_equals( document.getElementById('two').getElementsByTagName('p')[0].innerHTML, '' );
+}, 'HTML parsing should close an unclosed <p> before <details>');
+
+test(function () {
+ assert_true( !!window.HTMLDetailsElement );
+}, 'HTMLDetailsElement should be exposed for prototyping');
+
+test(function () {
+ assert_true( makeDetails() instanceof window.HTMLDetailsElement);
+}, 'a dynamically created details element should be instanceof HTMLDetailsElement');
+
+test(function () {
+ assert_true( document.getElementById('one').getElementsByTagName('details')[0] instanceof window.HTMLDetailsElement);
+}, 'a details element from the parser should be instanceof HTMLDetailsElement');
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html
new file mode 100644
index 0000000000..80812cccb5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=969619">
+<details open="open" style="display:table;"><rt></rt></details>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ test(()=> { }, "No crash");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/modified-details-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/modified-details-crash.html
new file mode 100644
index 0000000000..35ddca1fa6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/modified-details-crash.html
@@ -0,0 +1,31 @@
+<!doctype HTML>
+<link rel=author href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://crbug.com/1276488">
+
+<style>
+.first + .second {}
+</style>
+
+<script>
+const details = document.createElement('details');
+const ol = document.createElement('ol');
+const text = document.createTextNode('abcdefghijklmnopqrstuvxyz');
+
+function step3() {
+ ol.setAttribute('class', 'first');
+}
+
+function step2() {
+ details.appendChild(text);
+ requestAnimationFrame(step3);
+}
+
+function runTest() {
+ document.documentElement.appendChild(details);
+ details.appendChild(ol);
+
+ requestAnimationFrame(step2);
+}
+
+onload = runTest;
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/name-attribute.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/name-attribute.html
new file mode 100644
index 0000000000..2685546e9b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/name-attribute.html
@@ -0,0 +1,469 @@
+<!DOCTYPE HTML>
+<meta charset=UTF-8>
+<title>Test for the name attribute creating exclusive accordions from details elements</title>
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Google" href="http://www.google.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-details-element">
+<link rel="help" href="https://open-ui.org/components/accordion.explainer">
+<link rel="help" href="https://github.com/openui/open-ui/issues/725">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1444057">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="container">
+</div>
+
+<script>
+
+function assert_element_states(elements, expectations, description) {
+ assert_array_equals(elements.map(e => Number(e.open)), expectations, description);
+}
+
+let container = document.getElementById("container");
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details name="a">
+ <summary>1</summary>
+ This is the first item.
+ </details>
+
+ <details name="a">
+ <summary>2</summary>
+ This is the second item.
+ </details>
+ `;
+ let first = container.firstElementChild;
+ let second = first.nextElementSibling;
+ assert_false(first.open);
+ assert_false(second.open);
+ first.open = true;
+ assert_true(first.open);
+ assert_false(second.open);
+ second.open = true;
+ assert_false(first.open);
+ assert_true(second.open);
+ second.open = true;
+ assert_false(first.open);
+ assert_true(second.open);
+ second.open = false;
+ assert_false(first.open);
+ assert_false(second.open);
+}, "basic handling of mutually exclusive details");
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details name="a" open>
+ <summary>1</summary>
+ This is the first item.
+ </details>
+
+ <details name="a">
+ <summary>2</summary>
+ This is the second item.
+ </details>
+
+ <details name="a" open>
+ <summary>3</summary>
+ This is the third item.
+ </details>
+ `;
+ let first = container.firstElementChild;
+ let second = first.nextElementSibling;
+ let third = second.nextElementSibling;
+ function assert_states(expected_first, expected_second, expected_third, description) {
+ assert_array_equals([first.open, second.open, third.open], [expected_first, expected_second, expected_third], description);
+ }
+
+ assert_states(true, false, false, "initial states from open attribute");
+ first.open = true;
+ assert_states(true, false, false, "non-mutation doesn't change state");
+ second.open = true;
+ assert_states(false, true, false, "mutation closes multiple open elements");
+ third.setAttribute("open", "");
+ assert_states(false, false, true, "setAttribute closes other open element");
+}, "more complex handling of mutually exclusive details");
+
+promise_test(async t => {
+ let details_elements_string = `
+ <details name="a"></details>
+ <details name="a" open></details>
+ <details name="b"></details>
+ <details name="b"></details>
+ `;
+ container.innerHTML = `
+ ${details_elements_string}
+ <div id="shadow_host"></div>
+ `;
+ let shadow_root = document.getElementById("shadow_host").attachShadow({ mode: "open" });
+ shadow_root.innerHTML = details_elements_string;
+ let elements = Array.from(container.querySelectorAll("details")).concat(Array.from(shadow_root.querySelectorAll("details")));
+
+ assert_element_states(elements, [0, 1, 0, 0, 0, 1, 0, 0], "initial states from open attribute");
+ elements[4].open = true;
+ assert_element_states(elements, [0, 1, 0, 0, 1, 0, 0, 0], "after mutation in shadow tree");
+ for (let i = 0; i < 8; ++i) {
+ elements[i].open = true;
+ }
+ assert_element_states(elements, [0, 1, 0, 1, 0, 1, 0, 1], "after setting all elements open");
+ elements[0].open = true;
+ assert_element_states(elements, [1, 0, 0, 1, 0, 1, 0, 1], "after final mutation");
+}, "mutually exclusive details across multiple names and multiple tree scopes");
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details name="a" id="e0" open></details>
+ <details name="a" id="e1"></details>
+ <details name="a" id="e3" open></details>
+ `;
+ let e2 = document.createElement("details");
+ e2.id = "e2";
+ e2.name = "a";
+ e2.open = true;
+ let elements = [ document.getElementById("e0"),
+ document.getElementById("e1"),
+ e2,
+ document.getElementById("e3") ];
+ container.insertBefore(e2, elements[3]);
+
+ let mutation_event_received_ids = [];
+ let mutation_listener = event => {
+ assert_equals(event.type, "DOMSubtreeModified");
+ assert_equals(event.target.nodeType, Node.ELEMENT_NODE);
+ let element = event.target;
+ assert_equals(element.localName, "details");
+ mutation_event_received_ids.push(element.id);
+ };
+ let toggle_event_received_ids = [];
+ let toggle_event_promises = [];
+ for (let element of elements) {
+ element.addEventListener("DOMSubtreeModified", mutation_listener);
+ toggle_event_promises.push(new Promise((resolve, reject) => {
+ element.addEventListener("toggle", event => {
+ assert_equals(event.type, "toggle");
+ assert_equals(event.target, element);
+ toggle_event_received_ids.push(element.id);
+ resolve(undefined);
+ });
+ }));
+ }
+ assert_array_equals(mutation_event_received_ids, []);
+ assert_element_states(elements, [1, 0, 0, 0], "states before mutation");
+ elements[1].open = true;
+ if (mutation_event_received_ids.length == 0) {
+ // ok if mutation events are not supported
+ } else {
+ assert_array_equals(mutation_event_received_ids, ["e1"],
+ "mutation events received only for open attribute mutation and not for closing other element");
+ }
+ assert_element_states(elements, [0, 1, 0, 0], "states after mutation");
+ assert_array_equals(toggle_event_received_ids, [], "toggle events received before awaiting promises");
+ await Promise.all(toggle_event_promises);
+ assert_array_equals(toggle_event_received_ids, ["e3", "e2", "e1", "e0"], "toggle events received after awaiting promises, including toggle events from parser insertion");
+}, "mutation event and toggle event order");
+
+// This function is used to guard tests that test behavior that is
+// relevant only because of Mutation Events. If mutation events (for
+// attribute addition/removal) are removed from the web, the tests using
+// this function can be removed.
+function mutation_events_for_attribute_removal_supported() {
+ if (!("MutationEvent" in window)) {
+ return false;
+ }
+ container.innerHTML = `<div id="event-removal-test"></div>`;
+ let element = container.firstChild;
+ let event_fired = false;
+ element.addEventListener("DOMSubtreeModified", event => event_fired = true);
+ element.removeAttribute("id");
+ return event_fired;
+}
+
+promise_test(async t => {
+ if (!mutation_events_for_attribute_removal_supported()) {
+ return;
+ }
+ container.innerHTML = `
+ <details name="a" id="e0" open></details>
+ <details name="a" id="e1"></details>
+ <details name="a" id="e2" open></details>
+ `;
+ let elements = [ document.getElementById("e0"),
+ document.getElementById("e1"),
+ document.getElementById("e2") ];
+
+ let received_ids = [];
+ let listener = event => {
+ received_ids.push(event.target.id);
+ let i = 0;
+ for (let element of elements) {
+ element.setAttribute("name", `b${i++}`);
+ }
+ };
+ for (let element of elements) {
+ element.addEventListener("DOMSubtreeModified", listener);
+ }
+ assert_array_equals(received_ids, []);
+ assert_element_states(elements, [1, 0, 0], "states before mutation");
+ elements[1].open = true;
+ assert_array_equals(received_ids, ["e1"],
+ "mutation events received only for open attribute mutation and not for closing other element");
+ assert_element_states(elements, [0, 1, 0], "states after mutation");
+}, "interaction of open attribute changes with mutation events");
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details></details>
+ <details></details>
+ <details name></details>
+ <details name></details>
+ <details name=""></details>
+ <details name=""></details>
+ `;
+ let elements = Array.from(container.querySelectorAll("details"));
+
+ assert_element_states(elements, [0, 0, 0, 0, 0, 0], "initial states from open attribute");
+ for (let i = 0; i < 6; ++i) {
+ elements[i].open = true;
+ }
+ assert_element_states(elements, [1, 1, 1, 1, 1, 1], "after setting all elements open");
+}, "empty and missing name attributes do not create groups");
+
+const connected_scenarios = {
+ "connected": {
+ "create": data => container,
+ "cleanup": data => {},
+ },
+ "disconnected": {
+ "create": data => document.createElement("div"),
+ "cleanup": data => {},
+ },
+ "shadow": {
+ "create": data => {
+ let e = document.createElement("div");
+ container.appendChild(e);
+ data.wrapper = e;
+ let shadowRoot = e.attachShadow({ mode: "open" });
+ let d = document.createElement("div");
+ shadowRoot.appendChild(d);
+ return d;
+ },
+ "cleanup": data => { data.wrapper.remove(); },
+ },
+ "shadow-in-disconnected": {
+ "create": data => {
+ let e = document.createElement("div");
+ let shadowRoot = e.attachShadow({ mode: "open" });
+ let d = document.createElement("div");
+ shadowRoot.appendChild(d);
+ return d;
+ },
+ "cleanup": data => {},
+ },
+ "template-in-disconnected": {
+ "create": data => {
+ let e = document.createElement("div");
+ e.innerHTML = `
+ <template>
+ <div></div>
+ </template>
+ `;
+ return e.firstElementChild.content.firstElementChild;
+ },
+ "cleanup": data => {},
+ },
+ "connected-in-xhr-response": {
+ "create": data => new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "support/empty-html-document.html");
+ xhr.responseType = "document";
+ xhr.send();
+ xhr.addEventListener("load", event => { resolve(xhr.response.body); });
+ let reject_with_type =
+ event => { reject(`${event.type} event received`); }
+ xhr.addEventListener("error", reject_with_type);
+ xhr.addEventListener("abort", reject_with_type);
+ }),
+ "cleanup": data => {},
+ },
+ "connected-in-implementation-create-document": {
+ "create": data => {
+ let doc = document.implementation.createHTMLDocument("impl-created");
+ return doc.body;
+ },
+ "cleanup": data => {},
+ },
+ "connected-in-template": {
+ "create": data => {
+ container.innerHTML = `
+ <template>
+ <div></div>
+ </template>
+ `;
+ return container.firstElementChild.content.firstElementChild;
+ },
+ "cleanup": data => { container.innerHTML = ""; },
+ },
+};
+
+for (const [scenario, scenario_callbacks] of Object.entries(connected_scenarios)) {
+ promise_test(async t => {
+ let data = {};
+ let container = await scenario_callbacks.create(data);
+ t.add_cleanup(async () => await scenario_callbacks.cleanup(data));
+ assert_true(container instanceof HTMLDivElement ||
+ container instanceof HTMLBodyElement,
+ "error in test setup");
+
+ container.innerHTML = `
+ <details name="scenariotest" open></details>
+ <details name="scenariotest"></details>
+ `;
+
+ let elements = Array.from(container.querySelectorAll("details[name='scenariotest']"));
+ assert_element_states(elements, [1, 0], "state before toggle");
+ elements[1].open = true;
+ assert_element_states(elements, [0, 1], "state after toggle enforces exclusivity");
+ }, `exclusivity enforcement with attachment scenario ${scenario}`);
+}
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details name="a" id="e0" open></details>
+ <details name="a" id="e1"></details>
+ <details name="b" id="e2" open></details>
+ `;
+ let elements = [ document.getElementById("e0"),
+ document.getElementById("e1"),
+ document.getElementById("e2") ];
+
+ let mutation_received_ids = [];
+ let listener = event => {
+ mutation_received_ids.push(event.target.id);
+ };
+ for (let element of elements) {
+ element.addEventListener("DOMSubtreeModified", listener);
+ }
+
+ assert_element_states(elements, [1, 0, 1], "states before first mutation");
+ assert_array_equals(mutation_received_ids, [], "mutation events received before first mutation");
+ elements[2].name = "a";
+ assert_element_states(elements, [1, 0, 0], "states after first mutation");
+ if (mutation_received_ids.length != 0) {
+ // OK to not support mutation events, or to send DOMSubtreeModified
+ // only for attribute addition/removal (open) but not for attribute
+ // change (name)
+ assert_array_equals(mutation_received_ids, ["e2"], "mutation events received after first mutation");
+ }
+ elements[0].name = "c";
+ elements[2].open = true;
+ assert_element_states(elements, [1, 0, 1], "states before second mutation");
+ if (mutation_received_ids.length != 0) { // OK to not support mutation events
+ if (mutation_received_ids.length == 1) {
+ // OK to receive DOMSubtreeModified for attribute addition/removal
+ // (open) but not for attribute change (name)
+ assert_array_equals(mutation_received_ids, ["e2"], "mutation events received before second mutation");
+ } else {
+ assert_array_equals(mutation_received_ids, ["e2", "e0", "e2"], "mutation events received before second mutation");
+ }
+ }
+ elements[0].name = "a";
+ assert_element_states(elements, [0, 0, 1], "states after second mutation");
+ if (mutation_received_ids.length != 0) { // OK to not support mutation events
+ if (mutation_received_ids.length == 1) {
+ // OK to receive DOMSubtreeModified for attribute addition/removal
+ // (open) but not for attribute change (name)
+ assert_array_equals(mutation_received_ids, ["e2"], "mutation events received before second mutation");
+ } else {
+ assert_array_equals(mutation_received_ids, ["e2", "e0", "e2", "e0"], "mutation events received after second mutation");
+ }
+ }
+}, "handling of name attribute changes");
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details name="a" id="e0" open></details>
+ <details name="a" id="e1" open></details>
+ <details open name="a" id="e2"></details>
+ `;
+ let elements = [ document.getElementById("e0"),
+ document.getElementById("e1"),
+ document.getElementById("e2") ];
+
+ assert_element_states(elements, [1, 0, 0], "states after insertion by parser");
+}, "closing as a result of parsing doesn't depend on attribute order");
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details name="a" id="e0" open></details>
+ <details name="a" id="e1"></details>
+ `;
+ let elements = [ document.getElementById("e0"),
+ document.getElementById("e1") ];
+
+ assert_element_states(elements, [1, 0], "states before first mutation");
+
+ let make_details = () => {
+ let e = document.createElement("details");
+ e.setAttribute("name", "a");
+ return e;
+ };
+
+ let watch_e0 = new EventWatcher(t, elements[0], ['toggle']);
+ let watch_e1 = new EventWatcher(t, elements[1], ['toggle']);
+
+ let expect_opening = async (watcher) => {
+ await watcher.wait_for(['toggle'], {record: 'all'}).then((events) => {
+ assert_equals(events[0].oldState, "closed");
+ assert_equals(events[0].newState, "open");
+ });
+ };
+
+ let expect_closing = async (watcher) => {
+ await watcher.wait_for(['toggle'], {record: 'all'}).then((events) => {
+ assert_equals(events[0].oldState, "open");
+ assert_equals(events[0].newState, "closed");
+ });
+ };
+
+ let track_mutations = (element) => {
+ let result = { count: 0 };
+ let listener = event => {
+ ++result.count;
+ };
+ element.addEventListener("DOMSubtreeModified", listener);
+ return result;
+ }
+
+ await expect_opening(watch_e0);
+
+ // Test appending an open element in the group.
+ let new1 = make_details();
+ let mutations1 = track_mutations(new1);
+ let watch_new1 = new EventWatcher(t, new1, ['toggle']);
+ new1.open = true;
+ assert_in_array(mutations1.count, [0, 1], "mutation events count before inserting new1");
+ await expect_opening(watch_new1);
+ container.appendChild(new1);
+ await expect_closing(watch_new1);
+ assert_in_array(mutations1.count, [0, 1], "mutation events count after inserting new1");
+
+ // Test appending a closed element in the group.
+ let new2 = make_details();
+ let mutations2 = track_mutations(new2);
+ let watch_new2 = new EventWatcher(t, new2, ['toggle']);
+ container.appendChild(new2);
+ assert_equals(mutations2.count, 0, "mutation events count after inserting new2");
+
+ // Test inserting an open element at the start of the group.
+ let new3 = make_details();
+ let mutations3 = track_mutations(new3);
+ new3.open = true; // this time do this before creating the EventWatcher
+ let watch_new3 = new EventWatcher(t, new3, ['toggle']);
+ assert_in_array(mutations3.count, [0, 1], "mutation events count before inserting new3");
+ await expect_opening(watch_new3);
+ container.insertBefore(new3, elements[0]);
+ await expect_closing(watch_new3);
+ assert_in_array(mutations3.count, [0, 1], "mutation events count after inserting new3");
+}, "handling of insertion of elements into group");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-details-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-details-crash.html
new file mode 100644
index 0000000000..f3e821a950
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-details-crash.html
@@ -0,0 +1,26 @@
+<!doctype HTML>
+<link rel=author href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://crbug.com/1270206">
+
+<script type="text/javascript">
+function eventHandler1() {
+ document.getElementById('target').insertAdjacentText("afterEnd", "");
+ document.getElementById('target').focus();
+ document.getElementById('target').hidden = "true";
+}
+function operate() {
+ document.addEventListener('DOMNodeInsertedIntoDocument', eventHandler1, true);
+}
+function exec_event() {
+ event = new Event('DOMNodeInsertedIntoDocument')
+ document.dispatchEvent(event)
+}
+function go(){
+ operate();
+ exec_event();
+}
+</script>
+<body onload="go();" contentEditable="true">
+<details onselectstart='eventHandler2();'>
+<dfn id='target' class=onload='eventHandler1();'>
+<details id= onmsgesturehold='eventHandler2();'>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-top-layer-elements-in-details-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-top-layer-elements-in-details-crash.html
new file mode 100644
index 0000000000..c8d8ae4ed7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-top-layer-elements-in-details-crash.html
@@ -0,0 +1,17 @@
+<!doctype HTML>
+<link rel=author href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://crbug.com/1273395">
+
+<dialog id="parentElement">
+ <details id="childElement" open="true" ontoggle="toggleHandler()">
+ <dialog id="grandchildElement">
+ </dialog>
+ </details>
+</dialog>
+<script>
+function toggleHandler() {
+ grandchildElement.showModal();
+ parentElement.showModal();
+ childElement.open = false;
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/support/empty-html-document.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/support/empty-html-document.html
new file mode 100644
index 0000000000..56415b8476
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/support/empty-html-document.html
@@ -0,0 +1,2 @@
+<!DOCTYPE HTML>
+<title>Empty HTML Document</title>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/toggleEvent.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/toggleEvent.html
new file mode 100644
index 0000000000..c918f8eb62
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/toggleEvent.html
@@ -0,0 +1,183 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>The details 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-details-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<details id=details1>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details2>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details3 style="display:none;">
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details4>
+</details>
+<details id=details6>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details7>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details8>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<script>
+ window.details9TogglePromise = new Promise(resolve => {
+ window.details9TogglePromiseResolver = resolve;
+ });
+</script>
+<details id=details9 ontoggle="window.details9TogglePromiseResolver()" open>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details10>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<script>
+ var t1 = async_test("Adding open to 'details' should fire a toggle event at the 'details' element, with 'oldState: closed' and 'newState: open'"),
+ t2 = async_test("Adding open to 'details' and then removing open from that 'details' should fire only one toggle event at the 'details' element, with 'oldState: closed' and 'newState: closed'"),
+ t3 = async_test("Adding open to 'details' (display:none) should fire a toggle event at the 'details' element, with 'oldState: closed' and 'newState: open'"),
+ t4 = async_test("Adding open to 'details' (no children) should fire a toggle event at the 'details' element, with 'oldState: closed' and 'newState: open'"),
+ t6 = async_test("Adding open to 'details' and then removing open from that 'details' and then again adding open to that 'details' should fire only one toggle event at the 'details' element, with 'oldState: closed' and 'newState: closed'"),
+ t7 = async_test("Adding open to 'details' using setAttribute('open', '') should fire a toggle event at the 'details' element, with 'oldState: closed' and 'newState: open'"),
+ t8 = async_test("Adding open to 'details' and then calling removeAttribute('open') should fire only one toggle event at the 'details' element, with 'oldState: closed' and 'newState: closed'"),
+ t9 = async_test("Setting open=true on an opened 'details' element should not fire a toggle event at the 'details' element"),
+ t10 = async_test("Setting open=false on a closed 'details' element should not fire a toggle event at the 'details' element"),
+
+ details1 = document.getElementById('details1'),
+ details2 = document.getElementById('details2'),
+ details3 = document.getElementById('details3'),
+ details4 = document.getElementById('details4'),
+ details6 = document.getElementById('details6'),
+ details7 = document.getElementById('details7'),
+ details8 = document.getElementById('details8'),
+ details9 = document.getElementById('details9'),
+ details10 = document.getElementById('details10'),
+ loop=false;
+
+ function testEvent(evt) {
+ assert_true(evt.isTrusted, "event is trusted");
+ assert_false(evt.bubbles, "event doesn't bubble");
+ assert_false(evt.cancelable, "event is not cancelable");
+ assert_equals(Object.getPrototypeOf(evt), ToggleEvent.prototype, "Prototype of toggle event is ToggleEvent.prototype");
+ }
+
+ details1.ontoggle = t1.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "open");
+ assert_true(details1.open);
+ testEvent(evt)
+ });
+ details1.open = true; // opens details1
+
+ details2.ontoggle = t2.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "closed");
+ assert_false(details2.open);
+ testEvent(evt);
+ });
+ details2.open = true;
+ details2.open = false; // closes details2
+
+ details3.ontoggle = t3.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "open");
+ assert_true(details3.open);
+ testEvent(evt);
+ });
+ details3.open = true; // opens details3
+
+ details4.ontoggle = t4.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "open");
+ assert_true(details4.open);
+ testEvent(evt);
+ });
+ details4.open = true; // opens details4
+
+ async_test(function(t) {
+ var details5 = document.createElement("details");
+ details5.ontoggle = t.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "open");
+ assert_true(details5.open);
+ testEvent(evt);
+ })
+ details5.open = true;
+ }, "Adding open to 'details' (not in the document) should fire a toggle event at the 'details' element, with 'oldState: closed' and 'newState: open'");
+
+ details6.open = true;
+ details6.open = false;
+ details6.ontoggle = t6.step_func(function(evt) {
+ if (loop) {
+ assert_unreached("toggle event fired twice");
+ } else {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "closed");
+ loop = true;
+ }
+ });
+ t6.step_timeout(function() {
+ assert_true(loop);
+ t6.done();
+ }, 0);
+
+ details7.ontoggle = t7.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "open");
+ assert_true(details7.open);
+ testEvent(evt)
+ });
+ details7.setAttribute('open', ''); // opens details7
+
+ details8.ontoggle = t8.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "closed");
+ assert_false(details8.open);
+ testEvent(evt)
+ });
+ details8.open = true;
+ details8.removeAttribute('open'); // closes details8
+
+ window.details9TogglePromise.then(t9.step_func(() => {
+ // The toggle event should be fired once when declaring details9 with open
+ // attribute.
+ details9.ontoggle = t9.step_func(() => {
+ assert_unreached("toggle event fired twice on opened details element");
+ });
+ // setting open=true on details9 should not fire another event since it is
+ // already open.
+ details9.open = true;
+ t9.step_timeout(() => {
+ assert_true(details9.open);
+ t9.done();
+ });
+ }));
+
+ details10.ontoggle = t10.step_func_done(function(evt) {
+ assert_unreached("toggle event fired on closed details element");
+ });
+ details10.open = false; // closes details10
+ t10.step_timeout(function() {
+ assert_false(details10.open);
+ t10.done();
+ }, 0);
+
+ async_test(function(t) {
+ new DOMParser().parseFromString("<details open>", "text/html").querySelector("details").ontoggle = t.step_func_done(function(e) {
+ assert_true(e.target.open);
+ });
+ }, "Setting open from the parser fires a toggle event");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/WEB_FEATURES.yml b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/WEB_FEATURES.yml
new file mode 100644
index 0000000000..f270236cff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: dialog
+ files: "**"
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/abspos-dialog-layout.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/abspos-dialog-layout.html
new file mode 100644
index 0000000000..77ed29ce56
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/abspos-dialog-layout.html
@@ -0,0 +1,175 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+<meta name="viewport" content="user-scalable=no">
+<title>Tests layout of absolutely positioned modal dialogs.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+/* Remove body margin and dialog styles for easier positioning expected values */
+body {
+ height: 10000px;
+ margin: 0;
+}
+
+dialog {
+ border: 0;
+ padding: 0;
+ max-width: 100%;
+ max-height: 100%;
+}
+
+#absolute-div {
+ position: absolute;
+ top: 800px;
+ height: 50px;
+ width: 90%;
+}
+
+#relative-div {
+ position: relative;
+ top: 20px;
+ height: 30px;
+}
+</style>
+</head>
+<dialog >It is my dialog.</dialog>
+<div id="absolute-div">
+ <div id="relative-div"></div>
+</div>
+<script>
+"use strict";
+
+function checkVerticallyCentered(dialog) {
+ var centeredTop = (document.documentElement.clientHeight - dialog.offsetHeight) / 2;
+ // Using approx equals because getBoundingClientRect() and centeredTop
+ // are calculated by different parts of the engine. Due to the loss
+ // of precision, the numbers might not equal exactly.
+ assert_approx_equals(dialog.getBoundingClientRect().top, centeredTop, 1);
+}
+
+function reset() {
+ document.body.style.width = "auto";
+ dialog.style.top = null;
+ dialog.style.height = null;
+ if (dialog.open)
+ dialog.close();
+ dialog.remove();
+ document.body.appendChild(dialog);
+ window.scroll(0, 500);
+}
+
+var dialog = document.querySelector('dialog');
+var absoluteContainer = document.querySelector('#absolute-div');
+var relativeContainer = document.querySelector('#relative-div');
+reset();
+
+test(function() {
+ this.add_cleanup(reset);
+
+ dialog.showModal();
+ checkVerticallyCentered(dialog);
+}, "showModal() should center in the viewport");
+
+test(function() {
+ this.add_cleanup(reset);
+
+ dialog.showModal();
+ dialog.close();
+ window.scroll(0, 2 * window.scrollY);
+ dialog.showModal();
+ checkVerticallyCentered(dialog);
+}, "Dialog should be recentered if showModal() is called after close()");
+
+test(function() {
+ this.add_cleanup(reset);
+
+ dialog.showModal();
+ var expectedTop = dialog.getBoundingClientRect().top;
+ window.scroll(0, window.scrollY * 2);
+
+ // Trigger relayout
+ document.body.offsetHeight;
+
+ window.scroll(0, window.scrollY / 2);
+ assert_equals(dialog.getBoundingClientRect().top, expectedTop);
+}, "Dialog should not recenter on relayout.");
+
+test(function() {
+ this.add_cleanup(reset);
+
+ dialog.style.height = '20000px';
+ dialog.showModal();
+ assert_equals(dialog.getBoundingClientRect().top, 0);
+}, "A tall dialog should be positioned at the top of the viewport.");
+
+test(function() {
+ this.add_cleanup(reset);
+
+ document.body.style.width = '4000px';
+ dialog.showModal();
+ checkVerticallyCentered(dialog);
+
+}, "The dialog should be centered regardless of the presence of a horizontal scrollbar.");
+
+test(function() {
+ this.add_cleanup(reset);
+
+ dialog.remove();
+ absoluteContainer.appendChild(dialog);
+ dialog.showModal();
+ checkVerticallyCentered(dialog);
+ dialog.close();
+
+ dialog.remove();
+ relativeContainer.appendChild(dialog);
+ dialog.showModal();
+ checkVerticallyCentered(dialog);
+}, "Centering should work when dialog is inside positioned containers.");
+
+test(function() {
+ this.add_cleanup(reset);
+
+ dialog.showModal();
+ var expectedTop = dialog.getBoundingClientRect().top;
+ relativeContainer.style.display = 'none';
+ relativeContainer.style.display = 'block';
+ assert_equals(dialog.getBoundingClientRect().top, expectedTop);
+}, "A centered dialog's position should survive becoming display:none temporarily.");
+
+test(function() {
+ this.add_cleanup(reset);
+
+ // Remove and reinsert so that the document position isn't changed by the second remove and reinsert
+ dialog.remove();
+ relativeContainer.appendChild(dialog);
+
+ dialog.showModal();
+ checkVerticallyCentered(dialog);
+ dialog.remove();
+
+ relativeContainer.appendChild(dialog);
+ assert_equals(relativeContainer.getBoundingClientRect().top, dialog.getBoundingClientRect().top);
+}, "Dialog should not still be centered when removed, and re-added to the document.");
+
+test(function() {
+ this.add_cleanup(reset);
+
+ dialog.showModal();
+ dialog.style.top = '0px';
+ var expectedTop = dialog.getBoundingClientRect().top;
+ dialog.close();
+ dialog.showModal();
+ assert_equals(dialog.getBoundingClientRect().top, expectedTop);
+}, "Dialog's specified position should survive after close() and showModal().");
+
+test(function() {
+ this.add_cleanup(reset);
+
+ dialog.showModal();
+ dialog.removeAttribute('open');
+ dialog.showModal();
+ checkVerticallyCentered(dialog);
+}, "Dialog should be recentered if showModal() is called after removing 'open'.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-descendant-selector-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-descendant-selector-ref.html
new file mode 100644
index 0000000000..70d84c21ff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-descendant-selector-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+.backdrop {
+ position: absolute;
+ height: 100px;
+ width: 100px;
+ background: green;
+}
+</style>
+</head>
+<body>
+Test ::backdrop used in descendant selectors. The test passes if there are two green boxes and no red.
+<div class="backdrop" style="top: 100px; left: 100px"></div>
+<div class="backdrop" style="top: 100px; left: 300px"></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-descendant-selector.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-descendant-selector.html
new file mode 100644
index 0000000000..73706548ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-descendant-selector.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="match" href="backdrop-descendant-selector-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ visibility: hidden;
+}
+
+::backdrop {
+ visibility: visible;
+ position: absolute;
+ height: 100px;
+ width: 100px;
+ background: red;
+}
+
+/* This shouldn't be matched, dialog is not the parent of ::backdrop.
+ * It is given high specificity so we actually test something.
+ */
+#dialog-parent > #dialog > ::backdrop,
+#dialog-parent > #dialog ::backdrop {
+ background: red;
+}
+
+#dialog-parent > ::backdrop {
+ top: 100px;
+ left: 100px;
+ background: green;
+}
+
+#backdrop-ancestor ::backdrop {
+ top: 100px;
+ left: 300px;
+ background: green;
+}
+</style>
+</head>
+<body>
+Test ::backdrop used in descendant selectors. The test passes if there are two green boxes and no red.
+
+<div id="dialog-parent">
+ <dialog id="dialog"></dialog>
+</div>
+<div id="backdrop-ancestor">
+ <p><span><dialog></dialog></span></p>
+</div>
+<script>
+var dialogs = document.querySelectorAll('dialog');
+for (var i = 0; i < dialogs.length; ++i)
+ dialogs[i].showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-display-none-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-display-none-ref.html
new file mode 100644
index 0000000000..c49a11d416
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-display-none-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<title>Reference: Test that adding display: none; dynamically on ::backdrop makes it disappear</title>
+<meta charset="utf-8">
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<p>Test passes if there is no red.</p>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-display-none.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-display-none.html
new file mode 100644
index 0000000000..bcf100b368
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-display-none.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<title>Test that adding display: none; dynamically on ::backdrop makes it disappear</title>
+<meta charset="utf-8">
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="match" href="backdrop-dynamic-display-none-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<p>Test passes if there is no red.</p>
+<dialog></dialog>
+<style>
+dialog { visibility: hidden; }
+::backdrop { background-color: red; }
+.hidden-backdrop::backdrop {
+ display: none;
+}
+</style>
+<script>
+dialog = document.querySelector("dialog");
+dialog.showModal();
+requestAnimationFrame(() => {
+ dialog.classList.add("hidden-backdrop");
+});
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-style-change-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-style-change-ref.html
new file mode 100644
index 0000000000..01cb93d2ab
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-style-change-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+.backdrop {
+ position: absolute;
+ top: 100px;
+ left: 100px;
+ height: 100px;
+ width: 100px;
+ background-color: green;
+}
+</style>
+</head>
+<body>
+Test dynamic changes to ::backdrop style. The test passes if there is a green box below.
+<div class="backdrop"></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-style-change.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-style-change.html
new file mode 100644
index 0000000000..6c609a769c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-dynamic-style-change.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="match" href="backdrop-dynamic-style-change-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ visibility: hidden;
+}
+
+::backdrop {
+ visibility: visible;
+ position: absolute;
+ top: 100px;
+ left: 100px;
+ height: 100px;
+ width: 100px;
+ background-color: red;
+}
+
+.green::backdrop {
+ background-color: green;
+}
+</style>
+</head>
+<body>
+Test dynamic changes to ::backdrop style. The test passes if there is a green box below.
+<dialog></dialog>
+<script>
+dialog = document.querySelector('dialog');
+dialog.showModal();
+dialog.classList.add('green');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-in-flow-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-in-flow-ref.html
new file mode 100644
index 0000000000..4857557bf8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-in-flow-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<style>
+#backdrop {
+ position: absolute;
+ top: 100px;
+ left: 100px;
+ height: 100px;
+ width: 100px;
+ background: green;
+}
+</style>
+<body>
+Test that position 'static' or 'relative' for ::backdrop computes to 'absolute'.
+The test passes if there is a single green box.
+<div id="backdrop"></div>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-in-flow.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-in-flow.html
new file mode 100644
index 0000000000..06f790979e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-in-flow.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<link rel="match" href="backdrop-in-flow-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ visibility: hidden;
+}
+
+dialog::backdrop {
+ visibility: visible;
+ height: 100px;
+ width: 50px;
+}
+
+#left::backdrop {
+ position: static;
+ top: 100px;
+ left: 100px;
+ background: green;
+}
+
+#right::backdrop {
+ position: relative;
+ background: green;
+ top: 100px;
+ left: 150px;
+}
+</style>
+<body>
+Test that position 'static' or 'relative' for ::backdrop computes to 'absolute'.
+The test passes if there is a single green box.
+<dialog id="left"></dialog>
+<dialog id="right"></dialog>
+</div>
+<script>
+document.querySelector('#left').showModal();
+document.querySelector('#right').showModal();
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-inherits-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-inherits-ref.html
new file mode 100644
index 0000000000..d9f4cb2c84
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-inherits-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<style>
+#backdrop {
+ position: absolute;
+ top: 100px;
+ left: 100px;
+ height: 100px;
+ width: 100px;
+ background: green;
+}
+</style>
+<body>
+Test that ::backdrop inherits from its originating element. The test passes if
+there is a green box and no red.
+<div id="backdrop"></div>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-inherits.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-inherits.html
new file mode 100644
index 0000000000..458320f019
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-inherits.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<link rel="match" href="backdrop-inherits-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-position-4/#backdrop">
+<style>
+dialog {
+ --backdrop-bg: green;
+ visibility: hidden;
+}
+
+dialog::backdrop {
+ position: absolute;
+ top: 100px;
+ left: 100px;
+ height: 100px;
+ width: 100px;
+ visibility: visible;
+ background-color: var(--backdrop-bg);
+}
+</style>
+Test that ::backdrop inherits from its originating element. The test passes if
+there is a green box and no red.
+<dialog></dialog>
+<script>
+document.querySelector('dialog').showModal();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-receives-element-events.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-receives-element-events.html
new file mode 100644
index 0000000000..5d515000ce
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-receives-element-events.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<title>Test that ::backdrop receives events for the associated element</title>
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<body>
+<style>
+/* ::backdrop takes up whole screen, actual <dialog> is hidden */
+dialog {
+ visibility: hidden;
+ pointer-events: none;
+}
+
+dialog::backdrop {
+ visibility: visible;
+ pointer-events: initial;
+ background-color: red;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
+
+dialog.clicked::backdrop {
+ background-color: green;
+}
+</style>
+<dialog></dialog>
+<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>
+<script>
+setup({ single_test: true });
+
+const dialog = document.querySelector("dialog");
+dialog.showModal();
+dialog.addEventListener("click", () => {
+ // Change style for debugging purposes, done() actually makes the test pass
+ dialog.className = "clicked";
+ done();
+});
+new test_driver.Actions()
+ .pointerMove(0, 0, {origin: "viewport"})
+ .pointerDown()
+ .pointerUp()
+ .send();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-stacking-order-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-stacking-order-ref.html
new file mode 100644
index 0000000000..d3f82de181
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-stacking-order-ref.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<style>
+div {
+ position: absolute;
+}
+
+#bottom-backdrop {
+ top: 100px;
+ left: 100px;
+ height: 300px;
+ width: 300px;
+ background-color: rgb(0, 50, 0);
+}
+
+#bottom {
+ top: 125px;
+ left: 125px;
+ height: 250px;
+ width: 250px;
+ background-color: rgb(0, 90, 0);
+}
+
+#middle-backdrop {
+ top: 150px;
+ left: 150px;
+ height: 200px;
+ width: 200px;
+ background-color: rgb(0, 130, 0);
+}
+
+#middle {
+ top: 175px;
+ left: 175px;
+ height: 150px;
+ width: 150px;
+ background-color: rgb(0, 170, 0);
+}
+
+#top-backdrop {
+ top: 200px;
+ left: 200px;
+ height: 100px;
+ width: 100px;
+ background-color: rgb(0, 210, 0);
+}
+
+#top {
+ top: 225px;
+ left: 225px;
+ height: 50px;
+ width: 50px;
+ background-color: rgb(0, 255, 0);
+}
+</style>
+<body>
+Test for dialog::backdrop stacking order. The test passes if there are 6
+boxes enclosed in each other, becoming increasingly smaller and brighter
+green.
+<div id="bottom-backdrop"></div>
+<div id="bottom"></div>
+<div id="middle-backdrop"></div>
+<div id="middle"></div>
+<div id="top-backdrop"></div>
+<div id="top"></div>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-stacking-order.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-stacking-order.html
new file mode 100644
index 0000000000..897f54a53f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/backdrop-stacking-order.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<link rel="match" href="backdrop-stacking-order-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ padding: 0px;
+ border: none;
+ margin: 0px;
+ outline: none;
+}
+
+#bottom::backdrop {
+ top: 100px;
+ left: 100px;
+ height: 300px;
+ width: 300px;
+ background-color: rgb(0, 50, 0);
+ z-index: 100; /* z-index has no effect. */
+}
+
+#bottom {
+ top: 125px;
+ left: 125px;
+ height: 250px;
+ width: 250px;
+ background-color: rgb(0, 90, 0);
+}
+
+#middle::backdrop {
+ top: 150px;
+ left: 150px;
+ height: 200px;
+ width: 200px;
+ background-color: rgb(0, 130, 0);
+ z-index: -100; /* z-index has no effect. */
+}
+
+#middle {
+ top: 175px;
+ left: 175px;
+ height: 150px;
+ width: 150px;
+ background-color: rgb(0, 170, 0);
+}
+
+#top::backdrop {
+ top: 200px;
+ left: 200px;
+ height: 100px;
+ width: 100px;
+ background-color: rgb(0, 210, 0);
+ z-index: 0; /* z-index has no effect. */
+}
+
+#top {
+ top: 225px;
+ left: 225px;
+ height: 50px;
+ width: 50px;
+ background-color: rgb(0, 255, 0);
+ z-index: -1000; /* z-index has no effect. */
+}
+</style>
+<body>
+Test for dialog::backdrop stacking order. The test passes if there are 6
+boxes enclosed in each other, becoming increasingly smaller and brighter
+green.
+<dialog id="top"></dialog>
+<dialog id="middle"></dialog>
+<dialog id="bottom"></dialog>
+<script>
+var topDialog = document.getElementById('top');
+var middleDialog = document.getElementById('middle');
+var bottomDialog = document.getElementById('bottom');
+topDialog.showModal();
+bottomDialog.showModal();
+topDialog.close(); // Just to shuffle the top layer order around a little.
+middleDialog.showModal();
+topDialog.showModal();
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/centering-iframe.sub.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/centering-iframe.sub.html
new file mode 100644
index 0000000000..6ffd72296d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/centering-iframe.sub.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>dialog element centered frame</title>
+<style>
+ html {
+ writing-mode: {{GET[html-writing-mode]}}
+ }
+
+ #container {
+ writing-mode: {{GET[container-writing-mode]}}
+ }
+
+ dialog {
+ writing-mode: {{GET[dialog-writing-mode]}};
+ border: none;
+ padding: 0;
+ max-width: initial;
+ max-height: initial;
+ width: {{GET[dialog-width]}};
+ height: {{GET[dialog-height]}};
+ }
+</style>
+
+<div id="container">
+ <dialog>X</dialog>
+</div>
+
+<script>
+"use strict";
+document.querySelector("dialog").showModal();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/centering.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/centering.html
new file mode 100644
index 0000000000..2dc6ce3edf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/centering.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>dialog element: centered alignment</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/#flow-content-3">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+
+<script>
+"use strict";
+
+// Be sure to sync with centering-iframe.html
+const dialogWidth = 20;
+const dialogHeight = 10;
+
+testDialogCentering("horizontal-tb", "", "", "tall viewport", 40, 100);
+testDialogCentering("horizontal-tb", "", "", "wide viewport", 100, 40);
+testDialogCentering("horizontal-tb", "", "", "square viewport", 100, 100);
+testDialogCentering("horizontal-tb", "", "", "dialog and viewport match", dialogWidth, dialogHeight);
+
+testDialogCentering("vertical-rl", "", "", "tall viewport", 40, 100);
+testDialogCentering("vertical-lr", "", "", "tall viewport", 40, 100);
+
+testDialogCentering("vertical-rl", "", "horizontal-tb", "tall viewport", 40, 100);
+testDialogCentering("vertical-lr", "", "horizontal-tb", "tall viewport", 40, 100);
+
+testDialogCentering("horizontal-tb", "vertical-rl", "", "tall viewport", 40, 100);
+testDialogCentering("vertical-rl", "horizontal-tb", "", "tall viewport", 40, 100);
+
+testDialogCentering("horizontal-tb", "vertical-rl", "horizontal-tb", "tall viewport", 40, 100);
+testDialogCentering("vertical-rl", "horizontal-tb", "vertical-rl", "tall viewport", 40, 100);
+
+function testDialogCentering(writingMode, containerWritingMode, dialogWritingMode, label, iframeWidth, iframeHeight) {
+ const dialogSizesToTest = [["", ""], [dialogWidth.toString()+'px', dialogHeight.toString()+'px']];
+ for (const dialogSizes of dialogSizesToTest) {
+ const isDefaultSize = dialogSizes[0] == "";
+ // This test doesn't make sense if the dialog sizes are default
+ if (isDefaultSize && label == "dialog and viewport match") {
+ continue;
+ }
+
+ async_test(t => {
+ const iframe = document.createElement("iframe");
+ iframe.src = `centering-iframe.sub.html?html-writing-mode=${writingMode}&container-writing-mode=${containerWritingMode}&dialog-writing-mode=${dialogWritingMode}&dialog-width=${dialogSizes[0]}&dialog-height=${dialogSizes[1]}`;
+ iframe.width = iframeWidth;
+ iframe.height = iframeHeight;
+ iframe.onload = t.step_func_done(() => {
+ const dialog = iframe.contentDocument.querySelector("dialog");
+ const dialogRect = dialog.getBoundingClientRect();
+
+ const expectedLeftOffset = iframeWidth / 2 - dialogRect.width / 2;
+ const expectedTopOffset = Math.max(iframeHeight / 2 - dialogRect.height / 2, 0);
+
+ if (isDefaultSize) {
+ assert_approx_equals(dialogRect.left, expectedLeftOffset, 1/60);
+ assert_approx_equals(dialogRect.top, expectedTopOffset, 1/60);
+ } else {
+ assert_equals(dialogRect.left, expectedLeftOffset);
+ assert_equals(dialogRect.top, expectedTopOffset);
+ }
+ });
+ document.body.appendChild(iframe);
+ }, writingMode + (containerWritingMode ? ` (container ${containerWritingMode})` : "") +
+ (dialogWritingMode ? ` (dialog ${dialogWritingMode})` : "") + `: ${label}` + `, default-sizes: ${isDefaultSize}`);
+
+ }
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/child-sequential-focus.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/child-sequential-focus.html
new file mode 100644
index 0000000000..bc787202cf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/child-sequential-focus.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/pull/8199">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<dialog autofocus id=autofocusdialog data-description="dialog element with autofocus should get initial focus." class=target>
+ <button>focusable button</button>
+ <button autofocus>autofocusable button</button>
+</dialog>
+
+<dialog id=keyboardfocusdialog data-description="Only keyboard-focusable elements should get dialog initial focus.">
+ <button tabindex="-1">mouse focusable button</button>
+ <button class=target>keyboard focusable button</button>
+</dialog>
+
+<dialog id=autofocuswithoutkeyboarddialog data-description="Autofocus takes precedence over keyboard-focusable requirement.">
+ <button>keyboard focusable button</button>
+ <button tabindex="-1" autofocus class=target>mouse focusable autofocus button</button>
+</dialog>
+
+<dialog id=subtree data-description="Only keyboard-focusable elements should get dialog initial focus including in subtrees.">
+ <div>
+ <button tabindex="-1">mouse focusable button</button>
+ <button class=target>keyboard focusable button</button>
+ </div>
+</dialog>
+
+<dialog id=nestedbuttons data-description="Only keyboard-focusable elements should get dialog initial focus including in nested buttons.">
+ <button tabindex="-1">
+ <span>mouse focusable button</span>
+ <button tabindex="-1">nested mouse focusable button</button>
+ </button>
+ <button class=target>keyboard focusable button</button>
+</dialog>
+
+<script>
+document.querySelectorAll('dialog').forEach(dialog => {
+ test(t => {
+ let target = dialog.querySelector('.target');
+ if (dialog.classList.contains('target')) {
+ target = dialog;
+ }
+ t.add_cleanup(() => {
+ if (dialog.open)
+ dialog.close();
+ });
+
+ dialog.showModal();
+ assert_equals(document.activeElement, target,
+ 'showModal: the target element did not receive initial focus.');
+ dialog.close();
+
+ dialog.show();
+ assert_equals(document.activeElement, target,
+ 'show: the target element did not receive initial focus.');
+ dialog.close();
+ }, dialog.dataset.description);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/closed-dialog-does-not-block-mouse-events.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/closed-dialog-does-not-block-mouse-events.html
new file mode 100644
index 0000000000..9d9856962d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/closed-dialog-does-not-block-mouse-events.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.webkit.org/show_bug.cgi?id=110952">
+<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>
+#div {
+ height: 100px;
+ width: 100px;
+ background: red;
+}
+</style>
+<div id=div></div>
+<dialog id="dialog"></dialog>
+<dialog></dialog>
+
+<script>
+promise_test(async () => {
+ const dialog = document.getElementById('dialog');
+ dialog.showModal();
+ dialog.close();
+
+ const div = document.getElementById('div');
+ div.addEventListener('click', function(event) {
+ div.firedOn = true;
+ div.style.backgroundColor = 'green';
+ });
+
+ var absoluteTop = 0;
+ var absoluteLeft = 0;
+ for (var parentNode = div; parentNode; parentNode = parentNode.offsetParent) {
+ absoluteLeft += parentNode.offsetLeft;
+ absoluteTop += parentNode.offsetTop;
+ }
+
+ const x = absoluteLeft + div.offsetWidth / 2;
+ const y = absoluteTop + div.offsetHeight / 2;
+ const actions = new test_driver.Actions()
+ .pointerMove(x, y)
+ .pointerDown()
+ .pointerUp()
+ .pointerMove(0, 0);
+ await actions.send();
+ assert_true(div.firedOn, 'div should have gotten a click event.');
+}, 'Ensure that closed dialogs do not block mouse events. To test manually, click the red box. The test succeeds if the red box turns green.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/default-color.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/default-color.html
new file mode 100644
index 0000000000..5a6e6b21fb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/default-color.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for dialog element colors</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+:root { background-color: Canvas; color: CanvasText; }
+#light { color-scheme: light }
+#dark { color-scheme: dark }
+</style>
+<dialog id="dialog" open>
+ This is a dialog
+</dialog>
+<dialog id="light" open>
+ This is a dialog
+</dialog>
+<dialog id="dark" open>
+ This is a dialog
+</dialog>
+<script>
+test(function() {
+ let dialog = document.getElementById("dialog");
+ let cs = getComputedStyle(dialog);
+ let rootCs = getComputedStyle(document.documentElement);
+ assert_equals(cs.color, rootCs.color, "Dialog color should match CanvasText");
+ assert_equals(cs.backgroundColor, rootCs.backgroundColor, "Dialog background should match Canvas");
+}, "<dialog> color and background match default")
+
+test(function() {
+ let lightCs = getComputedStyle(document.getElementById("light"));
+ let darkCs = getComputedStyle(document.getElementById("dark"));
+ assert_not_equals(lightCs.color, darkCs.color, "Dialog color should react to color-scheme");
+ assert_not_equals(lightCs.backgroundColor, darkCs.backgroundColor, "Dialog background should react to color-scheme");
+}, "<dialog> colors react to dark mode")
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-audio-video-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-audio-video-crash.html
new file mode 100644
index 0000000000..c8c1ab2826
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-audio-video-crash.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<link rel="help" href="https://crbug.com/1206122">
+
+<body onload=dlg.show()>
+<dialog id="dlg">
+ <audio></audio>
+ <video></video>
+</dialog>
+
+This test passes if it does not crash.
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-autofocus-just-once.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-autofocus-just-once.html
new file mode 100644
index 0000000000..894efd59dc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-autofocus-just-once.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/interaction/focus/the-autofocus-attribute/resources/utils.js"></script>
+<body>
+<dialog>
+<input>
+<input autofocus>
+</dialog>
+<script>
+// https://github.com/whatwg/html/issues/4788
+promise_test(async () => {
+ const dialog = document.querySelector('dialog');
+ dialog.show();
+ assert_equals(document.activeElement, dialog.querySelector('[autofocus]'),
+ 'dialog.show() should set focus on a descendant element with an ' +
+ 'autofocus attribute.');
+ document.activeElement.blur();
+ await waitUntilStableAutofocusState();
+ assert_equals(document.activeElement, document.body,
+ 'Non-dialog autofocus processing should be skipped.');
+}, 'An autofocus element in a dialog element should not try to get focus twice.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-autofocus-multiple-times.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-autofocus-multiple-times.html
new file mode 100644
index 0000000000..ff9ebd7d28
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-autofocus-multiple-times.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/common.js"></script>
+<script>
+promise_test(() => {
+ return waitUntilLoadedAndAutofocused().then(() => {
+ assert_equals(document.activeElement, document.getElementById("outer-button"));
+
+ var focusCount = 0;
+ var dlg = document.getElementById("dlg");
+ var input1 = document.getElementById("input1");
+ var input2 = document.getElementById("input2");
+ input2.onfocus = function() { focusCount += 1 };
+
+ var expectedFocusCount = 3;
+ for (i = 0; i < expectedFocusCount; i++) {
+ dlg.show();
+ assert_equals(document.activeElement, input2);
+ input1.focus();
+ assert_equals(document.activeElement,input1);
+ dlg.close();
+ }
+
+ assert_equals(focusCount.toString(), expectedFocusCount.toString());
+ });
+}, "autofocus is run every time a dialog is opened");
+</script>
+</head>
+<body>
+<button id="outer-button" autofocus></button>
+<dialog id="dlg">
+ <!-- Unfocusable elements with [autofocus] should be ignored. -->
+ <input autofocus disabled>
+ <textarea autofocus hidden></textarea>
+ <input id="input1"></input>
+ <input id="input2" autofocus></input>
+</dialog>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-autofocus.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-autofocus.html
new file mode 100644
index 0000000000..149a53eacf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-autofocus.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/common.js"></script>
+<script>
+promise_test(() => {
+ return waitUntilLoadedAndAutofocused().then(() => {
+ assert_equals(document.activeElement, document.getElementById("outer-button"));
+
+ var dialog = document.getElementById('dialog');
+ dialog.showModal();
+
+ autofocusButton = document.getElementById('autofocus-button');
+ assert_equals(document.activeElement, autofocusButton);
+
+ anotherButton = document.getElementById('another-button');
+ anotherButton.focus();
+ assert_equals(document.activeElement, anotherButton);
+
+ // Test that recreating layout does not give focus back to a previously autofocused element.
+ autofocusButton.style.display = 'none';
+ document.body.offsetHeight;
+ autofocusButton.style.display = 'block';
+ document.body.offsetHeight;
+ assert_equals(document.activeElement, anotherButton);
+
+ // Test that reinserting does not give focus back to a previously autofocused element.
+ var parentNode = autofocusButton.parentNode;
+ parentNode.removeChild(autofocusButton);
+ document.body.offsetHeight;
+ parentNode.appendChild(autofocusButton);
+ document.body.offsetHeight;
+ assert_equals(document.activeElement, anotherButton);
+
+ dialog.close();
+ // Test that dialog focusing steps run when a dialog is reopened.
+ dialog.showModal();
+ assert_equals(document.activeElement, autofocusButton);
+ dialog.close();
+ });
+}, "autofocus when a modal dialog is opened");
+</script>
+</head>
+<body>
+<button id="outer-button" autofocus></button>
+<dialog id="dialog">
+ <button></button>
+ <!-- Unfocusable elements with [autofocus] should be ignored. -->
+ <input autofocus disabled>
+ <textarea autofocus hidden></textarea>
+ <dialog>
+ <button autofocus></button>
+ </dialog>
+ <div>
+ <span>
+ <button id="autofocus-button" autofocus></button>
+ </span>
+ </div>
+ <button id="another-button" autofocus></button>
+</dialog>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-events.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-events.html
new file mode 100644
index 0000000000..d583c4cd23
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-events.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test cancel event is fired when the dialog is closed by user close requests</title>
+<link rel="help" href="https://bugs.webkit.org/show_bug.cgi?id=227534">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322947">
+<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="/common/top-layer.js"></script>
+<script src="/close-watcher/resources/helpers.js"></script>
+
+<dialog>
+ <p>Hello World</p>
+</dialog>
+
+<script type="module">
+setup({ single_test: true });
+
+const dialog = document.querySelector("dialog");
+const events = [];
+
+dialog.addEventListener("cancel", event => {
+ assert_true(event.cancelable, "cancel event should be cancelable");
+ assert_array_equals(events, []);
+
+ events.push("addEventListener cancel");
+});
+
+assert_equals(dialog.oncancel, null);
+dialog.oncancel = () => {
+ assert_array_equals(events, ["addEventListener cancel"]);
+
+ events.push("oncancel");
+};
+
+dialog.addEventListener("close", () => {
+ assert_array_equals(events, ["addEventListener cancel", "oncancel"]);
+
+ events.push("addEventListener close");
+});
+
+assert_equals(dialog.onclose, null);
+dialog.onclose = () => {
+ assert_array_equals(events, ["addEventListener cancel", "oncancel", "addEventListener close"]);
+
+ done();
+};
+
+dialog.showModal();
+await blessTopLayer(dialog);
+await sendCloseRequest();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-preventDefault.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-preventDefault.html
new file mode 100644
index 0000000000..4daffc09a4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-preventDefault.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test cancel event with preventDefault on cancel event for dialog element</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>
+ <script src="/common/top-layer.js"></script>
+ <script src="/close-watcher/resources/helpers.js"></script>
+ <link rel="help" href="https://bugs.webkit.org/show_bug.cgi?id=227534">
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322947">
+</head>
+<body>
+<p>Test cancel event with preventDefault on cancel event for dialog element</p>
+<dialog>
+ <p>Hello World</p>
+</dialog>
+<script type=module>
+ setup({ single_test: true });
+
+ var hasCancelEventFired = false;
+
+ const dialog = document.querySelector("dialog");
+
+ const verify = () => {
+ assert_true(hasCancelEventFired, "cancel is fired");
+ done();
+ };
+
+ dialog.addEventListener("cancel", function(event) {
+ hasCancelEventFired = true;
+ event.preventDefault();
+ step_timeout(function() {
+ verify();
+ }, 0)
+ });
+
+ dialog.addEventListener("close", function() {
+ assert_true(false, "close event should not be fired");
+ });
+
+ dialog.showModal();
+
+ await blessTopLayer(dialog);
+ await sendCloseRequest();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-with-input.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-with-input.html
new file mode 100644
index 0000000000..153d434317
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-with-input.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test dialog modal is closed by escape key with input focused</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>
+ <link rel="help" href="https://bugs.webkit.org/show_bug.cgi?id=227534">
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322947">
+</head>
+<body>
+<p>Test dialog modal is closed by escape key with input focused</p>
+<dialog id="dialog">
+ <p>Hello World</p>
+</dialog>
+
+<dialog id="dialogWithAutofocus">
+ <input autofocus/>
+</dialog>
+
+<script>
+ setup({ single_test: true });
+
+ const triggerEscKey = () => {
+ test_driver.send_keys(document.documentElement, "\uE00C"); // ESC key
+ };
+
+ /* Make sure we still cancel the dialog even if the input element is focused */
+ function runTestCancelWhenInputFocused() {
+ const dialog = document.getElementById("dialogWithAutofocus");
+ const input = document.querySelector("input");
+
+ dialog.addEventListener("close", function() {
+ assert_false(dialog.open, "dialog with input autofocused is closed");
+ done();
+ });
+ dialog.showModal();
+ assert_true(input == document.activeElement, "input element should be focused");
+
+ triggerEscKey();
+ }
+
+ const dialog = document.getElementById("dialog");
+
+ dialog.addEventListener("close", function() {
+ assert_false(dialog.open, "dialog closed");
+ step_timeout(function() {
+ runTestCancelWhenInputFocused();
+ }, 0);
+ });
+
+ dialog.showModal();
+ triggerEscKey();
+</script>
+</pre>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-with-select.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-with-select.html
new file mode 100644
index 0000000000..178d5a2711
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-cancel-with-select.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test dialog modal is closed by escape key with select focused</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>
+ <link rel="help" href="https://bugs.webkit.org/show_bug.cgi?id=227534">
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322947">
+</head>
+<body>
+<p>Test dialog modal is closed by escape key with select focused</p>
+<dialog id="dialog">
+ <select>
+ <option value="one">one</option>
+ <option value="two">two</option>
+ </select>
+</dialog>
+
+<script>
+ setup({ single_test: true });
+
+ const dialog = document.getElementById("dialog");
+ const select = document.querySelector("select");
+
+ dialog.addEventListener("close", function() {
+ assert_false(dialog.open, "dialog with select is closed");
+ done();
+ });
+ dialog.showModal();
+ assert_true(select == document.activeElement, "select element should be focused");
+
+ test_driver.send_keys(document.documentElement, "\uE00C"); // ESC key
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-canceling.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-canceling.html
new file mode 100644
index 0000000000..e368bde6fb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-canceling.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=253357">
+<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>
+<script src="/common/top-layer.js"></script>
+<script src="/close-watcher/resources/helpers.js"></script>
+
+<!--
+To test manually, hit Escape once to see the topmost dialog turn green
+then once again to close it. Repeat for the remaining dialog.
+-->
+
+<style>
+#bottom {
+ top: 100px;
+ left: 100px;
+ height: 300px;
+ width: 300px;
+ margin: 0;
+ background: cyan;
+}
+
+#top {
+ top: 150px;
+ left: 150px;
+ height: 200px;
+ width: 200px;
+ margin: 0;
+ background: yellow;
+}
+</style>
+
+<dialog id="bottom">
+ <span></span>
+ <div>You can't Escape when this textbox has focus: <input id="swallow-input" type="text"></div>
+ <div>You can Escape even if this textbox has focus: <input id="normal-input" type="text"></div>
+</dialog>
+<dialog id="top">
+ <span></span>
+</dialog>
+
+<script>
+function handleCancel(event) {
+ this.style.background = 'green';
+ this.querySelector('span').textContent = 'I blocked the cancel! Try again to close me.';
+ event.preventDefault();
+ this.removeEventListener('cancel', handleCancel);
+}
+
+promise_test(async () => {
+ bottomDialog = document.getElementById('bottom');
+ bottomDialog.addEventListener('cancel', handleCancel);
+
+ topDialog = document.getElementById('top');
+ topDialog.addEventListener('cancel', handleCancel);
+
+ normalInput = document.getElementById('normal-input');
+ swallowInput = document.getElementById('swallow-input');
+ swallowInput.addEventListener('keydown', function(event) {
+ event.preventDefault();
+ });
+
+ bottomDialog.showModal();
+ await blessTopLayer(bottomDialog);
+ topDialog.showModal();
+
+ await blessTopLayer(topDialog);
+ await sendEscKey();
+ assert_true(topDialog.open, 'Top dialog event listener should prevent closing.');
+ assert_true(bottomDialog.open, 'Top dialog event listener should prevent closing.');
+
+ await blessTopLayer(topDialog);
+ await sendEscKey();
+ assert_false(topDialog.open, 'Top dialog should close.');
+ assert_true(bottomDialog.open, 'Top dialog should close.');
+
+ swallowInput.focus();
+ await sendEscKey();
+ await sendEscKey();
+ await sendEscKey();
+ assert_false(topDialog.open, 'Input should swallow Escape mechanism.');
+ assert_true(bottomDialog.open, 'Input should swallow Escape mechanism.');
+
+ normalInput.focus();
+ await sendEscKey();
+ assert_false(topDialog.open, 'Bottom dialog event listener should prevent closing.');
+ assert_true(bottomDialog.open, 'Bottom dialog event listener should prevent closing.');
+
+ await sendEscKey();
+ assert_false(topDialog.open, 'Bottom dialog should close.');
+ assert_false(bottomDialog.open, 'Bottom dialog should close.');
+
+ await sendEscKey();
+ assert_false(topDialog.open, 'Pressing Escape now should do nothing.');
+ assert_false(bottomDialog.open, 'Pressing Escape now should do nothing.');
+
+ bottomDialog.remove();
+ topDialog.remove();
+}, 'Modal dialogs should close when the escape key is pressed.');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close-event-async.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close-event-async.html
new file mode 100644
index 0000000000..0f8d40aa2c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close-event-async.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<title>dialog element: close()</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#the-dialog-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<dialog id="d1" open>
+ <p>foobar</p>
+ <button>OK</button>
+</dialog>
+<script>
+ var d1 = document.getElementById('d1'),
+ t = async_test("close() fires a close event"),
+ was_queued = false;
+
+ d1.onclose = t.step_func_done(function(e) {
+ assert_true(was_queued, "close event should be queued");
+ assert_true(e.isTrusted, "close event is trusted");
+ assert_false(e.bubbles, "close event doesn't bubble");
+ assert_false(e.cancelable, "close event is not cancelable");
+ });
+
+ t.step(function() {
+ d1.close();
+ was_queued = true;
+ })
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close-event.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close-event.html
new file mode 100644
index 0000000000..b7903ed461
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close-event.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=276785">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<dialog></dialog>
+
+<script>
+async_test(t => {
+ document.addEventListener('close', t.step_func_done(() => {
+ t.assert_unreached(`The 'close' event unexpectedly bubbled.`);
+ }));
+
+ closedCount = 0;
+ dialog = document.querySelector('dialog');
+ dialog.addEventListener('close', function(event) {
+ const selfDialog = this;
+ t.step(() => {
+ closedCount++;
+ assert_equals(selfDialog, dialog);
+ assert_false(dialog.open);
+ assert_false(event.cancelable);
+ event.preventDefault();
+
+ if (closedCount == 1) {
+ dialog.show();
+ dialog.close();
+ assert_equals(closedCount, 1, `dialog's close event handler shouldn't be called synchronously.`);
+ } else if (closedCount == 2) {
+ t.done();
+ }
+ });
+ });
+
+ dialog.show();
+ dialog.close();
+
+ // Verify that preventDefault() didn't cancel closing.
+ assert_false(dialog.open);
+
+ // dialog's close event handler shouldn't be called synchronously.
+ assert_equals(closedCount, 0);
+}, "Test that dialog receives a close event upon closing.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close-via-attribute.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close-via-attribute.html
new file mode 100644
index 0000000000..5c2e70f87a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close-via-attribute.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/5802">
+<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>button</button>
+<dialog>hello world</dialog>
+
+<script>
+const dialog = document.querySelector('dialog');
+const button = document.querySelector('button');
+
+promise_test(async t => {
+ dialog.showModal();
+
+ let closeFired = false;
+ let cancelFired = false;
+ dialog.addEventListener('close', () => closeFired = true);
+ dialog.addEventListener('cancel', () => cancelFired = true);
+
+ dialog.removeAttribute('open');
+ await new Promise(resolve => t.step_timeout(resolve, 0));
+ await new Promise(requestAnimationFrame);
+
+ assert_false(dialog.matches(':modal'),
+ 'The dialog should not match :modal after closing.');
+ assert_false(cancelFired,
+ 'The cancel event should not fire when removing the open attribute.');
+ assert_true(closeFired,
+ 'The close event should be fired when removing the open attribute.');
+
+ let buttonFiredClick = false;
+ button.addEventListener('click', () => buttonFiredClick = true);
+ await test_driver.click(button);
+ assert_true(buttonFiredClick,
+ 'The page should not be inert or blocked after removing the open attribute.');
+}, 'Removing the open attribute from an open modal dialog should run the closing algorithm.');
+
+promise_test(async t => {
+ dialog.show();
+
+ let closeFired = false;
+ let cancelFired = false;
+ dialog.addEventListener('close', () => closeFired = true);
+ dialog.addEventListener('cancel', () => cancelFired = true);
+
+ dialog.removeAttribute('open');
+ await new Promise(resolve => t.step_timeout(resolve, 0));
+ await new Promise(requestAnimationFrame);
+
+ assert_false(cancelFired,
+ 'The cancel event should not fire when removing the open attribute.');
+ assert_true(closeFired,
+ 'The close event should be fired when removing the open attribute.');
+}, 'Removing the open attribute from an open non-modal dialog should fire a close event.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close.html
new file mode 100644
index 0000000000..9029612b24
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-close.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>dialog element: close()</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#the-dialog-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<dialog id="d1">
+ <p>foobar</p>
+ <button>OK</button>
+</dialog>
+<dialog id="d2" open>
+ <p>foobar</p>
+ <button>OK</button>
+</dialog>
+<dialog id="d3" open>
+ <p>foobar</p>
+ <button>OK</button>
+</dialog>
+<dialog id="d4" open>
+ <p>foobar</p>
+ <button>OK</button>
+</dialog>
+<dialog id="d5" open>
+ <p>foobar</p>
+ <button>OK</button>
+</dialog>
+<script>
+ var d1 = document.getElementById('d1'),
+ d2 = document.getElementById('d2'),
+ d3 = document.getElementById('d3'),
+ d4 = document.getElementById('d4'),
+ d5 = document.getElementById('d5'),
+ t = async_test("close() fires a close event"),
+ was_queued = false;
+
+ test(function(){
+ d1.close("closedialog");
+ assert_equals(d1.returnValue, "");
+ }, "close() on a <dialog> that doesn't have an open attribute aborts the steps");
+
+ test(function(){
+ assert_true(d2.open);
+ assert_equals(d2.returnValue, "");
+ d2.close("closedialog");
+ assert_false(d2.hasAttribute("open"));
+ assert_equals(d2.returnValue, "closedialog");
+ }, "close() removes the open attribute and set the returnValue to the first argument");
+
+ test(function(){
+ assert_true(d3.open);
+ assert_equals(d3.returnValue, "");
+ d3.returnValue = "foobar";
+ d3.close();
+ assert_false(d3.hasAttribute("open"));
+ assert_equals(d3.returnValue, "foobar");
+ }, "close() without argument removes the open attribute and there's no returnValue");
+
+ d4.onclose = t.step_func_done(function(e) {
+ assert_true(was_queued, "close event should be queued");
+ assert_true(e.isTrusted, "close event is trusted");
+ assert_false(e.bubbles, "close event doesn't bubble");
+ assert_false(e.cancelable, "close event is not cancelable");
+ });
+
+ t.step(function() {
+ d4.close();
+ was_queued = true;
+ })
+
+ test(function(){
+ Object.defineProperty(HTMLDialogElement.prototype, 'returnValue', { set: function(v) { assert_unreached('JS-defined setter returnValue on the prototype was invoked'); }, configurable:true });
+ Object.defineProperty(d5, 'returnValue', { set: function(v) { assert_unreached('JS-defined setter returnValue on the instance was invoked'); }, configurable:true });
+ d5.close('foo');
+ }, "close() should set the returnValue IDL attribute but not the JS property");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-enabled.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-enabled.html
new file mode 100644
index 0000000000..87a130c6f0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-enabled.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<dialog></dialog>
+<script>
+test(function() {
+ dialog = document.querySelector('dialog')
+ assert_true(dialog instanceof HTMLDialogElement);
+}, "The DIALOG element should be recognized");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focus-previous-outside.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focus-previous-outside.html
new file mode 100644
index 0000000000..43a17676ed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focus-previous-outside.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/8904">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<button id=b1>button 1</button>
+<button id=b2>button 2</button>
+<div id=host>
+ <template shadowrootmode=open>
+ <button>button in shadowroot outside dialog</button>
+ </template>
+</div>
+<dialog id=mydialog>
+ <button id=b3>button in dialog</button>
+ <div id=dialoghost>
+ <template shadowrootmode=open>
+ <button>button in shadowroot in dialog</button>
+ </template>
+ </div>
+</dialog>
+
+<div id=host2>
+ <template shadowrootmode=open>
+ <dialog>
+ <slot></slot>
+ </dialog>
+ </template>
+ <button id=host2button>button</button>
+</div>
+
+<dialog id=mydialog2>hello world</dialog>
+
+<script>
+test(() => {
+ b1.focus();
+ mydialog.show();
+ b2.focus();
+ mydialog.close();
+ assert_equals(document.activeElement, b2);
+}, 'Focus should not be restored if the currently focused element is not inside the dialog.');
+
+test(() => {
+ const shadowbutton = host.shadowRoot.querySelector('button');
+ b2.focus();
+ mydialog.show();
+ shadowbutton.focus();
+ mydialog.close();
+ assert_equals(document.activeElement, host, 'document.activeElement should point at the shadow host.');
+ assert_equals(host.shadowRoot.activeElement, shadowbutton, 'The button in the shadowroot should remain focused.');
+}, 'Focus restore should not occur when the focused element is in a shadowroot outside of the dialog.');
+
+test(() => {
+ const shadowbutton = dialoghost.shadowRoot.querySelector('button');
+ b2.focus();
+ mydialog.show();
+ shadowbutton.focus();
+ mydialog.close();
+ assert_equals(document.activeElement, b2);
+}, 'Focus restore should occur when the focused element is in a shadowroot inside the dialog.');
+
+test(() => {
+ const dialog = host2.shadowRoot.querySelector('dialog');
+ b2.focus();
+ dialog.show();
+ host2button.focus();
+ dialog.close();
+ assert_equals(document.activeElement, b2);
+}, 'Focus restore should occur when the focused element is slotted into a dialog.');
+
+test(() => {
+ b1.focus();
+ const dialog = document.getElementById('mydialog2');
+ dialog.showModal();
+ dialog.blur();
+ assert_equals(document.activeElement, document.body,
+ 'Focus should return to the body when calling dialog.blur().');
+ dialog.close();
+ assert_equals(document.activeElement, b1,
+ 'Focus should be restored to the previously focused element.');
+}, 'Focus restore should always occur for modal dialogs.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focus-shadow-double-nested.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focus-shadow-double-nested.html
new file mode 100644
index 0000000000..2cd63eb796
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focus-shadow-double-nested.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>dialog focusing delegation: with two nested shadow trees</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+
+<dialog>
+ <template class="turn-into-shadow-tree delegates-focus">
+ <button disabled>Non-focusable</button>
+ <template class="turn-into-shadow-tree delegates-focus">
+ <button tabindex="-1">Focusable</button>
+ <button tabindex="-1" autofocus>Focusable</button>
+ <button tabindex="-1">Focusable</button>
+ </template>
+ <button tabindex="-1">Focusable</button>
+ </template>
+ <button tabindex="-1">Focusable</button>
+</dialog>
+
+<script>
+function turnIntoShadowTree(template) {
+ for (const subTemplate of template.content.querySelectorAll(".turn-into-shadow-tree")) {
+ turnIntoShadowTree(subTemplate);
+ }
+
+ const div = document.createElement("div");
+ div.attachShadow({ mode: "open", delegatesFocus: template.classList.contains("delegates-focus") });
+ div.shadowRoot.append(template.content);
+ template.replaceWith(div);
+}
+
+for (const template of document.querySelectorAll(".turn-into-shadow-tree")) {
+ turnIntoShadowTree(template);
+}
+
+for (const method of ["show", "showModal"]) {
+ test(t => {
+ const dialog = document.querySelector("dialog");
+ dialog[method]();
+ t.add_cleanup(() => dialog.close());
+
+ const shadowHostOuter = dialog.querySelector("div");
+ assert_equals(document.activeElement, shadowHostOuter, "document.activeElement");
+
+ const shadowHostInner = shadowHostOuter.shadowRoot.querySelector("div");
+ assert_equals(shadowHostOuter.shadowRoot.activeElement, shadowHostInner, "shadowHostOuter.shadowRoot.activeElement");
+
+ const button = shadowHostInner.shadowRoot.querySelector("[autofocus]");
+ assert_equals(shadowHostInner.shadowRoot.activeElement, button, "shadowHostInner.shadowRoot.activeElement");
+ }, `${method}()`);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focus-shadow.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focus-shadow.html
new file mode 100644
index 0000000000..7e57685425
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focus-shadow.html
@@ -0,0 +1,274 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>dialog focus delegation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+
+<!--
+ We focus this one between each test, to ensure that for non-modal dialogs,
+ if there is no focus delegate, it stays focused (instead of causing focus to reset to the body).
+-->
+<button id="focus-between-tests">Focus between tests</button>
+
+<dialog data-description="No autofocus, no delegatesFocus, no siblings">
+ <template class="turn-into-shadow-tree">
+ <button disabled>Non-focusable</button>
+ <button>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="No autofocus, no delegatesFocus, sibling before">
+ <button class="focus-me">Focusable</button>
+ <template class="turn-into-shadow-tree">
+ <button disabled>Non-focusable</button>
+ <button>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="No autofocus, no delegatesFocus, sibling after">
+ <template class="turn-into-shadow-tree">
+ <button disabled>Non-focusable</button>
+ <button>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+ <button class="focus-me">Focusable</button>
+</dialog>
+
+<dialog data-description="No autofocus, yes delegatesFocus, no siblings">
+ <template class="turn-into-shadow-tree delegates-focus">
+ <button disabled>Non-focusable</button>
+ <button class="focus-me">Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="No autofocus, yes delegatesFocus, sibling before">
+ <button class="focus-me">Focusable</button>
+ <template class="turn-into-shadow-tree delegates-focus">
+ <button disabled>Non-focusable</button>
+ <button>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="No autofocus, yes delegatesFocus, sibling after">
+ <template class="turn-into-shadow-tree delegates-focus">
+ <button disabled>Non-focusable</button>
+ <button class="focus-me">Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+ <button>Focusable</button>
+</dialog>
+
+<dialog data-description="Autofocus before, no delegatesFocus">
+ <button autofocus class="focus-me">Focusable</button>
+ <template class="turn-into-shadow-tree">
+ <button disabled>Non-focusable</button>
+ <button>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="Autofocus before, yes delegatesFocus">
+ <button autofocus class="focus-me">Focusable</button>
+ <template class="turn-into-shadow-tree delegates-focus">
+ <button disabled>Non-focusable</button>
+ <button>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="Autofocus after, no delegatesFocus">
+ <template class="turn-into-shadow-tree">
+ <button disabled>Non-focusable</button>
+ <button>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+ <button autofocus class="focus-me">Focusable</button>
+</dialog>
+
+<dialog data-description="Autofocus after, yes delegatesFocus">
+ <template class="turn-into-shadow-tree delegates-focus">
+ <button disabled>Non-focusable</button>
+ <button>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+ <button autofocus class="focus-me">Focusable</button>
+</dialog>
+
+<dialog data-description="Autofocus on shadow host, yes delegatesFocus, no siblings">
+ <template class="turn-into-shadow-tree delegates-focus autofocus">
+ <button disabled>Non-focusable</button>
+ <button class="focus-me">Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="Autofocus on shadow host, yes delegatesFocus, sibling before">
+ <button>Focusable</button>
+ <template class="turn-into-shadow-tree delegates-focus autofocus">
+ <button disabled>Non-focusable</button>
+ <button class="focus-me">Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="Autofocus on shadow host, yes delegatesFocus, sibling after">
+ <template class="turn-into-shadow-tree delegates-focus autofocus">
+ <button disabled>Non-focusable</button>
+ <button class="focus-me">Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+ <button>Focusable</button>
+</dialog>
+
+<dialog data-description="Autofocus on shadow host, no delegatesFocus, no siblings">
+ <template class="turn-into-shadow-tree autofocus">
+ <button disabled>Non-focusable</button>
+ <button>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="Autofocus on shadow host, no delegatesFocus, sibling before">
+ <button class="focus-me">Focusable</button>
+ <template class="turn-into-shadow-tree autofocus">
+ <button disabled>Non-focusable</button>
+ <button>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="Autofocus on shadow host, no delegatesFocus, sibling after">
+ <template class="turn-into-shadow-tree autofocus">
+ <button disabled>Non-focusable</button>
+ <button>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+ <button class="focus-me">Focusable</button>
+</dialog>
+
+<dialog data-description="Autofocus inside shadow tree, yes delegatesFocus, no siblings">
+ <template class="turn-into-shadow-tree delegates-focus">
+ <button>Focusable</button>
+ <button autofocus class="focus-me">Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="Autofocus inside shadow tree, yes delegatesFocus, sibling before">
+ <button class="focus-me">Focusable</button>
+ <template class="turn-into-shadow-tree delegates-focus">
+ <button>Focusable</button>
+ <button autofocus>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="Autofocus inside shadow tree, yes delegatesFocus, sibling after">
+ <template class="turn-into-shadow-tree delegates-focus">
+ <button>Focusable</button>
+ <button autofocus class="focus-me">Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+ <button>Focusable</button>
+</dialog>
+
+<dialog data-description="Autofocus inside shadow tree, no delegatesFocus, no siblings">
+ <template class="turn-into-shadow-tree">
+ <button>Focusable</button>
+ <button autofocus>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="Autofocus inside shadow tree, no delegatesFocus, sibling before">
+ <button class="focus-me">Focusable</button>
+ <template class="turn-into-shadow-tree">
+ <button>Focusable</button>
+ <button autofocus>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="Autofocus inside shadow tree, no delegatesFocus, sibling after">
+ <template class="turn-into-shadow-tree">
+ <button>Focusable</button>
+ <button autofocus>Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+ <button class="focus-me">Focusable</button>
+</dialog>
+
+<dialog data-description="Two shadow trees, both delegatesFocus, first tree doesn't have autofocus element, second does">
+ <template class="turn-into-shadow-tree delegates-focus">
+ <button disabled>Non-focusable</button>
+ <button class="focus-me">Focusable</button>
+ <button disabled>Non-focusable</button>
+ </template>
+ <template class="turn-into-shadow-tree delegates-focus">
+ <button autofocus>Focusable</button>
+ </template>
+</dialog>
+
+<dialog data-description="No autofocus, no delegatesFocus, slotted target">
+ <template class="turn-into-shadow-tree">
+ <button>Focusable</button>
+ <slot></slot>
+ <button>Focusable</button>
+ </template>
+ <button class="focus-me">Focusable</button>
+</dialog>
+
+<dialog data-description="Shadowroot on child, no autofocus, no delegatesFocus">
+ <div>
+ <template class="turn-into-shadow-tree">
+ <button>Focusable</button>
+ </template>
+ </div>
+ <button class="focus-me">Focusable</button>
+</dialog>
+
+<script>
+for (const template of document.querySelectorAll(".turn-into-shadow-tree")) {
+ const div = document.createElement("div");
+ div.attachShadow({ mode: "open", delegatesFocus: template.classList.contains("delegates-focus") });
+
+ if (template.classList.contains("autofocus")) {
+ div.setAttribute("autofocus", true);
+ }
+ div.shadowRoot.append(template.content);
+ template.replaceWith(div);
+}
+
+const focusBetweenTests = document.querySelector("#focus-between-tests");
+
+for (const dialog of document.querySelectorAll("dialog")) {
+ for (const method of ["show", "showModal"]) {
+ test(t => {
+ focusBetweenTests.focus();
+
+ dialog[method]();
+ t.add_cleanup(() => dialog.close());
+
+ const expectedFocusOutsideShadowTree = dialog.querySelector(".focus-me");
+ if (expectedFocusOutsideShadowTree) {
+ assert_equals(document.activeElement, expectedFocusOutsideShadowTree);
+ } else {
+ const shadowHost = dialog.querySelector("div");
+ const expectedFocusInsideShadowTree = shadowHost.shadowRoot.querySelector(".focus-me");
+ if (expectedFocusInsideShadowTree) {
+ assert_equals(document.activeElement, shadowHost);
+ assert_equals(shadowHost.shadowRoot.activeElement, expectedFocusInsideShadowTree);
+ } else {
+ // There is no focus delegate. The dialog element should be focused.
+ assert_equals(document.activeElement, dialog);
+ }
+ }
+ }, `${method}: ${dialog.dataset.description}`);
+ }
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusability.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusability.html
new file mode 100644
index 0000000000..1e00086609
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusability.html
@@ -0,0 +1,58 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>dialog element: focusability</title>
+<link rel=help href="https://github.com/whatwg/html/pull/8199">
+<link rel=author href="mailto:masonf@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>
+<script src="/resources/testdriver-actions.js"></script>
+
+<button id="before">before</button>
+<dialog id="dialog1" open>
+ <button id="within1">button</button>
+</dialog>
+<button id="after1">after 1</button>
+<dialog tabindex=0 id="dialog2" open>
+ <button id="within2">button</button>
+</dialog>
+<button id="after2">after 2</button>
+<dialog tabindex="-1" id="dialog3" open>
+ <button id="within3">button</button>
+</dialog>
+<button id="after3">after 3</button>
+<dialog contenteditable="true" id="dialog4" open>
+ <button id="within4">button</button>
+</dialog>
+<button id="after4">after 4</button>
+
+<style>
+ #dialog1 { top: 25px; }
+ #dialog2 { top: 100px; }
+ #dialog3 { top: 175px; }
+ #dialog4 { top: 250px; }
+</style>
+
+<script>
+ function navigateForward() {
+ const TAB = '\ue004';
+ return test_driver.send_keys(document.body, TAB);
+ }
+ async function assert_focus_order(elements) {
+ assert_true(elements.length >= 2);
+ elements[0].focus();
+ for(let i=0;i<elements.length;++i) {
+ assert_equals(document.activeElement,elements[i],`Focus order mismatch at step ${i+1}/${elements.length}`);
+ await navigateForward();
+ }
+ }
+
+ async_test((t) => {
+ window.onload = async () => {
+ await assert_focus_order([before,within1,after1,dialog2,within2,after2,
+ within3,after3,dialog4,within4,after4]);
+ t.done();
+ };
+ }, "The dialog element itself should not be keyboard focusable.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusing-steps-disconnected.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusing-steps-disconnected.html
new file mode 100644
index 0000000000..bf621b640b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusing-steps-disconnected.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Test focusing steps when dialog is disconnected</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<input>
+<script>
+test(function() {
+ const outerInput = document.querySelector("input");
+ outerInput.focus();
+ assert_equals(document.activeElement, outerInput,
+ "Focus should be on element we just focused");
+
+ const dialog = document.createElement("dialog");
+ assert_false(dialog.open, "Dialog should initially be closed");
+ assert_false(dialog.hasAttribute('open'), "Dialog should initially be closed");
+
+ const innerInput = document.createElement("input");
+ innerInput.autofocus = true;
+ dialog.append(innerInput);
+
+ dialog.show();
+ this.add_cleanup(() => { dialog.close(); });
+ assert_true(dialog.open, "Disconnected dialog can still be open");
+
+
+ assert_equals(document.activeElement, outerInput, "Focusing steps should not change focus");
+}, "dialog.show(): focusing steps should not change focus on disconnected <dialog>");
+
+test(function() {
+ assert_throws_dom("InvalidStateError", () => {
+ document.createElement("dialog").showModal();
+ });
+}, "dialog.showModal() should throw on disconnected <dialog>");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusing-steps-inert.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusing-steps-inert.html
new file mode 100644
index 0000000000..003c456179
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusing-steps-inert.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Test focusing steps when dialog is inert</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<input id="outer-input">
+<dialog>
+ <input autofocus>
+</dialog>
+<script>
+function test_focusing_steps_with_inert_dialog(test, isModal) {
+ const outerInput = document.querySelector("#outer-input");
+ outerInput.focus();
+ assert_equals(document.activeElement, outerInput,
+ "Focus should be on element we just focused");
+
+ const dialog = document.querySelector("dialog");
+ assert_false(dialog.open, "Dialog should initially be closed");
+
+ dialog.inert = true;
+ test.add_cleanup(() => { dialog.inert = false; });
+
+ if (isModal) {
+ dialog.showModal();
+ test.add_cleanup(() => { dialog.close(); });
+ assert_equals(document.activeElement, document.body,
+ "dialog.showModal(): focusing steps should apply focus fixup rule when dialog is inert");
+ } else {
+ dialog.show();
+ test.add_cleanup(() => { dialog.close(); });
+ assert_equals(document.activeElement, outerInput,
+ "dialog.show(): focusing steps should not change focus when dialog is inert");
+ }
+}
+
+test(function() {
+ test_focusing_steps_with_inert_dialog(this, false);
+}, "dialog.show(): focusing steps should not change focus when dialog is inert");
+
+test(function() {
+ test_focusing_steps_with_inert_dialog(this, true);
+}, "dialog.showModal(): focusing steps should apply focus fixup rule when dialog is inert");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusing-steps-prevent-autofocus.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusing-steps-prevent-autofocus.html
new file mode 100644
index 0000000000..4d1e792c1c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-focusing-steps-prevent-autofocus.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/interaction/focus/the-autofocus-attribute/resources/utils.js"></script>
+<body>
+<dialog></dialog>
+<script>
+// https://github.com/whatwg/html/issues/4788
+promise_test(async () => {
+ const dialog = document.querySelector('dialog');
+ dialog.show();
+ dialog.close();
+ const input = document.createElement('input');
+ input.autofocus = true;
+ document.body.insertBefore(input, dialog);
+ await waitUntilStableAutofocusState();
+ assert_not_equals(document.activeElement, input,
+ 'Non-dialog autofocus processing should be skipped.');
+}, 'After showing a dialog, non-dialog autofocus processing won\'t work.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-form-submission-unusual.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-form-submission-unusual.html
new file mode 100644
index 0000000000..ae0de29a89
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-form-submission-unusual.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<title>Test dialog form submission, unusual cases</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id=log></div>
+
+<dialog>
+ <form method=dialog action="https://test:test/" target=doesnotmatter rel=noopener>
+ <input type=submit formaction="https://test:test/" id=submit-1>
+ <input type=submit id=submit-2>
+ </form>
+</dialog>
+
+<script>
+test(() => {
+ const dialog = document.querySelector("dialog");
+ dialog.showModal();
+ assert_true(dialog.open);
+
+ document.getElementById("submit-1").click();
+ assert_false(dialog.open);
+}, "A form's action and rel=noopener are ignored during submission");
+
+test(() => {
+ const dialog = document.querySelector("dialog");
+ dialog.showModal();
+ assert_true(dialog.open);
+
+ document.getElementById("submit-2").click();
+ assert_false(dialog.open);
+}, "A form's action and rel=noopener are ignored during submission, part 2");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-form-submission.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-form-submission.html
new file mode 100644
index 0000000000..5934485087
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-form-submission.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<meta charset=urf-8>
+<meta name=viewport content="width=device-width,initial-scale=1">
+<title>Test dialog form submission</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>
+
+<body>
+<dialog id="favDialog">
+ <form id="dialogForm" method="dialog">
+ <button id="confirmBtn" value="default">Confirm</button>
+ <input id="confirmImgBtn" src="./resources/submit.jpg" width="41"
+ height="41" type="image" alt="Hello">
+ </form>
+ <form method="post">
+ <input id="confirmImgBtn2" src="./resources/submit.jpg" width="41"
+ formmethod="dialog" height="41" type="image" alt="Hello">
+ </form>
+</dialog>
+<script>
+promise_test(async () => {
+ const dialog = document.querySelector('dialog');
+ dialog.showModal();
+
+ const button = document.querySelector('button');
+ button.click();
+
+ assert_false(dialog.open, "dialog should be closed now");
+ assert_equals(dialog.returnValue, "default", "Return the default value");
+}, 'click the form submission button should close the dialog');
+
+promise_test(async () => {
+ const dialog = document.querySelector('dialog');
+ dialog.showModal();
+
+ const button = document.querySelector('button');
+ button.value = "sushi";
+ button.click();
+
+ assert_false(dialog.open, "dialog should be closed now");
+ assert_equals(dialog.returnValue, "sushi", "Return the updated value");
+}, 'form submission should return correct value');
+
+promise_test(async () => {
+ const dialog = document.querySelector('dialog');
+ dialog.showModal();
+
+ const button = document.querySelector('button');
+ button.removeAttribute("value");
+ button.click();
+ assert_false(dialog.open, "dialog should be closed now");
+ assert_not_equals(dialog.returnValue, undefined, "returnValue should not be set");
+}, "no returnValue when there's no result.");
+
+promise_test(async () => {
+ const dialog = document.querySelector('dialog');
+ dialog.showModal();
+
+ const button = document.querySelector('input');
+ let expectedReturnValue = "";
+ button.addEventListener('click', function(event) {
+ expectedReturnValue = event.offsetX + "," + event.offsetY;
+ });
+ await test_driver.click(button);
+
+ assert_false(dialog.open, "dialog should be closed now");
+ assert_not_equals(dialog.returnValue, "", "returnValue shouldn't be empty string");
+ assert_equals(dialog.returnValue, expectedReturnValue, "returnValue should be the offsets of the click");
+}, "input image button should return the coordinates");
+
+promise_test(async () => {
+ const dialog = document.querySelector('dialog');
+ dialog.showModal();
+ const button = document.getElementById('confirmImgBtn2');
+ await test_driver.click(button);
+ assert_false(dialog.open, "dialog should be closed now");
+}, "formmethod attribute should use dialog form submission");
+
+promise_test(async () => {
+ const dialog = document.querySelector('dialog');
+ dialog.returnValue = "";
+ dialog.showModal();
+
+ const button = document.querySelector('button');
+ button.value = "sushi";
+
+ const dialogForm = document.getElementById('dialogForm');
+ dialogForm.onsubmit = function() {
+ dialog.close();
+ }
+
+ button.click();
+ assert_false(dialog.open, "dialog should be closed now");
+ // If the submission request got processed, the returnValue should change
+ // to "sushi" because that's the value of the submitter
+ assert_equals(dialog.returnValue, "", "dialog's returnValue remains the same");
+}, "closing the dialog while submitting should stop the submission");
+
+promise_test(async () => {
+ const dialog = document.querySelector('dialog');
+ dialog.returnValue = undefined;
+ dialog.showModal();
+
+ let submitEvent = false;
+ const dialogForm = document.getElementById('dialogForm');
+ dialogForm.onsubmit = function() {
+ submitEvent = true;
+ assert_false(dialog.open, "dialog should be closed");
+ assert_equals(dialog.returnValue, "", "dialog's returnValue remains the same");
+ };
+
+ const button = document.querySelector('button');
+ button.value = "sushi";
+ button.onclick = function() {
+ dialogForm.submit();
+ assert_false(dialog.open, "dialog should be closed now");
+ // The returnValue should be "" because there is no submitter
+ assert_equals(dialog.returnValue, "", "returnValue shouldn be empty string");
+ };
+
+ button.click();
+ assert_true(submitEvent, "Should have submit event");
+ assert_false(dialog.open, "dialog should be closed");
+ assert_equals(dialog.returnValue, "", "dialog's returnValue remains the same");
+}, "calling form.submit() in click handler of submit button should start the submission synchronously");
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-inert.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-inert.html
new file mode 100644
index 0000000000..864420b9d2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-inert.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<meta charset=utf-8>
+<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>
+<style>
+ body { margin: 0 }
+ dialog {
+ width: 100%;
+ height: 100%;
+ max-width: 100%;
+ max-height: 100%;
+ box-sizing: border-box;
+ padding: 0;
+ }
+ dialog::backdrop {
+ display: none;
+ }
+</style>
+<dialog id=dialog>Something</dialog>
+<script>
+test(function() {
+ let dialog = document.getElementById("dialog");
+ dialog.showModal();
+ assert_equals(
+ document.elementFromPoint(10, 10),
+ dialog,
+ "Dialog is hittable by default",
+ );
+ dialog.inert = true;
+ assert_not_equals(
+ document.elementFromPoint(10, 10),
+ dialog,
+ "Dialog becomes inert dynamically",
+ );
+ dialog.close();
+ dialog.showModal();
+ assert_not_equals(
+ document.elementFromPoint(10, 10),
+ dialog,
+ "Dialog remains inert after open",
+ );
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-keydown-preventDefault.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-keydown-preventDefault.html
new file mode 100644
index 0000000000..4a50b13c87
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-keydown-preventDefault.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test cancel event with preventDefault on keydown event for dialog element</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>
+ <link rel="help" href="https://bugs.webkit.org/show_bug.cgi?id=227534">
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322947">
+</head>
+<body>
+<p>Test cancel event with preventDefault on keydown event for dialog element</p>
+<dialog>
+ <p>Hello World</p>
+</dialog>
+<script>
+ setup({ single_test: true });
+
+ var hasCancelEventFired = false;
+
+ const dialog = document.querySelector("dialog");
+
+ const verify = () => {
+ assert_false(hasCancelEventFired, "cancel should not be fired");
+ assert_true(hasKeydownEventFired, "document level keydown event should be fired");
+ done();
+ };
+
+ dialog.addEventListener("cancel", function(event) {
+ hasCancelEventFired = true;
+ });
+
+ document.addEventListener("keydown", function(event) {
+ hasKeydownEventFired = true;
+ event.preventDefault();
+ step_timeout(function() {
+ verify();
+ }, 0);
+ });
+ dialog.showModal();
+ test_driver.send_keys(document.documentElement, "\uE00C"); // ESC key
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-no-throw-requested-state.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-no-throw-requested-state.html
new file mode 100644
index 0000000000..c86cbe84a6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-no-throw-requested-state.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/pull/9142">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<dialog>hello</dialog>
+
+<script>
+test(() => {
+ const dialog = document.querySelector('dialog');
+
+ // calling close() on a dialog that is already closed should not throw.
+ dialog.close();
+
+ dialog.show();
+ // calling show() on a dialog that is already showing non-modal should not throw.
+ dialog.show();
+ assert_throws_dom('InvalidStateError', () => dialog.showModal(),
+ 'Calling showModal() on a dialog that is already showing non-modal should throw.');
+ dialog.close();
+
+ dialog.showModal();
+ assert_throws_dom('InvalidStateError', () => dialog.show(),
+ 'Calling show() on a dialog that is already showing modal should throw.');
+ // calling showModal() on a dialog that is already showing modal should not throw.
+ dialog.showModal();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-not-in-tree-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-not-in-tree-crash.html
new file mode 100644
index 0000000000..fe3fab8ebb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-not-in-tree-crash.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<script>
+ const dialog = document.createElement("dialog");
+ dialog.show();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-2.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-2.html
new file mode 100644
index 0000000000..79120d07eb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-open-2.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.webkit.org/show_bug.cgi?id=90931">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<dialog id=mydialog>It's my dialog.</dialog>
+
+<script>
+test(() => {
+ const dialog = document.getElementById('mydialog');
+ let computedStyle = window.getComputedStyle(dialog, null);
+ assert_equals(computedStyle.getPropertyValue('display'), 'none');
+
+ dialog.show();
+ computedStyle = window.getComputedStyle(dialog, null);
+ assert_equals(computedStyle.getPropertyValue('display'), 'block');
+
+ dialog.close();
+ computedStyle = window.getComputedStyle(dialog, null);
+
+ assert_equals(computedStyle.getPropertyValue('display'), 'none');
+ dialog.close();
+}, "Tests that dialog is visible after show() is called and not visible after close() is called.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-open.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-open.html
new file mode 100644
index 0000000000..e1f4c6ab82
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-open.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>dialog element: open</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/forms.html#dom-dialog-open">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<dialog id="d1">
+ <p>foobar</p>
+ <button>OK</button>
+</dialog>
+<dialog id="d2" open>
+ <p>foobar</p>
+ <button>OK</button>
+</dialog>
+<script>
+ var d1 = document.getElementById('d1');
+ var d2 = document.getElementById('d2');
+
+ test(function(){
+ assert_false(d1.open);
+ assert_true(d2.open);
+ }, "On getting, the IDL open attribute must return true if the content open attribute is set, and false if it is absent.");
+
+ test(function(){
+ d1.open = true;
+ assert_true(d1.hasAttribute("open"));
+ d2.open = false;
+ assert_false(d2.hasAttribute("open"));
+ }, "On setting, the content open attribute must be removed if the IDL open attribute is set to false, and must be present if the IDL open attribute is set to true.");
+
+ async_test(function(t){
+ d2.open = true;
+ assert_true(d2.hasAttribute("open"));
+ d2.onclose = t.unreached_func("close event should not be fired when just setting the open attribute");
+ d2.open = false;
+ assert_false(d2.hasAttribute("open"));
+
+ // close event is async, give it a chance to be fired
+ t.step_timeout(function() {
+ t.done();
+ }, 0);
+ }, "On setting it to false, the close event should not be fired");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-overlay-re-add-during-transition.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-overlay-re-add-during-transition.html
new file mode 100644
index 0000000000..30d104a973
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-overlay-re-add-during-transition.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>dialog: close and re-add modal dialog during overlay transition</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel="help" href="https://drafts.csswg.org/css-position-4/#overlay">
+<link rel="match" href="pass-dialog-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<dialog id="dialog1">PASS</dialog>
+<dialog id="dialog2">FAIL</dialog>
+<style>
+ dialog::backdrop { background-color: black; }
+ #dialog1 {
+ transition-property: overlay, display;
+ transition-duration: 100s;
+ }
+</style>
+<script>
+ const dialog1 = document.getElementById("dialog1");
+ const dialog2 = document.getElementById("dialog2");
+
+ dialog1.showModal();
+ dialog2.showModal();
+ dialog1.close();
+ requestAnimationFrame(() =>
+ requestAnimationFrame(() => {
+ // dialog1 no longer "in top layer" even if rendered in top-layer, should
+ // be added as last top layer element.
+ dialog1.showModal();
+ takeScreenshot();
+ })
+ );
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-overlay.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-overlay.html
new file mode 100644
index 0000000000..9cd2426b8c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-overlay.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<title>dialog: overlay</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel="help" href="https://drafts.csswg.org/css-position-4/#overlay">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<dialog id="dialog"></dialog>
+<script>
+ const dialog = document.getElementById("dialog");
+
+ test(() => {
+ assert_equals(getComputedStyle(dialog).overlay, "none",
+ "Computed overlay");
+ assert_equals(getComputedStyle(dialog, "::backdrop").overlay, "none",
+ "Computed overlay for ::backdrop");
+ }, "dialog computed overlay initially 'none'");
+
+ test(() => {
+ dialog.showModal();
+
+ assert_equals(getComputedStyle(dialog).overlay, "auto",
+ "Computed overlay on open dialog");
+ // ::backdrop pseudo element is always rendered in the top layer when its
+ // originating element is. It does not get its overlay property changed,
+ // though.
+ assert_equals(getComputedStyle(dialog, "::backdrop").overlay, "none",
+ "Computed overlay for ::backdrop");
+
+ dialog.close();
+
+ assert_equals(getComputedStyle(dialog).overlay, "none",
+ "Computed overlay on closed dialog");
+ assert_equals(getComputedStyle(dialog, "::backdrop").overlay, "none",
+ "Computed overlay for ::backdrop");
+ }, "Opening and closing a modal dialog changes computed overlay to 'auto' and back to 'none'");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-return-value.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-return-value.html
new file mode 100644
index 0000000000..2a80de65a1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-return-value.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<dialog></dialog>
+<script>
+test(function() {
+ dialog = document.querySelector('dialog');
+ assert_equals(dialog.returnValue, '');
+
+ dialog.returnValue = 'Setting value directly';
+ assert_equals(dialog.returnValue, 'Setting value directly');
+
+ dialog.returnValue = null;
+ assert_equals(dialog.returnValue, 'null');
+
+ dialog.returnValue = '';
+ assert_equals(dialog.returnValue, '');
+
+ dialog.returnValue = 7;
+ assert_equals(dialog.returnValue, '7');
+
+ dialog.show();
+ dialog.close('Return value set from close()');
+ assert_equals(dialog.returnValue, 'Return value set from close()');
+
+ dialog.show();
+ dialog.close('');
+ assert_equals(dialog.returnValue, '');
+
+ dialog.show();
+ dialog.close(null);
+ assert_equals(dialog.returnValue, 'null');
+
+ dialog.returnValue = 'Should not change because no argument to close()';
+ dialog.show();
+ dialog.close();
+ assert_equals(dialog.returnValue, 'Should not change because no argument to close()');
+
+ dialog.returnValue = 'Should not change because of undefined argument to close()';
+ dialog.show();
+ dialog.close(undefined);
+ assert_equals(dialog.returnValue, 'Should not change because of undefined argument to close()');
+
+ dialog.returnValue = 'Should not change because of no-op close()';
+ dialog.close('blah');
+ assert_equals(dialog.returnValue, 'Should not change because of no-op close()');
+}, "Tests dialog.returnValue is settable and returns the last value set.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-showModal-inert-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-showModal-inert-crash.html
new file mode 100644
index 0000000000..54c2edab6b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-showModal-inert-crash.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<iframe id="frame"></iframe>
+<script>
+ async_test(function(t) {
+ onload = t.step_func(() => {
+ const host = document.createElement("div");
+ frame.appendChild(host);
+ frame.contentDocument.body.innerHTML = "<dialog></dialog>";
+ document.body.offsetTop;
+ const root = host.attachShadow({mode: 'open'});
+ root.innerHTML = "<content>";
+ const dialog = frame.contentDocument.querySelector("dialog");
+ dialog.showModal();
+ t.done();
+ });
+ }, "Dialog.showModal() called when we have a dirty shadow distribution should not crash.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-showModal-remove.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-showModal-remove.html
new file mode 100644
index 0000000000..c2350c3042
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-showModal-remove.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>dialog element: removing from document after showModal()</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-showmodal">
+<link rel=help href="https://fullscreen.spec.whatwg.org/#removing-steps">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<dialog></dialog>
+<script>
+async_test(t => {
+ const dialog = document.querySelector('dialog')
+ dialog.showModal()
+ assert_true(dialog.open)
+ // The dialog element is now in top layer. Removing it should synchronously
+ // remove it from top layer, but should leave it in a strange limbo state.
+ dialog.addEventListener('close', t.unreached_func('close event'))
+ dialog.remove()
+ assert_true(dialog.open)
+ // if an event was queued, it would fire before this timeout
+ step_timeout(t.step_func_done(() => {
+ assert_true(dialog.open)
+ // pass if no close event was fired
+ }))
+})
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-showModal.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-showModal.html
new file mode 100644
index 0000000000..47612e759e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialog-showModal.html
@@ -0,0 +1,188 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>dialog element: showModal()</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#the-dialog-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<button id="b0">OK</button>
+<dialog id="d1">
+ <p>foobar</p>
+ <button id="b1">OK</button>
+</dialog>
+<dialog id="d2" open>
+ <p>foobar</p>
+ <button>OK</button>
+</dialog>
+<dialog id="d3">
+ <p>foobar</p>
+ <button id="b3">OK</button>
+</dialog>
+<dialog id="d4">
+ <p>foobar</p>
+ <button id="b4">OK</button>
+</dialog>
+<dialog id="d5">
+ <p>foobar</p>
+ <button id="b5">OK</button>
+</dialog>
+<dialog id="d6"></dialog>
+<dialog id="d7">
+ <input id="i71" value="foobar">
+ <input id="i72" value="foobar">
+ <button id="b7">OK</button>
+</dialog>
+<dialog id="d8">
+ <input id="i81" value="foobar">
+ <input id="i82" value="foobar" autofocus>
+ <button id="b8">OK</button>
+</dialog>
+<dialog id="d9"></dialog>
+<dialog id="d10"></dialog>
+<dialog id="d11"></dialog>
+<script>
+ var d1 = document.getElementById('d1'),
+ d2 = document.getElementById('d2'),
+ d3 = document.getElementById('d3'),
+ d4 = document.getElementById('d4'),
+ d5 = document.getElementById('d5'),
+ d6 = document.getElementById('d6'),
+ d7 = document.getElementById('d7'),
+ d8 = document.getElementById('d8'),
+ d9 = document.getElementById('d9'),
+ d10 = document.getElementById('d10'),
+ d11 = document.getElementById('d11'),
+ b0 = document.getElementById('b0'),
+ b1 = document.getElementById('b1'),
+ b3 = document.getElementById('b3'),
+ b4 = document.getElementById('b4'),
+ b5 = document.getElementById('b5');
+
+ test(function(){
+ assert_false(d1.open);
+ assert_false(d1.hasAttribute("open"));
+ assert_equals(getComputedStyle(d1).display, "none");
+ d1.showModal();
+ this.add_cleanup(function() { d1.close(); });
+ assert_true(d1.open);
+ assert_equals(d1.getAttribute("open"), "");
+ assert_equals(getComputedStyle(d1).display, "block");
+ assert_equals(document.activeElement, b1);
+ });
+
+ test(function(){
+ this.add_cleanup(function() { d2.close(); });
+ assert_throws_dom("INVALID_STATE_ERR", function() {
+ d2.showModal();
+ });
+ }, "showModal() on a <dialog> that already has an open attribute throws an InvalidStateError exception");
+
+ test(function(){
+ d9.showModal();
+ this.add_cleanup(function() { d9.close(); });
+ assert_true(d9.open);
+ d9.removeAttribute("open");
+ assert_false(d9.open);
+ d9.showModal();
+ assert_true(d9.open);
+ }, "showModal() on a <dialog> after initial showModal() and removing the open attribute");
+
+ test(function(){
+ var d = document.createElement("dialog");
+ this.add_cleanup(function() { d.close(); });
+ assert_throws_dom("INVALID_STATE_ERR", function() {
+ d.showModal();
+ });
+ }, "showModal() on a <dialog> not in a Document throws an InvalidStateError exception");
+
+ test(function(){
+ assert_false(d3.open);
+ assert_false(d4.open);
+ assert_false(d5.open);
+ d3.showModal();
+ this.add_cleanup(function() { d3.close(); });
+ d4.showModal();
+ this.add_cleanup(function() { d4.close(); });
+ d5.showModal();
+ this.add_cleanup(function() { d5.close(); });
+ assert_true(d3.open);
+ assert_true(d4.open);
+ assert_true(d5.open);
+ }, "when opening multiple dialogs, only the newest one is non-inert");
+
+ test(function(){
+ assert_false(d6.open);
+ d6.showModal();
+ this.add_cleanup(function() { d6.close(); });
+ assert_true(d6.open);
+ assert_equals(document.activeElement, d6);
+ }, "opening dialog without focusable children");
+
+ test(function(){
+ assert_false(d7.open);
+ d7.showModal();
+ this.add_cleanup(function() { d7.close(); });
+ assert_true(d7.open);
+ assert_equals(document.activeElement, document.getElementById("i71"));
+ }, "opening dialog with multiple focusable children");
+
+ test(function(){
+ assert_false(d8.open);
+ d8.showModal();
+ this.add_cleanup(function() { d8.close(); });
+ assert_true(d8.open);
+ assert_equals(document.activeElement, document.getElementById("i82"));
+ }, "opening dialog with multiple focusable children, one having the autofocus attribute");
+
+ test(function(){
+ assert_false(d10.open);
+ assert_false(d11.open);
+ d10.showModal();
+ this.add_cleanup(function() { d10.close(); });
+ d11.showModal();
+ this.add_cleanup(function() { d11.close(); });
+ var rect10 = d10.getBoundingClientRect();
+ var rect11 = d11.getBoundingClientRect();
+
+ // The two <dialog>s are both in top layer, with the same position/size.
+ assert_equals(rect10.left, rect11.left);
+ assert_equals(rect10.top, rect11.top);
+ assert_equals(rect10.width, rect11.width);
+ assert_equals(rect10.height, rect11.height);
+
+ var pointX = rect10.left + rect10.width / 2,
+ pointY = rect10.top + rect10.height / 2;
+ function topElement() {
+ return document.elementFromPoint(pointX, pointY);
+ }
+
+ // d11 was most recently openened, and thus on top.
+ assert_equals(topElement(), d11);
+
+ // Removing the open attribute and running through the showModal() algorithm
+ // again should promote d10 to the top.
+ d10.removeAttribute("open");
+ assert_equals(topElement(), d11);
+ d10.showModal();
+ assert_equals(topElement(), d10);
+
+ // Closing d11 with close() should cause d10 to be the topmost element.
+ d11.close();
+ assert_equals(topElement(), d10);
+ }, "when opening multiple dialogs, the most recently opened is rendered on top");
+
+ test(function() {
+ assert_false(d11.open);
+ d11.parentNode.removeChild(d11);
+ assert_throws_dom("INVALID_STATE_ERR", () => d11.showModal());
+
+ const doc = document.implementation.createHTMLDocument();
+ doc.body.appendChild(d11);
+ this.add_cleanup(() => document.body.append(d11));
+ assert_false(d11.open);
+ d11.showModal();
+ assert_true(d11.open);
+ this.add_cleanup(() => d11.close());
+ }, "Although the document is not attached to any pages, showModal() should execute as normal.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialogs-with-no-backdrop-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialogs-with-no-backdrop-ref.html
new file mode 100644
index 0000000000..4b31dc7062
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialogs-with-no-backdrop-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<body>
+Test that ::backdrop is not shown for non-open or non-modal dialogs.
+The test passes if there is no red shown.
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialogs-with-no-backdrop.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialogs-with-no-backdrop.html
new file mode 100644
index 0000000000..fec4ba8587
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dialogs-with-no-backdrop.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<link rel="match" href="dialogs-with-no-backdrop-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-dialog-element">
+<style>
+dialog::backdrop {
+ position: absolute;
+ top: 100px;
+ left: 100px;
+ height: 100px;
+ width: 100px;
+ background: red;
+}
+
+#display-none-backdrop::backdrop {
+ display: none;
+}
+</style>
+<body>
+Test that ::backdrop is not shown for non-open or non-modal dialogs.
+The test passes if there is no red shown.
+<dialog id="never-opened-dialog"></dialog>
+<dialog id="display-none-dialog" style="display: none"></dialog>
+<dialog id="non-modal-dialog" style="visibility: hidden"></dialog>
+<dialog id="display-none-backdrop" style="visibility: hidden"></dialog>
+<dialog id="closed-dialog"></dialog>
+<dialog id="removed-dialog"></dialog>
+<script>
+document.getElementById('display-none-dialog').showModal();
+document.getElementById('non-modal-dialog').show();
+document.getElementById('display-none-backdrop').showModal();
+
+var closedDialog = document.getElementById('closed-dialog');
+closedDialog.showModal();
+closedDialog.close();
+
+var removedDialog = document.getElementById('removed-dialog');
+removedDialog.showModal();
+removedDialog.parentNode.removeChild(removedDialog);
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dont-share-style-to-top-layer-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dont-share-style-to-top-layer-ref.html
new file mode 100644
index 0000000000..7e6112b3ce
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dont-share-style-to-top-layer-ref.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<style>
+dialog {
+ outline: none;
+}
+#non-modal {
+ position: static;
+}
+</style>
+<p>Test that a non-top layer element doesn't share style with a top layer
+element. The test passes if you see two boxes.</p>
+<dialog id="non-modal" open></dialog>
+<dialog id="modal"></dialog>
+<script>
+document.querySelector('#modal').showModal();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dont-share-style-to-top-layer.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dont-share-style-to-top-layer.html
new file mode 100644
index 0000000000..e4f4ce50b3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/dont-share-style-to-top-layer.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<link rel="match" href="dont-share-style-to-top-layer-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ position: static;
+}
+#modal {
+ outline: none;
+}
+</style>
+<p>Test that a non-top layer element doesn't share style with a top layer
+element. The test passes if you see two boxes.</p>
+<dialog open></dialog>
+<dialog id="modal"></dialog>
+<script>
+document.querySelector('#modal').showModal();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/element-removed-from-top-layer-has-original-position-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/element-removed-from-top-layer-has-original-position-ref.html
new file mode 100644
index 0000000000..c0b64e68bd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/element-removed-from-top-layer-has-original-position-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+.green {
+ color: green;
+}
+</style>
+</head>
+<body>
+<p>Bug <a href="http://webkit.org/b/106538">106538</a>: Top layer fails for inline elements</p>
+<p>This tests that position 'static' no longer computes to 'absolute' for an
+element that has been removed from the top layer. The test passes if you see
+a single line of text.</p>
+<span class="green">This is the span.</span>
+<span class="green">This is the dialog following it.</span>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/element-removed-from-top-layer-has-original-position.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/element-removed-from-top-layer-has-original-position.html
new file mode 100644
index 0000000000..0dead33163
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/element-removed-from-top-layer-has-original-position.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="match" href="element-removed-from-top-layer-has-original-position-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+.green {
+ color: green;
+}
+
+#right-dialog {
+ display: inline;
+ position: static;
+ border: none;
+ padding: 0;
+ margin: 0;
+ outline: none;
+}
+</style>
+</head>
+<body>
+<p>Bug <a href="http://webkit.org/b/106538">106538</a>: Top layer fails for inline elements</p>
+<p>This tests that position 'static' no longer computes to 'absolute' for an
+element that has been removed from the top layer. The test passes if you see
+a single line of text.</p>
+<span class="green">This is the span.</span>
+<dialog class="green" id="right-dialog">This is the dialog following it.</dialog>
+<script>
+var dialog = document.getElementById('right-dialog');
+dialog.showModal();
+dialog.close();
+dialog.show();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-contain-ancestor.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-contain-ancestor.html
new file mode 100644
index 0000000000..5ee64fc1d9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-contain-ancestor.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<title>Test that a fixed positioned child of a dialog is aligned to the viewport</title>
+<head>
+<link rel="match" href="fixed-position-child-with-fixed-position-cb-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+::backdrop {
+ display: none;
+}
+#dialog {
+ outline: none;
+}
+</style>
+</head>
+<body>
+<div style="contain: layout">
+ <dialog id="dialog">
+ Dialog should be centered.
+ <div style="position: fixed; top: 0px; left: 0px">This fixed positioned element is aligned to viewport top-left.</div>
+ </dialog>
+</div>
+<script>
+dialog.showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-fixed-position-cb-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-fixed-position-cb-ref.html
new file mode 100644
index 0000000000..d973c0876d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-fixed-position-cb-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<title>Test that a fixed positioned child of a dialog is aligned to the viewport</title>
+<head>
+<link rel="stylesheet" href="resources/dialog.css">
+</head>
+<style>
+</style>
+<body>
+<div class="pseudodialog" style="position: fixed">
+Dialog should be centered.
+</div>
+<div style="position: fixed; top: 0px; left: 0px">This fixed positioned element is aligned to viewport top-left.</div>
+</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-fo-ancestor.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-fo-ancestor.html
new file mode 100644
index 0000000000..2bc294be2f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-fo-ancestor.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<title>Test that a fixed positioned child of a dialog is aligned to the viewport</title>
+<head>
+<link rel="match" href="fixed-position-child-with-fixed-position-cb-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+::backdrop {
+ display: none;
+}
+#dialog {
+ outline: none;
+}
+</style>
+</head>
+<body>
+<svg>
+ <foreignObject>
+ <dialog id="dialog">
+ Dialog should be centered.
+ <div style="position: fixed; top: 0px; left: 0px">This fixed positioned element is aligned to viewport top-left.</div>
+ </dialog>
+ </foreignObject>
+</svg>
+<script>
+dialog.showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-transformed-ancestor.tentative.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-transformed-ancestor.tentative.html
new file mode 100644
index 0000000000..527d508252
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-transformed-ancestor.tentative.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<title>Test that a fixed positioned child of a dialog is aligned to the viewport</title>
+<head>
+<link rel="match" href="fixed-position-child-with-fixed-position-cb-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+::backdrop {
+ display: none;
+}
+#dialog {
+ outline: none;
+}
+</style>
+</head>
+<body>
+<div style="scale: 1">
+ <dialog id="dialog">
+ Dialog should be centered.
+ <div style="position: fixed; top: 0px; left: 0px">This fixed positioned element is aligned to viewport top-left.</div>
+ </dialog>
+</div>
+<script>
+dialog.showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-will-change-ancestor.tentative.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-will-change-ancestor.tentative.html
new file mode 100644
index 0000000000..e9db7321cd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/fixed-position-child-with-will-change-ancestor.tentative.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<title>Test that a fixed positioned child of a dialog is aligned to the viewport</title>
+<head>
+<link rel="match" href="fixed-position-child-with-fixed-position-cb-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+::backdrop {
+ display: none;
+}
+#dialog {
+ outline: none;
+}
+</style>
+</head>
+<body>
+<div style="will-change: transform">
+ <dialog id="dialog">
+ Dialog should be centered.
+ <div style="position: fixed; top: 0px; left: 0px">This fixed positioned element is aligned to viewport top-left.</div>
+ </dialog>
+</div>
+<script>
+dialog.showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/focus-after-close.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/focus-after-close.html
new file mode 100644
index 0000000000..93baf65cf6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/focus-after-close.html
@@ -0,0 +1,229 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Test focus is moved to the previously focused element when dialog is closed</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>
+
+<body>
+<input />
+<dialog>
+ <button id="button1">This is a button1</button>
+ <button id="button2">This is a button2</button>
+ <button id="button3">This is a button3</button>
+</dialog>
+<script>
+
+// Test focus is moved to the previously focused element
+function test_move_to_previously_focused(showModal) {
+ const input = document.querySelector("input");
+ input.focus();
+ const dialog = document.querySelector("dialog");
+ if (showModal) {
+ dialog.showModal();
+ } else {
+ dialog.show();
+ }
+ dialog.close();
+
+ assert_equals(document.activeElement, input);
+}
+
+// Test focus is moved to the previously focused element with some complex dialog usage
+async function test_move_to_previously_focused_with_complex_dialog_usage(showModal) {
+ const input = document.querySelector("input");
+ input.focus();
+ const dialog = document.querySelector("dialog");
+ if (showModal) {
+ dialog.showModal();
+ } else {
+ dialog.show();
+ }
+
+ const button1 = document.getElementById("button1");
+ const button2 = document.getElementById("button2");
+ const button3 = document.getElementById("button3");
+
+ await test_driver.click(button1);
+ await test_driver.click(button2);
+ await test_driver.click(button3);
+
+ dialog.close();
+
+ assert_equals(document.activeElement, input);
+}
+
+// Test focus is moved to the previously focused element even if that element moved in between
+function test_element_move_in_between_show_close(showModal) {
+ const input = document.querySelector("input");
+ input.focus();
+ const dialog = document.querySelector("dialog");
+
+ assert_equals(input.nextElementSibling, dialog, "Element is in correct position");
+
+ if (showModal) {
+ dialog.showModal();
+ } else {
+ dialog.show();
+ }
+
+ document.body.appendChild(input);
+ assert_not_equals(input.nextElementSibling, dialog, "Element should have moved");
+
+ dialog.close();
+ assert_equals(document.activeElement, input, "Focus should be restored to previously focused input");
+
+ // Clean up
+ document.body.insertBefore(input, dialog);
+}
+
+// Test focus is moved to the previously focused element even if that element moved to shadow root in between
+function test_element_move_to_shadow_root_in_between_show_close(showModal) {
+ const input = document.querySelector("input");
+ input.focus();
+ const dialog = document.querySelector("dialog");
+
+ assert_equals(input.nextElementSibling, dialog, "Element is in correct position");
+
+ if (showModal) {
+ dialog.showModal();
+ } else {
+ dialog.show();
+ }
+
+ const shadowHost = document.createElement("div");
+ const shadowRoot = shadowHost.attachShadow({mode: "open"});
+ shadowRoot.appendChild(input);
+ document.body.appendChild(shadowHost);
+
+ assert_not_equals(input.nextElementSibling, dialog, "Element should have moved");
+
+ dialog.close();
+ assert_equals(shadowRoot.activeElement, input, "Focus should be restored to previously focused input");
+ assert_equals(document.activeElement, shadowHost, "document.activeElement should be previously focused input's shadow DOM host");
+
+ // Clean up
+ document.body.insertBefore(input, dialog);
+ shadowHost.remove();
+}
+
+// Test focus is moved to <body> if the previously focused
+// element can't be focused
+function test_move_to_body_if_fails(showModal) {
+ const input = document.querySelector("input");
+ input.focus();
+ const dialog = document.querySelector("dialog");
+ if (showModal) {
+ dialog.showModal();
+ } else {
+ dialog.show();
+ }
+ dialog.close();
+ input.remove();
+ assert_equals(document.activeElement, document.body);
+
+ // Clean up
+ document.body.insertBefore(input, dialog);
+}
+
+// Test focus is moved to shadow host if the previously
+// focused element is a shadow node.
+function test_move_to_shadow_host(showModal) {
+ const shadowHost = document.createElement("div");
+
+ const shadowRoot = shadowHost.attachShadow({mode: "open"});
+ shadowRoot.appendChild(document.createElement("input"));
+
+ document.body.appendChild(shadowHost);
+ const inputElement = shadowRoot.querySelector("input");
+ inputElement.focus();
+
+ assert_equals(document.activeElement, shadowHost);
+ assert_equals(shadowRoot.activeElement, inputElement);
+
+ const dialog = document.querySelector("dialog");
+ if (showModal) {
+ dialog.showModal();
+ } else {
+ dialog.show();
+ }
+ dialog.close();
+
+ assert_equals(document.activeElement, shadowHost);
+ assert_equals(shadowRoot.activeElement, inputElement);
+
+ // Clean up
+ shadowHost.remove();
+}
+
+// Test moving the focus doesn't scroll the viewport
+async function test_move_focus_dont_scroll_viewport(showModal) {
+ const outViewPortButton = document.createElement("button");
+ outViewPortButton.style.top = (window.innerHeight + 10).toString() + "px";
+ outViewPortButton.style.position = "absolute";
+ document.body.appendChild(outViewPortButton);
+
+ await new Promise(resolve => {
+ document.addEventListener("scroll", resolve, { once: true });
+ outViewPortButton.focus();
+ });
+
+ // Since the outViewPortButton is focused, so the viewport should be
+ // scrolled to it
+ assert_true(document.documentElement.scrollTop > 0 );
+
+ const dialog = document.querySelector("dialog");
+ if (showModal) {
+ dialog.showModal();
+ } else {
+ dialog.show();
+ }
+
+ window.scrollTo(0, 0);
+ assert_equals(document.documentElement.scrollTop, 0);
+
+ dialog.close();
+ assert_equals(document.documentElement.scrollTop, 0);
+
+ assert_equals(document.activeElement, outViewPortButton);
+}
+
+test(() => {
+ test_move_to_previously_focused(true);
+ test_move_to_previously_focused(false);
+}, "Focus should be moved to the previously focused element (Simple dialog usage)");
+
+promise_test(async () => {
+ await test_move_to_previously_focused_with_complex_dialog_usage(true);
+ await test_move_to_previously_focused_with_complex_dialog_usage(false);
+}, "Focus should be moved to the previously focused element (Complex dialog usage)");
+
+test(() => {
+ test_element_move_in_between_show_close(true);
+ test_element_move_in_between_show_close(false);
+}, "Focus should be moved to the previously focused element even if it has moved in between show/close");
+
+test(() => {
+ test_element_move_to_shadow_root_in_between_show_close(true);
+ test_element_move_to_shadow_root_in_between_show_close(false);
+}, "Focus should be moved to the previously focused element even if it has moved to shadow DOM root in between show/close");
+
+test(() => {
+ test_move_to_body_if_fails(true);
+ test_move_to_body_if_fails(false);
+}, "Focus should be moved to the body if the previously focused element is removed");
+
+test(() => {
+ test_move_to_shadow_host(true);
+ test_move_to_shadow_host(false);
+}, "Focus should be moved to the shadow DOM host if the previouly focused element is a shadow DOM node");
+
+promise_test(async () => {
+ await test_move_focus_dont_scroll_viewport(true);
+ await test_move_focus_dont_scroll_viewport(false);
+}, "Focus should not scroll if the previously focused element is outside the viewport");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/focus-previous-iframe.tentative.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/focus-previous-iframe.tentative.html
new file mode 100644
index 0000000000..c31daa4876
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/focus-previous-iframe.tentative.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Test focus is moved to the previously focused element when dialog is closed</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>
+
+<body>
+<dialog>Dialog in parent</dialog>
+
+<iframe srcdoc="<input><dialog> Dialog in child </dialog>"></iframe>
+
+<input>
+<script>
+test(() => {
+ window.onload = function() {
+ const iframe = document.querySelector("iframe");
+ const input = iframe.contentDocument.querySelector("input");
+ // <input> in the child document is focused
+ input.focus();
+
+ const dialog = document.querySelector("dialog");
+ // <dialog> in the parent document is opened
+ dialog.showModal();
+ dialog.close();
+
+ assert_equals(document.activeElement, iframe);
+ assert_equals(iframe.contentDocument.activeElement, input);
+ }
+}, "Focus should move back from parent document to child document");
+
+test(() => {
+ window.onload = function() {
+ const iframe = document.querySelector("iframe");
+ const input = document.querySelector("input");
+ // <input> in the parent document is focused
+ input.focus();
+
+ const dialog = iframe.contentDocument.querySelector("dialog");
+
+ // <dialog> in the child document is focused
+ dialog.showModal();
+ dialog.close();
+
+ assert_equals(document.activeElement, input);
+ }
+}, "Focus should move back from child document to parent document");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/green-dialog-and-backdrop.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/green-dialog-and-backdrop.html
new file mode 100644
index 0000000000..cd23c32a06
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/green-dialog-and-backdrop.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<link rel="stylesheet" href="resources/dialog.css">
+<style>
+body { background: red; }
+
+.backdrop {
+ display: block;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+
+.backdrop,
+.pseudodialog {
+ background: green;
+}
+</style>
+<body>
+<div class="backdrop"></div>
+<div class="pseudodialog">PASS if no red shows</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-does-not-match-disabled-selector.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-does-not-match-disabled-selector.html
new file mode 100644
index 0000000000..b3b0c0a929
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-does-not-match-disabled-selector.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+button {
+ color: green;
+}
+
+button:disabled {
+ color: red;
+}
+
+.trigger-style-recalc {
+ /* No change, we just need a new style recalculation. */
+ font-weight:bold;
+}
+</style>
+</head>
+<body style="color: green">
+<button>The test passes if this is in green.</button>
+<dialog></dialog>
+<script>
+"use strict";
+test(function() {
+ document.querySelector('dialog').showModal();
+ var button = document.querySelector('button');
+ button.classList.add('trigger-style-recalc');
+ var color = document.defaultView.getComputedStyle(button).getPropertyValue('color');
+ assert_equals(color, 'rgb(0, 128, 0)');
+}, "Tests inert elements do not match the :disabled selector.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-focus-in-frames.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-focus-in-frames.html
new file mode 100644
index 0000000000..2ccc133285
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-focus-in-frames.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=242848">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe height=400 width=600 id="main-iframe">
+ <frameset rows="*" cols="50,50">
+ <frame src="resources/inert-focus-in-frames-frame1.html">
+ <frame src="resources/inert-focus-in-frames-frame2.html">
+ </frameset>
+</iframe>
+
+<script>
+let framesLoadedResolver = null;
+const framesLoadedPromise = new Promise(resolve => framesLoadedResolver = resolve);
+framesLoaded = 0;
+numFrames = 4;
+
+function frameLoaded() {
+ framesLoaded++;
+ if (framesLoaded == numFrames)
+ framesLoadedResolver();
+}
+var mainIframe = document.getElementById('main-iframe');
+mainIframe.contentDocument.write(mainIframe.textContent);
+mainIframe.contentDocument.close();
+mainIframe.contentWindow.frames[1].window.onload = frameLoaded;
+window.onload = frameLoaded;
+
+promise_test(async () => {
+ await framesLoadedPromise;
+
+ function testFocus(element, expectFocus) {
+ let focusedElement = null;
+ element.addEventListener('focus', function() { focusedElement = element; }, false);
+ element.focus();
+ if (expectFocus) {
+ assert_equals(focusedElement, element, element.id);
+ } else {
+ assert_not_equals(focusedElement, element, element.id);
+ }
+ }
+
+ // Opening a modal dialog in frame1. It blocks other nodes in its document.
+ const frame1 = mainIframe.contentWindow.frames[0].document;
+ frame1.querySelector('dialog').showModal();
+
+ testFocus(frame1.querySelector('.target'), false);
+ const iframe = frame1.querySelector('#iframe1').contentDocument;
+ testFocus(iframe.querySelector('.target'), true);
+
+ // Even a modal dialog in the iframe is blocked by the modal dialog in the parent frame1.
+ iframe.querySelector('dialog').showModal();
+ testFocus(iframe.querySelector('button'), false);
+
+ // An iframe within a modal dialog can still be focused.
+ var dialogIframe = frame1.querySelector('#iframe-in-dialog').contentDocument;
+ testFocus(dialogIframe.querySelector('.target'), true);
+
+ // A modal dialog does not block nodes in a sibling frame.
+ var frame2 = mainIframe.contentWindow.frames[1].document;
+ testFocus(frame2.querySelector('.target'), true);
+
+ // Closing the dialog in frame1. The modal dialog in the iframe does not block nodes in its parent.
+ frame1.querySelector('dialog').close();
+ testFocus(iframe.querySelector('.target'), false);
+ testFocus(frame1.querySelector('.target'), true);
+
+}, 'Tests inert node focusing across frames and iframes.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-inlines.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-inlines.html
new file mode 100644
index 0000000000..5ee4113985
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-inlines.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=241699">
+<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>
+
+<p>
+To test manually, click on all the "Click me"s.
+The test fails if you see red.
+</p>
+
+<style>
+dialog {
+ width: 50px;
+}
+</style>
+
+<a id="a" href="javascript:void(0)">Click me</a>
+<button id="button">Click me</button>
+<div id="div" style="background-color: blue; width: 50px; height: 50px">Click meeee</div>
+<span id="span">Click me</span>
+<div id="dialog-parent" style="width: 50px; height: 50px">
+ <span id="dialog-sibling">Click meeee</span>
+ <dialog></dialog>
+</div>
+
+<script>
+promise_test(async () => {
+ async function clickOn(element) {
+ let absoluteTop = 0;
+ let absoluteLeft = 0;
+ for (let parentNode = element; parentNode; parentNode = parentNode.offsetParent) {
+ absoluteLeft += parentNode.offsetLeft;
+ absoluteTop += parentNode.offsetTop;
+ }
+
+ const x = Math.round(absoluteLeft + element.offsetWidth / 2);
+ const y = Math.round(absoluteTop + element.offsetHeight / 2);
+ const actions = new test_driver.Actions()
+ .pointerMove(x, y)
+ .pointerDown()
+ .pointerUp()
+ .pointerMove(0, 0);
+ await actions.send();
+ }
+
+ function eventFiredOnInertElement(e) {
+ e.target.style.background = 'red';
+ inertElementFiredOn = true;
+ }
+
+ inertElements = ['a', 'button', 'div', 'span']
+ inertElements.forEach(function(id) {
+ element = document.getElementById(id);
+ element.addEventListener('click', eventFiredOnInertElement);
+ element.addEventListener('mousemove', eventFiredOnInertElement);
+ });
+
+ document.addEventListener('click', function(e) {
+ document.firedOn = true;
+ });
+
+ document.getElementById('dialog-parent').addEventListener('click', function(e) {
+ e.target.firedOn = true;
+ });
+
+ document.querySelector('dialog').showModal();
+ for (const id of inertElements) {
+ expectedTarget = document;
+ if (id == 'dialog-sibling')
+ expectedTarget = document.getElementById('dialog-parent')
+ element = document.getElementById(id);
+ inertElementFiredOn = false;
+ expectedTarget.firedOn = false;
+ await clickOn(element);
+ assert_false(inertElementFiredOn, 'clicking on ' + id);
+ assert_true(expectedTarget.firedOn, 'clicking on ' + id);
+ }
+}, 'Tests that inert inlines do not receive mouse events.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-label-focus.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-label-focus.html
new file mode 100644
index 0000000000..61e3ddeaf2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-label-focus.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=242848">
+<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>
+
+<label for="submit">Label for Submit</label>
+<dialog>
+ <input id="text" type="text">
+ <input id="submit" type="submit">
+</dialog>
+
+<script>
+promise_test(async () => {
+ async function clickOn(element) {
+ let absoluteTop = 0;
+ let absoluteLeft = 0;
+ for (let parentNode = element; parentNode; parentNode = parentNode.offsetParent) {
+ absoluteLeft += parentNode.offsetLeft;
+ absoluteTop += parentNode.offsetTop;
+ }
+
+ const x = Math.round(absoluteLeft + element.offsetWidth / 2);
+ const y = Math.round(absoluteTop + element.offsetHeight / 2);
+ const actions = new test_driver.Actions()
+ .pointerMove(x, y)
+ .pointerDown()
+ .pointerUp()
+ .pointerMove(0, 0);
+ await actions.send();
+ }
+
+ document.querySelector('dialog').showModal();
+ document.querySelector('#text').focus();
+
+ label = document.querySelector('label');
+ submit = document.querySelector('#submit');
+ label.focus();
+ assert_equals(document.activeElement, submit,
+ 'label.focus() should send focus to the target.');
+
+ await clickOn(label);
+ assert_not_equals(document.activeElement, label,
+ 'Clicking the label should not focus the label.');
+ assert_not_equals(document.activeElement, submit,
+ 'Clicking the label should not focus the submit input.');
+}, 'Tests focusing of an inert label for a non-inert target.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-not-highlighted-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-not-highlighted-ref.html
new file mode 100644
index 0000000000..1b757ecf62
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-not-highlighted-ref.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+body p, span {
+ -webkit-user-select: none;
+ user-select: none;
+}
+
+::backdrop {
+ display: none;
+}
+</style>
+</head>
+<body>
+<p>Test that inert nodes are not painted as being selected. The test passes if
+none of the text outside the dialog is highlighted when selected.</p>
+
+<p>Although not shown as selected, the inert nodes are in window.getSelection()
+and copied to the clipboard, which is the same behavior as user-select:
+none (crbug.com/147490).</p>
+
+<br><span>This text shouldn't be highlighted as selected.</span>
+
+<dialog>
+ <div id="selectable">I'm selectable.</div>
+</dialog>
+
+<script>
+dialog = document.querySelector('dialog');
+dialog.showModal();
+selectable = document.querySelector('#selectable');
+window.getSelection().selectAllChildren(selectable);
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-not-highlighted.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-not-highlighted.html
new file mode 100644
index 0000000000..f6db38ed72
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-not-highlighted.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="match" href="inert-node-is-not-highlighted-ref.html">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#inert-subtrees">
+<style>
+::backdrop {
+ display: none;
+}
+</style>
+</head>
+<body>
+<p>Test that inert nodes are not painted as being selected. The test passes if
+none of the text outside the dialog is highlighted when selected.</p>
+
+<p>Although not shown as selected, the inert nodes are in window.getSelection()
+and copied to the clipboard, which is the same behavior as user-select:
+none (crbug.com/147490).</p>
+
+<br> <!-- Needed to the trigger the bug. -->
+This text shouldn't be highlighted as selected.
+
+<dialog>
+ <div id="selectable">I'm selectable.</div>
+</dialog>
+
+<script>
+dialog = document.querySelector('dialog');
+dialog.showModal();
+document.execCommand('SelectAll');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-uneditable.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-uneditable.html
new file mode 100644
index 0000000000..9141a383b0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-uneditable.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=252071">
+<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>
+
+<span id="not-editable" contenteditable>I'm not editable while the dialog is showing.</span>
+<dialog>
+ <span id="editable" contenteditable>I'm editable.</span>
+</dialog>
+
+<script>
+promise_test(async () => {
+ async function clickOn(element) {
+ let absoluteTop = 0;
+ let absoluteLeft = 0;
+ for (let parentNode = element; parentNode; parentNode = parentNode.offsetParent) {
+ absoluteLeft += parentNode.offsetLeft;
+ absoluteTop += parentNode.offsetTop;
+ }
+
+ const x = Math.round(absoluteLeft + element.offsetWidth / 2);
+ const y = Math.round(absoluteTop + element.offsetHeight / 2);
+ const actions = new test_driver.Actions()
+ .pointerMove(x, y)
+ .pointerDown()
+ .pointerUp()
+ .pointerMove(0, 0);
+ await actions.send();
+ }
+
+ dialog = document.querySelector('dialog');
+ dialog.showModal();
+ notEditable = document.querySelector('#not-editable');
+ editable = document.querySelector('#editable');
+
+ await clickOn(notEditable);
+ oldValue = notEditable.textContent;
+ await (new test_driver.Actions().keyDown('a').keyUp('a').send());
+ assert_equals(notEditable.textContent, oldValue);
+
+ await clickOn(editable);
+ oldValue = editable.textContent;
+ await (new test_driver.Actions().keyDown('a').keyUp('a').send());
+ assert_not_equals(editable.textContent, oldValue);
+
+ notEditable.remove();
+ editable.remove();
+}, 'Test that inert nodes cannot be edited. The test passes if the only text you can edit is in the dialog.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-unfocusable.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-unfocusable.html
new file mode 100644
index 0000000000..74379f50e2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-unfocusable.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html id="html" tabindex="1">
+<head>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#blocked-by-a-modal-dialog">
+<meta name="assert" content="Checks that, when opening modal dialogs, inert nodes are not focusable.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body id="body" tabindex="1">
+<style>
+dialog {
+ outline: none;
+}
+</style>
+<dialog id="top-dialog" tabindex="1" style="width: 100px; top: 30px"><button id="top-dialog-button">I get focus</button></dialog>
+<dialog id="bottom-dialog" tabindex="-1" style="width: 100px; bottom: 30px"><button id="bottom-dialog-button">I don't get focus.</button></dialog>
+<div id="container">
+ <input id="text" type="text">
+ <input id="datetime" type="datetime">
+ <input id="color" type="color">
+ <select id="select">
+ <optgroup id="optgroup">
+ <option id="option">Option</option>
+ </optgroup>
+ </select>
+ <div id="contenteditable-div" contenteditable>I'm editable</div>
+ <span id="tabindex-span" tabindex="0">I'm tabindexed.</div>
+ <embed id="embed" type="application/x-blink-test-plugin" width=100 height=100></embed>
+ <a id="anchor" href="">Link</a>
+</div>
+<script>
+"use strict";
+// The test passses if only the topmost dialog and its button are focusable.
+
+function testFocus(element, expectFocus) {
+ test(function() {
+ var focusedElement = null;
+ element.addEventListener('focus', function() { focusedElement = element; }, false);
+ element.focus();
+ var theElement = element;
+ if (expectFocus) {
+ assert_equals(focusedElement, theElement);
+ } else {
+ assert_not_equals(focusedElement, theElement);
+ }
+ }, `#${CSS.escape(element.id)} is ${expectFocus ? "" : "not "} focusable`);
+}
+
+function testTree(element, expectFocus) {
+ if (element.nodeType == Node.ELEMENT_NODE)
+ testFocus(element, expectFocus);
+ var childNodes = element.childNodes;
+ for (var i = 0; i < childNodes.length; i++)
+ testTree(childNodes[i], expectFocus);
+}
+
+var bottomDialog = document.getElementById('bottom-dialog');
+var topDialog = document.getElementById('top-dialog');
+setup(function() {
+ bottomDialog.showModal();
+ topDialog.showModal();
+ add_completion_callback(function() {
+ topDialog.close();
+ bottomDialog.close();
+ });
+});
+
+testFocus(document.documentElement, false);
+testFocus(document.body, false);
+testTree(topDialog, true);
+testTree(bottomDialog, false);
+testTree(document.getElementById('container'), false);
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-unselectable.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-unselectable.html
new file mode 100644
index 0000000000..2889e1e90a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-node-is-unselectable.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=252071">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+Here is a text node you can't select while the dialog is open.
+<dialog>I'm selectable.</dialog>
+
+<script>
+test(() => {
+ const dialog = document.querySelector('dialog');
+ dialog.showModal();
+ document.execCommand('SelectAll');
+ assert_equals(window.getSelection().toString(), "I'm selectable.");
+}, 'Test that inert nodes cannot be selected. The test passes if the only text you can select is inside the dialog.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest.html
new file mode 100644
index 0000000000..579aca7775
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Hit-testing with SVG made inert by modal dialog</title>
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#inert">
+<meta assert="assert" content="SVG made inert by modal dialog should be unreachable with hit-testing">
+<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/testdriver-actions.js"></script>
+
+<div id="wrapper">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
+ <rect width="500" height="500" id="target" fill="red">
+ </svg>
+</div>
+
+<dialog id="dialog">Content behind the open modal dialog should not be clickable</dialog>
+
+<style>
+dialog::backdrop {
+ display: none;
+}
+</style>
+
+<script>
+const dialog = document.getElementById("dialog");
+const wrapper = document.getElementById("wrapper");
+const target = document.getElementById("target");
+
+promise_test(async function() {
+ dialog.showModal();
+ this.add_cleanup(() => dialog.close());
+
+ let reachedTarget = false;
+ target.addEventListener("mousedown", () => {
+ reachedTarget = true;
+ }, { once: true });
+
+ let wrapperRect = wrapper.getBoundingClientRect();
+ await new test_driver.Actions()
+ .pointerMove(wrapperRect.x + 1, wrapperRect.y + 1, { origin: "viewport" })
+ .pointerDown()
+ .send();
+
+ assert_false(target.matches(":active"), "target is not active");
+ assert_false(target.matches(":hover"), "target is not hovered");
+ assert_false(reachedTarget, "target didn't get event");
+}, "Hit-testing doesn't reach contents of an inert SVG");
+
+promise_test(async function() {
+ assert_false(dialog.open, "dialog is closed");
+
+ let reachedTarget = false;
+ target.addEventListener("mousedown", () => {
+ reachedTarget = true;
+ }, { once: true });
+
+ await new test_driver.Actions()
+ .pointerMove(0, 0, { origin: wrapper })
+ .pointerDown()
+ .send();
+
+ assert_true(target.matches(":active"), "target is active");
+ assert_true(reachedTarget, "target got event");
+}, "Hit-testing can reach contents of a no longer inert SVG");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inertness-with-modal-dialogs-and-iframes.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inertness-with-modal-dialogs-and-iframes.html
new file mode 100644
index 0000000000..1a509f7f36
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/inertness-with-modal-dialogs-and-iframes.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Inertness with modal dialogs and iframes</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#inert">
+<meta name="assert" content="Checks that a modal dialog marks outer nodes as inert,
+ but only in its document, not in the parent browsing context,
+ nor in nested browsing contexts.">
+<div id="log"></div>
+<div id="wrapper">
+ (main document: outer text)
+ <iframe id="outerIframe" srcdoc="
+ <div id='wrapper'>
+ (outer iframe: outer text)
+ <dialog id='dialog' style='display: block'>
+ (outer iframe: dialog)
+ </dialog>
+ </div>
+ "></iframe>
+ <dialog id="dialog" style="display: block">
+ (main document: dialog)
+ <iframe id="innerIframe" srcdoc="
+ <div id='wrapper'>
+ (inner iframe: outer text)
+ <dialog id='dialog' style='display: block'>
+ (inner iframe: dialog)
+ </dialog>
+ </div>
+ "></iframe>
+ </dialog>
+</div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const innerIframeWindow = innerIframe.contentWindow;
+const outerIframeWindow = outerIframe.contentWindow;
+promise_setup(async () => {
+ for (let global of [innerIframeWindow, outerIframeWindow]) {
+ if (global.location.href === "about:blank" ||
+ global.document.readyState !== "complete") {
+ await new Promise(resolve => {
+ global.frameElement.addEventListener("load", resolve, {once: true});
+ });
+ }
+ }
+});
+add_completion_callback(() => {
+ for (let global of [window, innerIframeWindow, outerIframeWindow]) {
+ global.getSelection().removeAllRanges();
+ }
+});
+
+function checkSelection(global, expectedText) {
+ const selection = global.getSelection();
+ selection.selectAllChildren(global.wrapper);
+
+ // Remove whitespace between parentheses since it varies among browsers,
+ // but that's not relevant to this test.
+ const actualText = selection.toString().replace(/\)\s*\(/g, ")(").trim();
+ assert_equals(actualText, expectedText);
+}
+
+function showModals(test, globals) {
+ for (let global of globals) {
+ global.dialog.showModal();
+ test.add_cleanup(() => { global.dialog.close(); });
+ }
+}
+
+promise_test(async function() {
+ checkSelection(window, "(main document: outer text)(main document: dialog)");
+ checkSelection(innerIframeWindow, "(inner iframe: outer text)(inner iframe: dialog)");
+ checkSelection(outerIframeWindow, "(outer iframe: outer text)(outer iframe: dialog)");
+}, "Initially, no node is inert");
+
+promise_test(async function() {
+ showModals(this, [outerIframeWindow]);
+
+ checkSelection(window, "(main document: outer text)(main document: dialog)");
+ checkSelection(innerIframeWindow, "(inner iframe: outer text)(inner iframe: dialog)");
+ checkSelection(outerIframeWindow, "(outer iframe: dialog)");
+}, "Modal dialog in the outer iframe marks outer nodes in that iframe as inert.");
+
+promise_test(async function() {
+ showModals(this, [innerIframeWindow]);
+
+ checkSelection(window, "(main document: outer text)(main document: dialog)");
+ checkSelection(innerIframeWindow, "(inner iframe: dialog)");
+ checkSelection(outerIframeWindow, "(outer iframe: outer text)(outer iframe: dialog)");
+}, "Modal dialog in the inner iframe marks outer nodes in that iframe as inert.");
+
+promise_test(async function() {
+ showModals(this, [innerIframeWindow, outerIframeWindow]);
+
+ checkSelection(window, "(main document: outer text)(main document: dialog)");
+ checkSelection(innerIframeWindow, "(inner iframe: dialog)");
+ checkSelection(outerIframeWindow, "(outer iframe: dialog)");
+}, "Modal dialogs in both iframes mark outer nodes in these iframes as inert.");
+
+promise_test(async function() {
+ showModals(this, [window]);
+
+ checkSelection(window, "(main document: dialog)");
+ checkSelection(innerIframeWindow, "(inner iframe: outer text)(inner iframe: dialog)");
+ checkSelection(outerIframeWindow, "(outer iframe: outer text)(outer iframe: dialog)");
+}, "Modal dialog in the main document marks outer nodes as inert. Contents of the outer iframe aren't marked as inert.");
+
+promise_test(async function() {
+ showModals(this, [innerIframeWindow, window]);
+
+ checkSelection(window, "(main document: dialog)");
+ checkSelection(innerIframeWindow, "(inner iframe: dialog)");
+ checkSelection(outerIframeWindow, "(outer iframe: outer text)(outer iframe: dialog)");
+}, "Modal dialogs in the main document and inner iframe mark outer nodes as inert. Contents of the outer iframe aren't marked as inert.");
+
+promise_test(async function() {
+ showModals(this, [outerIframeWindow, window]);
+
+ checkSelection(window, "(main document: dialog)");
+ checkSelection(innerIframeWindow, "(inner iframe: outer text)(inner iframe: dialog)");
+ checkSelection(outerIframeWindow, "(outer iframe: dialog)");
+}, "Modal dialogs in the main document and outer iframe mark outer nodes as inert. Contents of the outer iframe aren't marked as inert.");
+
+promise_test(async function() {
+ showModals(this, [innerIframeWindow, outerIframeWindow, window]);
+
+ checkSelection(window, "(main document: dialog)");
+ checkSelection(innerIframeWindow, "(inner iframe: dialog)");
+ checkSelection(outerIframeWindow, "(outer iframe: dialog)");
+}, "Modal dialogs in the main document and both iframes mark outer nodes as inert. Contents of the outer iframe aren't marked as inert.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-ancestor-is-inert.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-ancestor-is-inert.html
new file mode 100644
index 0000000000..c6bcb5d4ca
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-ancestor-is-inert.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=329407">
+<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>
+#ancestor {
+ position: absolute;
+ height: 50px;
+ width: 50px;
+ top: 200px;
+ left: 100px;
+ border: 1px solid;
+}
+
+dialog {
+ height: 50px;
+ width: 50px;
+ top: 200px;
+ left: 200px;
+ margin: 0;
+}
+
+dialog::backdrop {
+ display: none;
+}
+</style>
+
+<div id="ancestor">
+ <dialog></dialog>
+</div>
+
+<script>
+promise_test(async () => {
+ async function clickOn(element) {
+ const rect = element.getBoundingClientRect();
+ const actions = new test_driver.Actions()
+ .pointerMove(rect.left + rect.width / 2, rect.top + rect.height / 2)
+ .pointerDown()
+ .pointerUp();
+ await actions.send();
+ }
+
+ const div = document.querySelector('#ancestor');
+ const dialog = document.querySelector('dialog');
+ dialog.showModal();
+
+ const handledEvent = {};
+ document.addEventListener('click', function(event) {
+ handledEvent['document'] = true;
+ });
+
+ document.body.addEventListener('click', function(event) {
+ handledEvent['body'] = true;
+ // body should get a event only via bubbling.
+ if (event.target != dialog) {
+ assert_unreached('body was targeted for an click event');
+ div.style.backgroundColor = 'red';
+ }
+ });
+
+ div.addEventListener('click', function(event) {
+ handledEvent['div'] = true;
+ // div should get a event only via bubbling.
+ if (event.target != dialog) {
+ assert_unreached('div was targeted for an click event');
+ div.style.backgroundColor = 'red';
+ }
+ });
+
+ dialog.addEventListener('click', function(event) {
+ handledEvent['dialog'] = true;
+ dialog.style.backgroundColor = 'green';
+ if (event.target != dialog) {
+ assert_unreached('dialog was not targeted for a click event');
+ dialog.style.backgroundColor = 'red';
+ }
+ });
+
+ const nodes = [ 'document', 'body', 'div', 'dialog' ];
+ nodes.map(function(node) { handledEvent[node] = false; });
+ await clickOn(div);
+ assert_true(handledEvent.document, 'Clicking on ancestor.');
+ assert_false(handledEvent.body, 'Clicking on ancestor.');
+ assert_false(handledEvent.dialog, 'Clicking on ancestor.');
+ assert_false(handledEvent.div, 'Clicking on ancestor.');
+ handledEvent.document = false;
+
+ await clickOn(dialog);
+ assert_true(handledEvent.document, 'Clicking on dialog.');
+ assert_true(handledEvent.body, 'Clicking on dialog.');
+ assert_true(handledEvent.dialog, 'Clicking on dialog.');
+ assert_true(handledEvent.div, 'Clicking on dialog.');
+}, 'Test that ancestors of modal dialog are inert.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop-opacity-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop-opacity-ref.html
new file mode 100644
index 0000000000..b50e6ae026
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop-opacity-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<link rel="stylesheet" href="resources/dialog.css">
+<style>
+.backdrop {
+ display: block;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+
+ background-color: green;
+ opacity: 0.5;
+}
+</style>
+<body>
+<div class="backdrop"></div>
+<div class="pseudodialog">Test passes if you see a green backdrop at half opacity.</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop-opacity.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop-opacity.html
new file mode 100644
index 0000000000..09c31ce2af
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop-opacity.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="match" href="modal-dialog-backdrop-opacity-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#user-agent-level-style-sheet-defaults">
+<style>
+dialog::backdrop {
+ background-color: rgb(0, 128, 0);
+ opacity: 0.5;
+}
+dialog:focus {
+ outline: none;
+}
+</style>
+<body>
+<dialog>Test passes if you see a green backdrop at half opacity.</dialog>
+<script>
+document.querySelector('dialog').showModal();
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop-ref.html
new file mode 100644
index 0000000000..d703b7f28e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<style>
+.dialog-default-ua-style {
+ position: absolute;
+ overflow: auto;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ margin: auto;
+ border: solid;
+ padding: 1em;
+ background: white;
+ color: black;
+}
+
+#dialog {
+ margin: auto;
+ height: 100px;
+ width: 100px;
+ top: 100px;
+ z-index: 1000;
+ background: green;
+}
+
+#backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0,0,0,0.1);
+ z-index: 100;
+}
+</style>
+<body>
+Test for the default user agent style of dialog::backdrop. The test passes if
+there is a green box, above a very lightly translucent gray box spanning the
+viewport.
+<div id="backdrop"></div>
+<div class="dialog-default-ua-style" id="dialog"></div>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop.html
new file mode 100644
index 0000000000..55d7132f8c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-backdrop.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<link rel="match" href="modal-dialog-backdrop-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#user-agent-level-style-sheet-defaults">
+<style>
+dialog {
+ top: 100px;
+ height: 100px;
+ width: 100px;
+ background: green;
+ outline: none;
+}
+</style>
+<body>
+Test for the default user agent style of dialog::backdrop. The test passes if
+there is a green box, above a very lightly translucent gray box spanning the
+viewport.
+<dialog></dialog>
+<script>
+document.querySelector('dialog').showModal();
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-blocks-mouse-events.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-blocks-mouse-events.html
new file mode 100644
index 0000000000..f6c0ec0ccb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-blocks-mouse-events.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.webkit.org/show_bug.cgi?id=110952">
+<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>
+
+<p>
+To test manually, move the mouse to the blue box, click, and then move the
+mouse outside. Then repeat for the red box. The test succeeds if both boxes
+turn green
+</p>
+
+<style>
+#inert-div {
+ height: 100px;
+ width: 100px;
+ background: blue;
+}
+
+dialog {
+ width: 100px;
+}
+
+dialog::backdrop {
+ display: none;
+}
+
+#dialog-div {
+ height: 100px;
+ width: 100px;
+ background: red;
+}
+</style>
+
+<div id="inert-div"></div>
+<dialog id="dialog">
+ <div id="dialog-div"></div>
+</dialog>
+
+<script>
+promise_test(async () => {
+ async function clickOn(element) {
+ const rect = element.getBoundingClientRect();
+ const actions = new test_driver.Actions()
+ .pointerMove(
+ Math.floor(rect.left + rect.width / 2),
+ Math.floor(rect.top + rect.height / 2))
+ .pointerDown()
+ .pointerUp()
+ .pointerMove(0, 0);
+ await actions.send();
+ }
+
+ dialog.showModal();
+
+ inertDivHandledEvent = false;
+ inertDiv = document.getElementById('inert-div');
+ eventFiredOnInertNode = function(event) {
+ inertDivHandledEvent = true;
+ inertDiv.style.backgroundColor = 'red';
+ };
+
+ events = ['mousedown', 'mouseup', 'click', 'mousemove', 'mouseover', 'mouseout'];
+ dialogDiv = document.getElementById('dialog-div');
+ handledEvents = {};
+ handledEvents.dialogDiv = {};
+ eventFiredOnDialog = function(event) {
+ handledEvents.dialogDiv[event.type] = true;
+ if (Object.keys(handledEvents.dialogDiv).length == events.length)
+ dialogDiv.style.backgroundColor = 'green';
+ };
+
+ handledEvents.document = {};
+ expectedEventCountForDocument = events.length - 1; // document won't get 'mouseout'
+ eventFiredOnDocument = function(event) {
+ handledEvents.document[event.type] = true;
+ if (Object.keys(handledEvents.document).length == document.expectedEventCount && !inertDivHandledEvent) {
+ inertDiv.style.backgroundColor = 'green';
+ }
+ };
+
+ for (let i = 0; i < events.length; ++i) {
+ inertDiv.addEventListener(events[i], eventFiredOnInertNode);
+ dialogDiv.addEventListener(events[i], eventFiredOnDialog);
+ document.addEventListener(events[i], eventFiredOnDocument);
+ }
+
+ await clickOn(inertDiv);
+ assert_false(inertDivHandledEvent, 'Clicking on inert box');
+ assert_equals(Object.keys(handledEvents.document).length, expectedEventCountForDocument, 'Clicking on inert box');
+
+ await clickOn(dialogDiv);
+ assert_false(inertDivHandledEvent, 'Clicking on non-inert box');
+ assert_equals(Object.keys(handledEvents.dialogDiv).length, events.length, 'Clicking on non-inert box');
+}, 'Ensure that mouse events are not dispatched to an inert node.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-display-contents-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-display-contents-ref.html
new file mode 100644
index 0000000000..7ac66f5095
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-display-contents-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<title>Reference: Test that display: contents; on modal dialog & ::backdrop acts like display: block</title>
+<meta charset="utf-8">
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<p>Test passes if there is a green dialog</p>
+<p>Dialog is display:block</p>
+<p>Dialog::backdrop is display:block</p>
+<dialog>Dialog Contents</dialog>
+<style>
+dialog {
+ background-color: green;
+}
+</style>
+<script>
+document.querySelector("dialog").showModal();
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-display-contents.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-display-contents.html
new file mode 100644
index 0000000000..032033de01
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-display-contents.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<title>Test that display: contents; on modal dialog & ::backdrop acts like display: block</title>
+<meta charset="utf-8">
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel="match" href="modal-dialog-display-contents-ref.html">
+<p>Test passes if there is a green dialog</p>
+<p>Dialog is display:<span id="computed-value"></span></p>
+<p>Dialog::backdrop is display:<span id="computed-value-backdrop"></span></p>
+<dialog>Dialog Contents</dialog>
+<style>
+dialog {
+ display: contents;
+ background-color: green;
+}
+dialog::backdrop {
+ display: contents;
+}
+</style>
+<script>
+dialog = document.querySelector("dialog");
+dialog.showModal();
+document.getElementById("computed-value").textContent = getComputedStyle(dialog).display;
+document.getElementById("computed-value-backdrop").textContent = getComputedStyle(dialog, "::backdrop").display;
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-generated-content-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-generated-content-ref.html
new file mode 100644
index 0000000000..10c9897c63
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-generated-content-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<style>
+#dialog {
+ position: absolute;
+ top: 100px;
+ left: 100px;
+ height: 100px;
+ width: 100px;
+ background: green;
+}
+
+#dialog-before {
+ position: absolute;
+ top: 0px;
+}
+
+#dialog-after {
+ position: absolute;
+ bottom: 0px;
+}
+
+#dialog-backdrop {
+ position: absolute;
+ top: 100px;
+ left: 300px;
+ height: 100px;
+ width: 100px;
+ background: green;
+}
+</style>
+<body>
+Test for a modal dialog with ::before, ::after, and ::backdrop. The test passes
+if there are two green boxes, one with the texts "::before" and "::after" in it.
+<div id="dialog">
+ <div id="dialog-before">::before</div>
+ <div id="dialog-after">::after</div>
+</div>
+<div id="dialog-backdrop"></div>
+<script>
+document.querySelector('dialog').showModal();
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-generated-content.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-generated-content.html
new file mode 100644
index 0000000000..96b97f8ec3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-generated-content.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="match" href="modal-dialog-generated-content-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ padding: 0px;
+ border: none;
+ margin: 0px;
+ top: 100px;
+ left: 100px;
+ height: 100px;
+ width: 100px;
+ background: green;
+ outline: none;
+}
+
+dialog::before {
+ content: '::before';
+ position: absolute;
+ top: 0px;
+}
+
+dialog::after {
+ content: '::after';
+ position: absolute;
+ bottom: 0px;
+}
+
+dialog::backdrop {
+ position: absolute;
+ top: 100px;
+ left: 300px;
+ height: 100px;
+ width: 100px;
+ background: green;
+ content: 'THIS TEXT SHOULD NOT BE SEEN';
+}
+
+dialog::backdrop::before {
+ content: '::backdrop::before';
+ position: absolute;
+ top: 0px;
+ background: red;
+}
+dialog::backdrop::after {
+ content: '::backdrop::after';
+ position: absolute;
+ bottom: 0px;
+ background: red;
+}
+</style>
+<body>
+Test for a modal dialog with ::before, ::after, and ::backdrop. The test passes
+if there are two green boxes, one with the texts "::before" and "::after" in it.
+<dialog></dialog>
+<script>
+document.querySelector('dialog').showModal();
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-iframe-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-iframe-ref.html
new file mode 100644
index 0000000000..b6c52b7d7d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-iframe-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<iframe></iframe>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-iframe.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-iframe.html
new file mode 100644
index 0000000000..f6440583fb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-iframe.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>Modal dialog inside iframe should not generate box</title>
+<link rel=match href="modal-dialog-in-iframe-ref.html">
+<link rel=help href="https://github.com/w3c/csswg-drafts/issues/6939">
+<link rel=help href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ background: red;
+ border-color: red;
+}
+</style>
+<iframe></iframe>
+<script>
+ const iframe = document.querySelector('iframe');
+ const dialog = document.createElement('dialog');
+ iframe.appendChild(dialog);
+ dialog.showModal();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-object-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-object-ref.html
new file mode 100644
index 0000000000..38e15c1d79
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-object-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<object width="200" type="image/svg+xml" data="../../../../images/100px-green-rect.svg"></object>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-object.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-object.html
new file mode 100644
index 0000000000..728748a7ee
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-object.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Modal dialog inside object should not generate box</title>
+<link rel=match href="modal-dialog-in-object-ref.html">
+<link rel=help href="https://github.com/w3c/csswg-drafts/issues/6939">
+<link rel=help href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ background: green;
+ border-color: green;
+}
+</style>
+<object width="200" type="image/svg+xml" data="../../../../images/100px-green-rect.svg">
+ <dialog id="dialog"></dialog>
+</object>
+<script>
+document.getElementById('dialog').showModal();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer-ref.html
new file mode 100644
index 0000000000..c837503caf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+dialog {
+ background: green;
+ border-color: green;
+}
+div {
+ content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPAQMAAAABGAcJAAAAA1BMVEUAgACc+aWRAAAADElEQVR42mNgIAEAAAAtAAH7KhMqAAAAAElFTkSuQmCC);
+}
+</style>
+</head>
+<body>
+<p>The test passes if you see a green square near the top of the viewport.
+<div></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer.html
new file mode 100644
index 0000000000..75727b42f0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Modal dialog inside replaced renderer should not generate box</title>
+<link rel="match" href="modal-dialog-in-replaced-renderer-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ background: green;
+ border-color: green;
+}
+div {
+ content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPAQMAAAABGAcJAAAAA1BMVEUAgACc+aWRAAAADElEQVR42mNgIAEAAAAtAAH7KhMqAAAAAElFTkSuQmCC);
+}
+</style>
+</head>
+<body>
+<p>The test passes if you see a green square near the top of the viewport.
+<div>
+<dialog id="dialog"></dialog>
+</div>
+<script>
+document.getElementById('dialog').showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column-ref.html
new file mode 100644
index 0000000000..0310d1ba24
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+dialog {
+ background: green;
+ border-color: green;
+}
+</style>
+</head>
+<body>
+<p>The test passes if you see no green rectangle.
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column.html
new file mode 100644
index 0000000000..3d72826b96
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Modal dialog inside display: table-column should not generate box</title>
+<link rel="match" href="modal-dialog-in-table-column-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ background: green;
+ border-color: green;
+}
+div {
+ display: table-column;
+}
+</style>
+</head>
+<body>
+<p>The test passes if you see no green rectangle.
+<div>
+<dialog id="dialog"></dialog>
+</div>
+<script>
+document.getElementById('dialog').showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-visibility-hidden.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-visibility-hidden.html
new file mode 100644
index 0000000000..abba08cfde
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-visibility-hidden.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<title>Test that modal dialogs have visibility: visible set from the UA sheet</title>
+<meta charset="utf-8">
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/rendering.html#flow-content-3:is-modal">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div style="visibility: hidden">
+ <dialog>This is a dialog</dialog>
+</div>
+
+<script>
+let dialog = document.querySelector("dialog");
+
+test(t => {
+ dialog.show();
+ t.add_cleanup(() => dialog.close());
+ assert_equals(getComputedStyle(dialog).visibility, "hidden");
+}, "Non-modal dialog should let parent visibility inherit");
+
+test(t => {
+ dialog.showModal();
+ t.add_cleanup(() => dialog.close());
+ assert_equals(getComputedStyle(dialog).visibility, "visible");
+}, "Modal dialog should have visibility: visible by default in UA sheet");
+
+test(t => {
+ dialog.style.visibility = "hidden";
+ dialog.showModal();
+ t.add_cleanup(() => {
+ dialog.style.removeProperty("visibility");
+ dialog.close();
+ });
+ assert_equals(getComputedStyle(dialog).visibility, "hidden");
+}, "Modal dialog visibility should be overridable");
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-scroll-height.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-scroll-height.html
new file mode 100644
index 0000000000..638217f021
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-scroll-height.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:skobes@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=403136">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+body {
+ margin: 0;
+}
+.spacer {
+ height: 500px;
+}
+dialog {
+ border: 0;
+ margin: 0;
+ padding: 1px;
+}
+</style>
+<div class="spacer"></div>
+<dialog>
+ <div class="spacer"></div>
+</dialog>
+
+<script>
+test(() => {
+ document.querySelector('dialog').showModal();
+ assert_equals(document.scrollingElement.scrollHeight, window.innerHeight);
+}, 'dialogs should be centered before computing overflow.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-selection.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-selection.html
new file mode 100644
index 0000000000..0242080268
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-selection.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Content selection in modal dialog</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-ui-4/#content-selection">
+<meta name="assert" content="Checks that text can be selected in a modal dialog, except with 'user-select: none'.">
+
+<link rel="stylesheet" href="/fonts/ahem.css">
+<style>
+dialog {
+ font: 10px/1 Ahem;
+ text-align: center;
+}
+</style>
+
+<dialog>123456789A</dialog>
+
+<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/testdriver-actions.js"></script>
+<script>
+const dialog = document.querySelector("dialog");
+dialog.showModal();
+
+function selectSomeText() {
+ // Clear existing selection.
+ getSelection().removeAllRanges();
+
+ // The dialog contains 10 characters. Select the 6 ones at the center.
+ return new test_driver.Actions()
+ .pointerMove(-3e1, 0, {origin: dialog})
+ .pointerDown()
+ .pointerMove(+3e1, 0, {origin: dialog})
+ .pointerUp()
+ .send();
+}
+
+function clickOnBackdrop() {
+ getSelection().removeAllRanges();
+
+ return new test_driver.Actions()
+ .pointerMove(10, 10)
+ .pointerDown()
+ .pointerUp()
+ .send();
+}
+
+promise_test(async function() {
+ await selectSomeText();
+ assert_equals(getSelection().toString(), "345678");
+}, "By default, text inside a modal dialog can be selected");
+
+promise_test(async function() {
+ await clickOnBackdrop();
+ assert_equals(getSelection().toString(), "");
+}, "Clicking on backdrop doesn't select text");
+
+promise_test(async function() {
+ dialog.style.userSelect = "none";
+
+ await selectSomeText();
+ assert_equals(getSelection().toString(), "");
+
+ dialog.style.userSelect = "";
+}, "'user-select: none' prevents text from being selected");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-sibling-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-sibling-ref.html
new file mode 100644
index 0000000000..38b628c309
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-sibling-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+dialog {
+ background: green;
+ border-color: green;
+}
+</style>
+</head>
+<body>
+<p>Bug <a href="http://webkit.org/b/103477">103477</a>: Make
+NodeRenderingContext::parentRenderer and nextRenderer top layer aware
+<p>The test passes if you see a green rectangle in the center of the viewport.
+<dialog id="dialog"></dialog>
+<script>
+document.getElementById('dialog').showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-sibling.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-sibling.html
new file mode 100644
index 0000000000..85cc61890a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/modal-dialog-sibling.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="match" href="modal-dialog-sibling-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ background: green;
+ border-color: green;
+}
+</style>
+</head>
+<body>
+<p>Bug <a href="http://webkit.org/b/103477">103477</a>: Make
+NodeRenderingContext::parentRenderer and nextRenderer top layer aware
+<p>The test passes if you see a green rectangle in the center of the viewport.
+<div style="display: none" id="div"></div>
+<dialog id="dialog"></dialog>
+<script>
+document.getElementById('dialog').showModal();
+document.getElementById('dialog').offsetTop; // force a layout/renderer creation
+document.getElementById('div').style.display = 'block';
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/multiple-centered-dialogs.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/multiple-centered-dialogs.html
new file mode 100644
index 0000000000..70bb3810e2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/multiple-centered-dialogs.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+body {
+ height: 10000px;
+}
+
+dialog {
+ padding: 0;
+ height: 50px;
+ width: 50px;
+}
+
+#console {
+ position: fixed;
+}
+</style>
+
+<dialog id="top-dialog"></dialog>
+<dialog id="first-middle-dialog"></dialog>
+<dialog id="second-middle-dialog" style="left: 100px"></dialog>
+<dialog id="bottom-dialog"></dialog>
+
+<script>
+test(() => {
+ function documentHeight() {
+ // clientHeight is an integer, but we want the correct floating point
+ // value. Start a binary search at clientHeight-1 and clientHeight+1.
+ let min = document.documentElement.clientHeight;
+ let max = min + 1;
+ --min;
+
+ // binary search with media queries to find the correct height
+ for (let iter = 0; iter < 10; ++iter) {
+ let test = (min + max) / 2;
+ if (window.matchMedia(`(min-height: ${test}px)`).matches)
+ min = test;
+ else
+ max = test;
+ }
+ return min;
+ }
+ function expectedTop(dialog) {
+ let height = documentHeight();
+ return (height - dialog.getBoundingClientRect().height) / 2;
+ }
+
+ function showAndTest(id) {
+ dialog = document.getElementById(id);
+ dialog.showModal();
+ assert_approx_equals(dialog.getBoundingClientRect().top, expectedTop(dialog), 0.05, id);
+ }
+
+ showAndTest('top-dialog');
+
+ window.scroll(0, 100);
+ showAndTest('first-middle-dialog');
+ showAndTest('second-middle-dialog');
+
+ window.scroll(0, 200);
+ showAndTest('bottom-dialog');
+}, 'Test that multiple dialogs are centered properly.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/non-modal-dialog-does-not-block-mouse-events.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/non-modal-dialog-does-not-block-mouse-events.html
new file mode 100644
index 0000000000..b550ba288e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/non-modal-dialog-does-not-block-mouse-events.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.webkit.org/show_bug.cgi?id=110952">
+<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>
+
+<p>
+To test manually, click the red box. The test succeeds if the red box turns green.
+</p>
+
+<style>
+#div {
+ height: 100px;
+ width: 100px;
+ background: red;
+}
+</style>
+
+<div id="div"></div>
+<dialog id="dialog"></dialog>
+
+<script>
+promise_test(async () => {
+ async function clickOn(element) {
+ const actions = new test_driver.Actions()
+ .pointerMove(0, 0, {origin: element})
+ .pointerDown()
+ .pointerUp()
+ .pointerMove(0, 0);
+ await actions.send();
+ }
+
+ const dialog = document.getElementById('dialog');
+ dialog.show();
+
+ const div = document.getElementById('div');
+ div.firedOn = false;
+ div.addEventListener('click', function(event) {
+ div.firedOn = true;
+ div.style.backgroundColor = 'green';
+ });
+
+ await clickOn(div);
+
+ assert_true(div.firedOn);
+}, 'Ensure that non-modal dialogs do not block mouse events.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/non-modal-dialog-layout.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/non-modal-dialog-layout.html
new file mode 100644
index 0000000000..248bec86f6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/non-modal-dialog-layout.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=382594">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+/* Remove body margin and dialog styles for easier positioning expected values */
+body {
+ height: 10000px;
+ margin: 0;
+}
+
+dialog {
+ margin: 0;
+ border: 0;
+ padding: 0;
+ width: auto;
+ height: auto;
+ max-width: initial;
+ max-height: initial;
+}
+
+#absolute-div {
+ position: absolute;
+ top: 800px;
+ height: 50px;
+ width: 90%;
+}
+
+#relative-div {
+ position: relative;
+ top: 20px;
+ height: 30px;
+}
+</style>
+
+<div id="absolute-div">
+ <div id="relative-div">
+ <dialog id="dialog">It is my dialog.</dialog>
+ </div>
+</div>
+
+<script>
+test(() => {
+ const dialog = document.querySelector('#dialog');
+ const div = document.querySelector('#div-dialog');
+ const relativeContainer = document.querySelector('#relative-div');
+ const offset = 50;
+ dialog.style.top = offset + 'px';
+ dialog.style.left = offset + 'px';
+
+ dialog.style.position = 'absolute';
+ dialog.show();
+ assert_equals(
+ dialog.getBoundingClientRect().top,
+ relativeContainer.getBoundingClientRect().top + offset,
+ 'Absolute position.');
+ assert_equals(
+ dialog.getBoundingClientRect().left,
+ relativeContainer.getBoundingClientRect().left + offset,
+ 'Absolute position.');
+
+ dialog.style.position = 'static';
+ assert_true(dialog.open);
+ assert_equals(
+ dialog.getBoundingClientRect().top,
+ relativeContainer.getBoundingClientRect().top,
+ 'Static position.');
+ assert_equals(
+ dialog.getBoundingClientRect().left,
+ relativeContainer.getBoundingClientRect().left,
+ 'Static position.');
+ dialog.close();
+
+ dialog.style.position = 'relative';
+ dialog.show();
+ assert_equals(
+ dialog.getBoundingClientRect().top,
+ relativeContainer.getBoundingClientRect().top + offset,
+ 'Relative position.');
+ assert_equals(
+ dialog.getBoundingClientRect().left,
+ relativeContainer.getBoundingClientRect().left + offset,
+ 'Relative position.');
+ dialog.close();
+
+ dialog.style.position = 'fixed';
+ dialog.show();
+ assert_equals(
+ dialog.getBoundingClientRect().top,
+ offset,
+ 'Fixed position.');
+ assert_equals(
+ dialog.getBoundingClientRect().left,
+ offset,
+ 'Fixed position.');
+ dialog.close();
+}, 'Tests layout of non-modal dialogs.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/pass-dialog-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/pass-dialog-ref.html
new file mode 100644
index 0000000000..6f1a8fde21
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/pass-dialog-ref.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<style>
+ dialog::backdrop { background-color: black; }
+</style>
+<dialog id="dialog">PASS</dialog>
+<script>
+ dialog.showModal();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/remove-dialog-should-unblock-document.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/remove-dialog-should-unblock-document.html
new file mode 100644
index 0000000000..2f2fbad1fc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/remove-dialog-should-unblock-document.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body id="body">
+ <dialog>
+ This is a dialog
+ </dialog>
+ <input />
+<script>
+"use strict";
+function testFocus(element, expectFocus) {
+ var focusedElement = null;
+ element.addEventListener('focus', function() { focusedElement = element; }, false);
+ element.focus();
+ var theElement = element;
+ assert_equals(focusedElement === theElement, expectFocus, element.id);
+}
+
+test(function() {
+ var dialog = document.querySelector('dialog');
+ dialog.showModal();
+
+ var input = document.querySelector('input');
+ testFocus(input, false);
+
+ dialog.remove();
+ testFocus(input, true);
+}, "Test that removing dialog unblocks the document.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/removed-element-is-removed-from-top-layer-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/removed-element-is-removed-from-top-layer-ref.html
new file mode 100644
index 0000000000..0856d6f9f1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/removed-element-is-removed-from-top-layer-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href="resources/dialog.css">
+<style>
+.pseudodialog {
+ height: 100px;
+ width: 100px;
+}
+
+#bottomDialog {
+ background-color: blue;
+ top: 0px;
+}
+
+#topDialog {
+ background-color: green;
+ top: 50px;
+ left: 50px;
+}
+</style>
+</head>
+<body>
+<p>Bug <a href="https://bugs.webkit.org/show_bug.cgi?id=105489">105489</a>: Elements must be reattached when inserted/removed from top layer
+<p>The test passes if you see a green rectangle stacked on top of a blue rectangle.
+<div id="bottomDialog" class="pseudodialog"></div>
+<div id="topDialog" class="pseudodialog"></div>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/removed-element-is-removed-from-top-layer.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/removed-element-is-removed-from-top-layer.html
new file mode 100644
index 0000000000..b0e50e3869
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/removed-element-is-removed-from-top-layer.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="match" href="removed-element-is-removed-from-top-layer-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ height: 100px;
+ width: 100px;
+}
+
+::backdrop {
+ display: none;
+}
+
+#bottomDialog {
+ background-color: blue;
+ top: 231px;
+}
+
+#topDialog {
+ background-color: green;
+ top: 50px;
+ left: 50px;
+}
+</style>
+</head>
+<body>
+<p>Bug <a href="https://bugs.webkit.org/show_bug.cgi?id=105489">105489</a>: Elements must be reattached when inserted/removed from top layer
+<p>The test passes if you see a green rectangle stacked on top of a blue rectangle.
+<dialog id="bottomDialog"></dialog>
+<dialog id="topDialog"></dialog>
+<script>
+document.getElementById('topDialog').showModal();
+var bottomDialog = document.getElementById('bottomDialog');
+bottomDialog.showModal();
+bottomDialog.offsetTop; // force a layout
+var parent = bottomDialog.parentNode;
+parent.removeChild(bottomDialog);
+parent.appendChild(bottomDialog);
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/common.js b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/common.js
new file mode 100644
index 0000000000..c72ed7f19c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/common.js
@@ -0,0 +1,18 @@
+function waitUntilLoadedAndAutofocused() {
+ return new Promise(function(resolve) {
+ var loaded = false;
+ var autofocused = false;
+ window.addEventListener('load', function() {
+ loaded = true;
+ if (autofocused)
+ resolve();
+ }, false);
+ document.addEventListener('focusin', function() {
+ if (autofocused)
+ return;
+ autofocused = true;
+ if (loaded)
+ resolve();
+ }, false);
+ });
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/dialog.css b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/dialog.css
new file mode 100644
index 0000000000..571e7b8b6f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/dialog.css
@@ -0,0 +1,14 @@
+.pseudodialog {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ width: fit-content;
+ height: fit-content;
+ margin: auto;
+ border: solid;
+ padding: 1em;
+ background: white;
+ color: black;
+}
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/inert-focus-in-frames-frame1.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/inert-focus-in-frames-frame1.html
new file mode 100644
index 0000000000..c5566bc092
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/inert-focus-in-frames-frame1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+window.onload = parent.parent.frameLoaded;
+</script>
+</head>
+<body>
+<dialog id="dialog">
+ <button id="dialog-button" tabindex="0">Button</button>
+ <iframe id="iframe-in-dialog" srcdoc='
+ <input id="iframe-under-dialog-input" class="target" type="date">
+ '></iframe>
+</dialog>
+<input id="frame1-input" class="target" type="text">
+<iframe id="iframe1" srcdoc='
+ <dialog id="iframe-dialog">
+ <button id="iframe-dialog-button" tabindex="0">Button</button>
+ </dialog>
+ <input id="iframe-input" class="target" type="date">
+ <script>window.onload = parent.parent.parent.frameLoaded;</script>
+'>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/inert-focus-in-frames-frame2.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/inert-focus-in-frames-frame2.html
new file mode 100644
index 0000000000..167c56945d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/inert-focus-in-frames-frame2.html
@@ -0,0 +1 @@
+<div id="frame2-div" class="target" tabindex="0">Hello</div>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/submit.jpg b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/submit.jpg
new file mode 100644
index 0000000000..8909de2430
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/resources/submit.jpg
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/show-modal-focusing-steps.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/show-modal-focusing-steps.html
new file mode 100644
index 0000000000..6a2ad8c4a0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/show-modal-focusing-steps.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/common.js"></script>
+<script>
+promise_test(() => {
+ return waitUntilLoadedAndAutofocused().then(() => {
+ outerButton = document.getElementById('outer-button');
+ assert_equals(document.activeElement, outerButton);
+
+ // Test that focus goes to the dialog if the dialog has no focusable elements
+ var outerDialog = document.getElementById('outer-dialog');
+ outerDialog.showModal();
+ assert_equals(document.activeElement, outerDialog);
+
+ // Test that an autofocus element in the dialog gets focus.
+ var dialog = document.getElementById('dialog');
+ dialog.showModal();
+ autofocusButton = document.getElementById('autofocus-button');
+ assert_equals(document.activeElement, autofocusButton);
+ dialog.close();
+
+ // ... or else first focusable element in the dialog gets focus.
+ autofocusButton.parentNode.removeChild(autofocusButton);
+ dialog.showModal();
+ firstButton = document.getElementById('first-button');
+ assert_equals(document.activeElement, firstButton);
+ dialog.close();
+
+ // ... or else the dialog itself gets focus.;
+ var buttons = dialog.querySelectorAll('button');
+ for (var i = 0; i < buttons.length; ++i)
+ buttons[i].hidden = true;
+ dialog.showModal();
+ assert_equals(document.activeElement, dialog);
+ dialog.close();
+
+ document.getElementById('outer-dialog').close();
+ });
+}, "focus when a modal dialog is opened");
+</script>
+</head>
+<body>
+<button id="outer-button" autofocus></button>
+<dialog id="outer-dialog">
+ <dialog id="dialog" tabindex=0>
+ <button disabled></button>
+ <dialog>
+ <button autofocus></button>
+ </dialog>
+ <button id="first-button"></button>
+ <div>
+ <span>
+ <button id="autofocus-button" autofocus></button>
+ </span>
+ </div>
+ <button id="final-button"></button>
+ </dialog>
+</dialog>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/showmodal-in-shadow-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/showmodal-in-shadow-crash.html
new file mode 100644
index 0000000000..c9cc150099
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/showmodal-in-shadow-crash.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:futhark@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=850664">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=851384">
+
+<div id="dialog">
+ <div id="item"></div>
+</div>
+<script>
+const itemRoot = item.attachShadow({mode: 'open'});
+const dialogRoot = dialog.attachShadow({mode: 'open'});
+dialogRoot.innerHTML = '<dialog><slot></slot></dialog>';
+dialog.offsetTop;
+dialogRoot.firstChild.showModal();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/showmodal-shadow-sibling-frame-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/showmodal-shadow-sibling-frame-crash.html
new file mode 100644
index 0000000000..a1d792010d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/showmodal-shadow-sibling-frame-crash.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html class=test-wait>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:noel@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=804047">
+
+<template>
+ <custom-dialog></custom-dialog>
+</template>
+<div id=shadow></div>
+<iframe id=sibling></iframe>
+
+<script>
+customElements.define('custom-dialog',class extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({mode: 'open'}).innerHTML = '<dialog></dialog>';
+ }
+ show() {
+ this.shadowRoot.querySelector('dialog').showModal();
+ }
+});
+
+onload = () => {
+ const template = document.querySelector('template');
+ const content = document.importNode(template.content, true);
+ const dialog = content.querySelector('custom-dialog');
+ document.querySelector('div').appendChild(dialog);
+ dialog.show();
+ document.documentElement.classList.remove('test-wait');
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/simulated-click-inert.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/simulated-click-inert.html
new file mode 100644
index 0000000000..8ff8a7e86c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/simulated-click-inert.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=241699">
+<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>
+
+<p>Ensure that simulated click is still dispatched to an inert node.
+To test manually, click the CLICK ME label and verify it does change the value of the checkbox.</p>
+<div>
+</div>
+<input type="checkbox" id="target">
+<dialog><label for="target">CLICK ME</label></dialog>
+
+<script>
+promise_test(async () => {
+ async function clickOn(element) {
+ const actions = new test_driver.Actions()
+ .pointerMove(0, 0, {origin: element})
+ .pointerDown()
+ .pointerUp()
+ await actions.send();
+ }
+
+ document.querySelector('dialog').showModal();
+ await clickOn(document.querySelector('label'));
+ assert_true(document.getElementById('target').checked);
+}, 'Ensure that simulated click is still dispatched to an inert node.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/submit-dialog-close-event.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/submit-dialog-close-event.html
new file mode 100644
index 0000000000..5954993d19
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/submit-dialog-close-event.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=304827">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<dialog>
+ <form method="dialog">
+ <input id="goodbye" type="submit" value="Goodbye">
+ <input id="hello" type="submit" value="Hello">
+ </form>
+</dialog>
+
+<script>
+async_test(t => {
+ const dialog = document.querySelector('dialog');
+ dialog.show();
+ dialog.addEventListener('close', t.step_func(() => {
+ assert_false(dialog.open);
+ assert_equals(dialog.returnValue, 'Goodbye');
+
+ dialog.show();
+ dialog.addEventListener('close', t.step_func_done(() => {
+ assert_false(dialog.open);
+ assert_equals(dialog.returnValue, 'Hello');
+ }));
+ document.querySelector('#hello').click();
+ }), {once: true});
+
+ document.querySelector('#goodbye').click();
+}, 'Tests submitting a dialog on a close event triggered by a previous submission.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/synthetic-click-inert.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/synthetic-click-inert.html
new file mode 100644
index 0000000000..3be8213cd4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/synthetic-click-inert.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=241699">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+dialog {
+ width: 50px;
+}
+</style>
+
+<button>Click me</button>
+<div id="div">Click me too</div>
+<dialog></dialog>
+
+<script>
+test(() => {
+ dialog = document.querySelector('dialog');
+ dialog.showModal();
+
+ const button = document.querySelector('button');
+ const div = document.getElementById('div');
+ let clicked = false;
+
+ [button, div].forEach(function(element) {
+ element.addEventListener('click', () => clicked = true);
+
+ clicked = false;
+ element.click();
+ assert_true(clicked, 'Calling click() on ' + element.tagName);
+
+ clicked = false;
+ element.dispatchEvent(new Event('click'));
+ assert_true(clicked, 'Calling dispatchEvent() on ' + element.tagName);
+ });
+}, 'Test that inert nodes still get programmatic click events');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-containing-block-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-containing-block-ref.html
new file mode 100644
index 0000000000..40b72cf5ef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-containing-block-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href="resources/dialog.css">
+</head>
+<body>
+<p>
+This tests that a modal dialog's containing block is in the initial containing block and that it is unaffected by
+ancestor elements with overflow or opacity.
+<div class="pseudodialog" style="position: absolute; top: 100px; height: 250px; width: 90%; background-color: yellow">
+ This dialog should be onscreen with a width of 90% of the page. It is the child of an narrow element
+ positioned off screen, but the containing block of a top layer element is the initial containing block, so its
+ position and percent lengths are relative to that.
+</div>
+<div class="pseudodialog" style="position: absolute; top: 200px; left: 0px; height: 100px; background-color: cyan">
+ This dialog should be unaffected by its ancestor with overflow. It should not be clipped.
+</div>
+<div class="pseudodialog" style="position: absolute; top: 250px; left: 0px; background-color: magenta">
+ This dialog should be unaffected by its ancestor with opacity.
+</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-containing-block.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-containing-block.html
new file mode 100644
index 0000000000..0886c2cd2c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-containing-block.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="match" href="top-layer-containing-block-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+::backdrop {
+ display: none;
+}
+</style>
+</head>
+<body>
+<p>
+This tests that a modal dialog's containing block is in the initial containing block and that it is unaffected by
+ancestor elements with overflow or opacity.
+<div style="position: absolute; top: 400px; opacity: 0.3">
+ <dialog id="opaqueDialog" style="position: absolute; top: 250px; left: 0px; background-color: magenta; outline: none">
+ This dialog should be unaffected by its ancestor with opacity.
+ </dialog>
+</div>
+<div style="position: absolute; overflow: hidden; width: 500px; height: 150px; top: 400px; left: 300px">
+ <dialog id="unclippedDialog" style="position: absolute; top: 200px; left: 0px; height: 100px; background-color: cyan">
+ This dialog should be unaffected by its ancestor with overflow. It should not be clipped.
+ </dialog>
+</div>
+<div style="position: absolute; top: 1000px; left: 1000px; width: 20px;">
+ <dialog id="bottomDialog" style="position: absolute; top: 100px; height: 250px; width: 90%; background-color: yellow">
+ This dialog should be onscreen with a width of 90% of the page. It is the child of an narrow element
+ positioned off screen, but the containing block of a top layer element is the initial containing block, so its
+ position and percent lengths are relative to that.
+ </dialog>
+</div>
+<script>
+document.getElementById('bottomDialog').showModal();
+document.getElementById('unclippedDialog').showModal();
+document.getElementById('opaqueDialog').showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-display-none-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-display-none-ref.html
new file mode 100644
index 0000000000..1880668cc3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-display-none-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href="resources/dialog.css">
+<style>
+.pseudodialog {
+ height: 150px;
+ width: 150px;
+}
+</style>
+</head>
+<body>
+This tests that a top layer element is not rendered if it, or an ancestor, has display: none.
+It passes if you see a green rectangle stacked on top of a blue rectangle, and see no red rectangles.
+
+<div class="pseudodialog" style="top: 50px; background-color: blue"></div>
+<div class="pseudodialog" style="top: 100px; left: 50px; background-color: green"></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-display-none.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-display-none.html
new file mode 100644
index 0000000000..ba790c1db9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-display-none.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="match" href="top-layer-display-none-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ height: 150px;
+ width: 150px;
+ outline: none;
+}
+
+::backdrop {
+ display: none;
+}
+
+.red {
+ background-color: red;
+ top: 200px;
+}
+
+#bottomDialog {
+ background-color: blue;
+ top: 50px;
+ display: none;
+}
+
+#topDialog {
+ background-color: green;
+ top: 100px;
+ left: 50px;
+}
+</style>
+</head>
+<body>
+This tests that a top layer element is not rendered if it, or an ancestor, has display: none.
+It passes if you see a green rectangle stacked on top of a blue rectangle, and see no red rectangles.
+
+<dialog id="hiddenDialog" class="red" style="display: none;"></dialog>
+<div id="container">
+ <div>
+ <dialog id="displayNoneChild1" class="red"></dialog>
+ <dialog id="displayNoneChild2" class="red"></dialog>
+ </div>
+</div>
+<dialog id="bottomDialog"></dialog>
+<dialog id="topDialog"></dialog>
+<script>
+document.getElementById('hiddenDialog').showModal();
+document.getElementById('displayNoneChild1').showModal();
+document.getElementById('container').style.display = 'none';
+document.getElementById('displayNoneChild2').showModal();
+
+// Test that stacking works even if an element is added to the top layer when it has no renderer.
+document.getElementById('bottomDialog').showModal();
+document.getElementById('topDialog').showModal();
+document.getElementById('bottomDialog').style.display = 'block';
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-nesting-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-nesting-ref.html
new file mode 100644
index 0000000000..0a2936abbe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-nesting-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+.pseudodialog {
+ height: 150px;
+ width: 150px;
+ position: absolute;
+ top: 0; right: 0; bottom: 0; left: 0;
+ margin: auto;
+ border: solid;
+ padding: 1em;
+ background: white;
+ color: black;
+}
+</style>
+</head>
+<body>
+This tests that top layer elements are stacked correctly even if nested in the DOM tree.
+The test passes if you see no red rectangles and see 3 rectangles stacked in the following order (from bottom to top): yellow, blue, green.
+
+<div class="pseudodialog" style="top: 100px; background-color: yellow"></div>
+<div class="pseudodialog" style="top: 150px; left: 50px; background-color: blue"></div>
+<div class="pseudodialog" style="top: 200px; left: 100px; background-color: green"></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-nesting.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-nesting.html
new file mode 100644
index 0000000000..9e0616e952
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-nesting.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="match" href="top-layer-nesting-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+dialog {
+ height: 150px;
+ width: 150px;
+ outline: none;
+}
+
+::backdrop {
+ display: none;
+}
+
+#bottomDialog {
+ background-color: yellow;
+ top: 100px;
+ z-index: 1000;
+}
+
+#middleDialog {
+ background-color: blue;
+ top: 150px;
+ left: 50px;
+ z-index: -500;
+}
+
+#topDialog {
+ background-color: green;
+ top: 200px;
+ left: 100px;
+ z-index: -1000;
+}
+
+.red {
+ background-color: red;
+ top: 250px;
+ left: 0px;
+}
+</style>
+</head>
+<body>
+This tests that top layer elements are stacked correctly even if nested in the DOM tree.
+The test passes if you see no red rectangles and see 3 rectangles stacked in the following order (from bottom to top): yellow, blue, green.
+
+<dialog id="topDialog">
+ <dialog id="middleDialog">
+ <dialog id="bottomDialog">
+ <dialog id="hiddenDialog" class="red">
+ <dialog id="hiddenDialogChild" class="red"></dialog>
+ </dialog>
+ </dialog>
+ </dialog>
+</dialog>
+<script>
+document.getElementById('hiddenDialogChild').showModal();
+document.getElementById('hiddenDialog').showModal();
+document.getElementById('bottomDialog').showModal();
+document.getElementById('middleDialog').showModal();
+document.getElementById('topDialog').showModal();
+document.getElementById('hiddenDialog').close();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-clip.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-clip.html
new file mode 100644
index 0000000000..6e3c52aa02
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-clip.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<title>Test that parent clip-path does not affect top layer elements</title>
+<meta charset="utf-8">
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="match" href="green-dialog-and-backdrop.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+body { background: red; }
+
+#parent {
+ clip-path: circle(5%);
+}
+
+dialog::backdrop,
+dialog {
+ background: green;
+ outline: none;
+}
+</style>
+<body>
+<div id="parent">
+ <dialog>PASS if no red shows</dialog>
+</div>
+<script>
+ document.querySelector("dialog").showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-filter.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-filter.html
new file mode 100644
index 0000000000..589d539779
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-filter.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<title>Test that parent filter does not affect top layer elements</title>
+<meta charset="utf-8">
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="match" href="green-dialog-and-backdrop.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+body { background: red; }
+
+#parent {
+ filter: blur(100px) opacity(50%);
+}
+
+dialog::backdrop,
+dialog {
+ background: green;
+ position: absolute;
+ outline: none;
+}
+</style>
+<body>
+<div id="parent">
+ <dialog>PASS if no red shows</dialog>
+</div>
+<script>
+ document.querySelector("dialog").showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-mask.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-mask.html
new file mode 100644
index 0000000000..8ba3ed47c2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-mask.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<title>Test that parent mask does not affect top layer elements</title>
+<meta charset="utf-8">
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="match" href="green-dialog-and-backdrop.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+body { background: red; }
+
+#parent {
+ mask-image: radial-gradient(black, transparent);
+ mask-size: 100px;
+}
+
+dialog::backdrop,
+dialog {
+ background: green;
+ outline: none;
+}
+</style>
+<body>
+<div id="parent">
+ <dialog>PASS if no red shows</dialog>
+</div>
+<script>
+ document.querySelector("dialog").showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-opacity.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-opacity.html
new file mode 100644
index 0000000000..46c5de2a6d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-opacity.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<title>Test that parent opacity does not affect top layer elements</title>
+<meta charset="utf-8">
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="match" href="green-dialog-and-backdrop.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<link rel="help" href="https://bugs.webkit.org/show_bug.cgi?id=229317">
+<style>
+body { background: red; }
+
+#parent {
+ opacity: 0;
+}
+
+dialog::backdrop,
+dialog {
+ background: green;
+ outline: none;
+}
+</style>
+<body>
+<div id="parent">
+ <dialog>PASS if no red shows</dialog>
+</div>
+<script>
+ document.querySelector("dialog").showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-overflow-clip.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-overflow-clip.html
new file mode 100644
index 0000000000..d805954969
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-overflow-clip.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<title>Test that parent overflow: clip; does not affect top layer elements</title>
+<meta charset="utf-8">
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="match" href="green-dialog-and-backdrop.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+body { background: red; }
+
+#parent {
+ max-width: 0;
+ max-height: 0;
+ width: 0;
+ height: 0;
+ overflow: clip;
+ position: absolute;
+}
+
+dialog::backdrop,
+dialog {
+ background: green;
+ position: absolute;
+ outline: none;
+}
+</style>
+<body>
+<div id="parent">
+ <dialog>PASS if no red shows</dialog>
+</div>
+<script>
+ document.querySelector("dialog").showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-overflow-hidden.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-overflow-hidden.html
new file mode 100644
index 0000000000..f5389ddc09
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-overflow-hidden.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<title>Test that parent overflow: hidden; does not affect top layer elements</title>
+<meta charset="utf-8">
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="match" href="green-dialog-and-backdrop.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+body { background: red; }
+
+#parent {
+ max-width: 0;
+ max-height: 0;
+ width: 0;
+ height: 0;
+ overflow: hidden;
+ position: absolute;
+}
+
+dialog::backdrop,
+dialog {
+ background: green;
+ outline: none;
+}
+</style>
+<body>
+<div id="parent">
+ <dialog>PASS if no red shows</dialog>
+</div>
+<script>
+ document.querySelector("dialog").showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-overflow-scroll.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-overflow-scroll.html
new file mode 100644
index 0000000000..a230defeea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-overflow-scroll.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<title>Test that parent overflow: scroll; does not affect top layer elements</title>
+<meta charset="utf-8">
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="match" href="green-dialog-and-backdrop.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+body { background: red; }
+
+#parent {
+ max-width: 0;
+ max-height: 0;
+ width: 0;
+ height: 0;
+ overflow: scroll;
+ position: absolute;
+}
+
+dialog::backdrop,
+dialog {
+ background: green;
+ position: absolute;
+ outline: none;
+}
+</style>
+<body>
+<div id="parent">
+ <dialog>PASS if no red shows</dialog>
+</div>
+<script>
+ document.querySelector("dialog").showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-transform.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-transform.html
new file mode 100644
index 0000000000..ac6f3cffc3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-parent-transform.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<title>Test that parent transform does not affect top layer elements</title>
+<meta charset="utf-8">
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="match" href="green-dialog-and-backdrop.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+body { background: red; }
+
+#parent {
+ transform: scale(0);
+}
+
+dialog::backdrop,
+dialog {
+ background: green;
+ outline: none;
+}
+</style>
+<body>
+<div id="parent">
+ <dialog>PASS if no red shows</dialog>
+</div>
+<script>
+ document.querySelector("dialog").showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position-ref.html
new file mode 100644
index 0000000000..01eff8c4de
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+
+<style>
+dialog {
+ background-color: green;
+ height: 50px;
+ width: 50px;
+ border: none;
+ padding: 0;
+ margin: 0;
+
+ position: absolute;
+ top: 100px;
+ left: 100px;
+}
+</style>
+
+<dialog></dialog>
+
+<script>
+document.querySelector('dialog').showModal();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position-relative.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position-relative.html
new file mode 100644
index 0000000000..0dbef7d2ac
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position-relative.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.webkit.org/show_bug.cgi?id=106538">
+<link rel=match href="top-layer-position-ref.html">
+<meta name=assert content="Position relative computes to absolute in the top layer for dialog elements.">
+
+<style>
+dialog {
+ background-color: green;
+ height: 50px;
+ width: 50px;
+ border: none;
+ padding: 0;
+ margin: 0;
+
+ position: relative;
+ top: 100px;
+ left: 100px;
+}
+</style>
+
+<dialog></dialog>
+
+<script>
+document.querySelector('dialog').showModal();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position-static.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position-static.html
new file mode 100644
index 0000000000..86d8c89f88
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position-static.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=author href="mailto:falken@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<link rel=help href="https://bugs.webkit.org/show_bug.cgi?id=106538">
+<link rel=match href="top-layer-position-ref.html">
+<meta name=assert content="Position static computes to absolute in the top layer for dialog elements.">
+
+<style>
+dialog {
+ background-color: green;
+ height: 50px;
+ width: 50px;
+ border: none;
+ padding: 0;
+ margin: 0;
+
+ position: static;
+ top: 100px;
+ left: 100px;
+}
+</style>
+
+<dialog></dialog>
+
+<script>
+document.querySelector('dialog').showModal();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position.html
new file mode 100644
index 0000000000..1fdbca5c1e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-position.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+test(() => {
+ const dialog = document.createElement('dialog');
+ document.body.appendChild(dialog);
+
+ dialog.style = 'position:static';
+ assert_equals(getComputedStyle(dialog).position, 'static');
+ dialog.showModal();
+ assert_true(dialog.open);
+ assert_equals(getComputedStyle(dialog).position, 'absolute',
+ `dialog should be position:absolute when element.style has position:static.`);
+ dialog.close();
+ assert_false(dialog.open);
+
+ dialog.style = 'position:relative';
+ assert_equals(getComputedStyle(dialog).position, 'relative');
+ dialog.showModal();
+ assert_true(dialog.open);
+ assert_equals(getComputedStyle(dialog).position, 'absolute',
+ `dialog should be position:absolute when element.style has position:relative.`);
+ dialog.close();
+ assert_false(dialog.open);
+}, `Verifies that position:static and position:relative computed to position:absolute in the top layer.`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-remove-popover-attribute-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-remove-popover-attribute-ref.html
new file mode 100644
index 0000000000..7aadaf51b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-remove-popover-attribute-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Shown modal dialog where the popover attribute is removed</title>
+</head>
+<body>
+ <dialog popover style="padding: 2em"></dialog>
+ <script>
+ const d = document.querySelector("dialog");
+ d.showModal();
+ </script>
+</body>
+<html>
+
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-remove-popover-attribute.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-remove-popover-attribute.html
new file mode 100644
index 0000000000..3827e90c5b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-remove-popover-attribute.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<meta name="assert" content="Removing the popover attribute of a hidden popover should not remove the dialog from the top layer.">
+<head>
+ <title>Shown modal dialog where the popover attribute is removed</title>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/popover.html#hide-popover-algorithm">
+ <link rel="match" href="top-layer-remove-popover-attribute-ref.html">
+</head>
+<body>
+ <dialog popover style="padding: 2em"></dialog>
+ <script>
+ const d = document.querySelector("dialog");
+ d.showModal();
+ d.popover = null;
+ </script>
+</body>
+<html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-correct-order-remove-readd-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-correct-order-remove-readd-ref.html
new file mode 100644
index 0000000000..392d1ca46e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-correct-order-remove-readd-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href="resources/dialog.css">
+<style>
+.pseudodialog {
+ height: 100px;
+ width: 100px;
+}
+</style>
+</head>
+<body>
+<p>Bug <a href="https://bugs.webkit.org/show_bug.cgi?id=105489">105489</a>: Elements must be reattached when inserted/removed from top layer
+<p>The test passes if you see a green rectangle stacked on top of a blue rectangle.
+
+<div class="pseudodialog" style="top: 100px; background-color: blue"></div>
+<div class="pseudodialog" style="top: 150px; left: 50px; background-color: green"></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-correct-order-remove-readd.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-correct-order-remove-readd.html
new file mode 100644
index 0000000000..4fdd28820d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-correct-order-remove-readd.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="match" href="top-layer-stacking-correct-order-remove-readd-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-dialog-element">
+<style>
+dialog {
+ height: 100px;
+ width: 100px;
+ outline: none;
+}
+
+::backdrop {
+ display: none;
+}
+
+#bottomDialog {
+ background-color: blue;
+ top: 100px;
+}
+
+#topDialog {
+ background-color: green;
+ top: 150px;
+ left: 50px;
+}
+</style>
+</head>
+<body>
+<p>Bug <a href="https://bugs.webkit.org/show_bug.cgi?id=105489">105489</a>: Elements must be reattached when inserted/removed from top layer
+<p>The test passes if you see a green rectangle stacked on top of a blue rectangle.
+
+<dialog id="topDialog"></dialog>
+<dialog id="bottomDialog"></dialog>
+<script>
+var topDialog = document.getElementById('topDialog');
+var bottomDialog = document.getElementById('bottomDialog');
+topDialog.showModal();
+bottomDialog.showModal();
+topDialog.offsetTop; // force a layout
+topDialog.close();
+topDialog.showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-dynamic-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-dynamic-ref.html
new file mode 100644
index 0000000000..6ddb317633
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-dynamic-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href="resources/dialog.css">
+<style>
+.pseudodialog {
+ height: 150px;
+ width: 150px;
+}
+</style>
+</head>
+<body>
+This tests top layer element stacking order after dynamically calling show/close and removal from the DOM tree.
+The test passes if you see a green rectangle stacked on top of a blue rectangle, and see no red rectangles.
+
+<div class="pseudodialog" style="top: 50px; background-color: blue"></div>
+<div class="pseudodialog" style="top: 100px; left: 50px; background-color: green"></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-dynamic.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-dynamic.html
new file mode 100644
index 0000000000..ebccdc66cf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-dynamic.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="match" href="top-layer-stacking-dynamic-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-dialog-element">
+<style>
+dialog {
+ height: 150px;
+ width: 150px;
+ outline: none;
+}
+
+::backdrop {
+ display: none;
+}
+
+.red {
+ background-color: red;
+ top: 200px;
+}
+
+#bottomDialog {
+ background-color: blue;
+ top: 50px;
+}
+
+#topDialog {
+ background-color: green;
+ top: 100px;
+ left: 50px;
+}
+</style>
+</head>
+<body>
+This tests top layer element stacking order after dynamically calling show/close and removal from the DOM tree.
+The test passes if you see a green rectangle stacked on top of a blue rectangle, and see no red rectangles.
+
+<dialog id="topDialog"></dialog>
+<dialog id="bottomDialog"></dialog>
+<dialog id="removedDialog" class="red">
+ <dialog id="removedDialogChild" class="red"></dialog>
+</dialog>
+<script>
+document.getElementById('topDialog').showModal();
+var removedDialog = document.getElementById('removedDialog');
+removedDialog.showModal();
+document.getElementById('bottomDialog').showModal();
+document.getElementById('removedDialogChild').showModal();
+removedDialog.parentNode.removeChild(removedDialog);
+document.getElementById('topDialog').close();
+document.getElementById('topDialog').showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-ref.html
new file mode 100644
index 0000000000..b271ef47b4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking-ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href="resources/dialog.css">
+</head>
+<style>
+.box {
+ height: 150px;
+ width: 150px;
+}
+.container {
+ perspective: 500px;
+ border: 1px solid black;
+ background-color: magenta;
+}
+.transformed {
+ transform: rotateY(45deg);
+ background-color: cyan;
+}
+</style>
+<body>
+<div class="pseudodialog" style="position: fixed; top: 10px; z-index:3000">
+ This white box is the topmost modal dialog. It should be on top of everything.
+</div>
+<div style="position: absolute; top: 0px; z-index: 3; background-color: red; left: 0; right: 0; height: 200px;"></div>
+<div class="pseudodialog" style="position: absolute; top: 50px; background-color: green; width: 75%; height: 400px; z-index:2000; overflow: auto;">
+ This green box is also a modal dialog. It should be rendered above the red and yellow regions.
+ <div class="container box">
+ <div class="transformed box">A transform within the dialog's subtree.</div>
+ </div>
+ <div class="box" style="position: absolute; top:300px; z-index: 2; background-color: cyan">
+ This shows z-index stacking within the dialog's subtree. The cyan box should be on top of the magenta one.
+ </div>
+ <div class="box" style="position: absolute; top:350px; left:50px; z-index: 1; background-color: magenta"></div>
+ <div style="position: fixed; top: 90px; left: 30px; background-color: green">This is part of the green dialog.</div>
+</div>
+<div style="position: absolute; top: 100px; left: 0px; right: 0px; height: 200em; background-color: yellow; z-index:1000">
+</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking.tentative.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking.tentative.html
new file mode 100644
index 0000000000..6407ef23c2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-dialog-element/top-layer-stacking.tentative.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<!-- This tests that top layer elements are rendered above z-indexed elements
+and stacked in the correct order amongst themselves. Also, layer features like
+transforms and z-index are tested inside a top layer element subtree. -->
+<html>
+<head>
+<link rel="match" href="top-layer-stacking-ref.html">
+<link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
+<style>
+.box {
+ height:150px;
+ width:150px;
+}
+
+::backdrop {
+ display: none;
+}
+
+.container {
+ perspective: 500px;
+ border: 1px solid black;
+ background-color: magenta;
+}
+.transformed {
+ transform: rotateY(45deg);
+ background-color: cyan;
+}
+</style>
+</head>
+<body>
+<dialog id="hiddenDialog" style="display: none; color: red">This should not be displayed.</dialog>
+<dialog id="topDialog" style="position: fixed; top: 10px; z-index: -10;">
+ This white box is the topmost modal dialog. It should be on top of everything.
+</dialog>
+<div style="position: absolute; top: 0px; z-index: 3; background-color: red; left: 0; right: 0; height: 200px;">
+ <dialog id="bottomDialog" style="position: absolute; top: 50px; background-color: green; width: 75%; height: 400px;">
+ This green box is also a modal dialog. It should be rendered above the red and yellow regions.
+ <div class="container box">
+ <div class="transformed box">A transform within the dialog's subtree.</div>
+ </div>
+ <div class="box" style="position: absolute; top:300px; z-index: 2; background-color: cyan">
+ This shows z-index stacking within the dialog's subtree. The cyan box should be on top of the magenta one.
+ </div>
+ <div class="box" style="position: absolute; top:350px; left:50px; z-index: 1; background-color: magenta"></div>
+ <div style="position: fixed; top: 90px; left: 30px; background-color: green">This is part of the green dialog.</div>
+ </dialog>
+</div>
+<div style="position: absolute; top: 100px; left: 0px; right: 0px; height: 200em; background-color: yellow; z-index:1000">
+</div>
+<script>
+document.getElementById('bottomDialog').showModal();
+document.getElementById('topDialog').showModal();
+document.getElementById('hiddenDialog').showModal();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/activation-behavior.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/activation-behavior.html
new file mode 100644
index 0000000000..4a3693bd2d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/activation-behavior.html
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>summary element: activation behavior</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-summary-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<details id="happy-path-starts-closed">
+ <summary id="happy-path-starts-closed-summary">Summary</summary>
+ <p>Contents</p>
+</details>
+
+<details id="happy-path-starts-open" open>
+ <summary id="happy-path-starts-open-summary">Summary</summary>
+ <p>Contents</p>
+</details>
+
+<details id="details-not-being-rendered" style="display: none">
+ <summary id="details-not-being-rendered-summary">Summary</summary>
+ <p>Contents</p>
+</details>
+
+<details id="summary-not-being-rendered">
+ <summary id="summary-not-being-rendered-summary" style="display: none">Summary</summary>
+ <p>Contents</p>
+</details>
+
+<details id="has-preceding-element">
+ <span></span>
+ <summary id="has-preceding-element-summary">Summary</summary>
+ <p>Contents</p>
+</details>
+
+<details id="has-preceding-summary">
+ <summary>Summary 1</summary>
+ <summary id="has-preceding-summary-summary">Summary 2</summary>
+ <p>Contents</p>
+</details>
+
+<details id="has-preceding-summary-descendant">
+ <span><summary>Summary 1</summary></span>
+ <summary id="has-preceding-summary-descendant-summary">Summary 2</summary>
+ <p>Contents</p>
+</details>
+
+<details id="summary-nested">
+ <span><summary id="summary-nested-summary">Summary</summary></span>
+ <p>Contents</p>
+</details>
+
+<details id="toggle-tester">
+ <summary>Summary</summary>
+ <p>Contents</p>
+</details>
+
+<script>
+"use strict";
+
+testSummary(
+ "happy-path-starts-closed", false, true,
+ "Should open a closed details if all conditions are met"
+);
+
+testSummary(
+ "happy-path-starts-open", true, false,
+ "Should close an open details if all conditions are met"
+);
+
+testSummary(
+ "details-not-being-rendered", false, true,
+ "Should open a closed details even if the details is not being rendered"
+);
+
+testSummary(
+ "summary-not-being-rendered", false, true,
+ "Should open a closed details even if the summary is not being rendered"
+);
+
+testSummary(
+ "has-preceding-element", false, true,
+ "Should open a closed details if a span element precedes the summary"
+);
+
+testSummary(
+ "has-preceding-summary", false, false,
+ "Should stay closed if another summary element precedes the summary"
+);
+
+testSummary(
+ "has-preceding-summary-descendant", false, true,
+ "Should open a closed details if another summary element *nested inside a span* precedes the summary"
+);
+
+testSummary(
+ "summary-nested", false, false,
+ "Should stay closed if the summary element is nested inside a span element"
+);
+
+async_test(t => {
+ const details = document.getElementById("toggle-tester");
+ const summary = details.firstElementChild;
+
+ let timesToggleFired = 0;
+ details.addEventListener("toggle", t.step_func(() => {
+ ++timesToggleFired;
+ }));
+
+ t.step_timeout(() => {
+ assert_equals(timesToggleFired, 1, "Expected toggle to fire exactly once");
+ t.done();
+ }, 200);
+
+ summary.click();
+ summary.click();
+ summary.click();
+ summary.click();
+ Promise.resolve().then(() => summary.click());
+
+}, "toggle events should be coalesced even when using the activation behavior of a summary");
+
+function testSummary(detailsId, expectedBefore, expectedAfter, name) {
+ test(() => {
+ const details = document.getElementById(detailsId);
+ const summary = document.getElementById(detailsId + "-summary");
+
+ assert_equals(details.open, expectedBefore, "Before activation: expected open to be " + expectedBefore);
+ summary.click();
+ assert_equals(details.open, expectedAfter, "After activation: expected open to be " + expectedAfter);
+ }, name);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/anchor-with-inline-element.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/anchor-with-inline-element.html
new file mode 100644
index 0000000000..6910a5de93
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/anchor-with-inline-element.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>summary element: clicking on anchor containing inline element</title>
+<link rel="author" title="Yu Han" href="mailto:yuzhehan@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/C/#the-summary-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>
+
+<details id="details_i">
+ <summary>Anchor text is wrapped with &lt;i&gt; tag <a href="#with_i_tag"><i id="with_i">permalink</i></a></summary>
+ <p>asdf</p>
+</details>
+
+<details id="details_span">
+ <summary>This one uses &lt;span&gt;. <a href="#with_span_tag"><span id="with_span">permalink</span></a></summary>
+ <p>asdf</p>
+</details>
+
+<details id="details_svg">
+ <summary>
+ <svg style="width: 100px;" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+ <a href="#inside_svg_w_circle">
+ <circle id="svg_circle" cx="50" cy="40" r="35"/>
+ </a>
+ <a href="#inside_svg_w_text">
+ <text id="svg_text" x="50" y="90" text-anchor="middle">
+ &lt;circle&gt;
+ </text>
+ </a>
+ </svg>
+ </summary>
+ <p>asdf</p>
+</details>
+
+<script>
+function testClickingOnInlineElement(detailsId, targetId, expected, testName) {
+ const details = document.getElementById(detailsId);
+ const target = document.getElementById(targetId);
+ const test = async_test(testName);
+
+ const promise = new Promise((resolve, reject) => {
+ window.onhashchange = test.step_func_done(() => {
+ assert_false(details.open);
+ assert_equals(location.hash, expected);
+ resolve();
+ });
+ });
+
+ if (target.click) {
+ target.click();
+ }
+ else {
+ // svg element don't have click method
+ target.dispatchEvent(new MouseEvent('click', {
+ view: window,
+ bubbles: true,
+ cancelable: true
+ }));
+ }
+ return promise;
+};
+
+async function testAll() {
+ try {
+ await testClickingOnInlineElement("details_i", "with_i", "#with_i_tag", "Expected <a> containing <i> to navigate");
+ await testClickingOnInlineElement("details_span", "with_span", "#with_span_tag", "Expected <a> containing <span> to navigate");
+ await testClickingOnInlineElement("details_svg", "svg_circle", "#inside_svg_w_circle", "Expected <a>, inside svg, containing <circle> to navigate");
+ await testClickingOnInlineElement("details_svg", "svg_text", "#inside_svg_w_text", "Expected <a>, inside svg, containing <text> to navigate");
+ } catch (exception) {
+ assert_unreached("should NOT-THROW exception");
+ }
+};
+
+var allTests = async_test("Clicking on anchor with embedded inline element should navigate instead of opening details");
+testAll().then(()=>{ allTests.done(); });
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/anchor-without-link.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/anchor-without-link.html
new file mode 100644
index 0000000000..edaf786b25
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/anchor-without-link.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>summary element: clicking on anchor without link</title>
+<link rel="author" title="Di Zhang" href="mailto:dizhangg@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/C/#the-summary-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>
+
+<details id="details">
+ <summary><a id="no_inline">Details</a></summary>
+ <p>Text</p>
+</details>
+
+<details id="details_inline">
+ <summary><a><i id="has_inline">Details</i></a></summary>
+ <p>Text</p>
+</details>
+
+
+<script>
+
+async function testClickingOnAnchorWithoutLink (detailsId, targetId) {
+ const details = document.getElementById(detailsId);
+ const target = document.getElementById(targetId);
+ const initialLoc = location.hash;
+
+ assert_false(details.open);
+ target.click();
+ assert_true(details.open);
+ assert_equals(location.hash, initialLoc);
+}
+
+promise_test(() => testClickingOnAnchorWithoutLink('details', 'no_inline'),
+ "clicking on anchor without link should open details and not navigate.");
+
+promise_test(() => testClickingOnAnchorWithoutLink('details_inline', 'has_inline'),
+ "clicking on anchor without link, with embedded inline element should open details and not navigate.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/click-behavior-optional.tentative.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/click-behavior-optional.tentative.html
new file mode 100644
index 0000000000..4418413fef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/click-behavior-optional.tentative.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>summary element: click behavior</title>
+<link rel="author" title="Mu-An Chiou" href="mailto:hi@muan.co">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-summary-element">
+<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>
+
+<body>
+ <div id="log"></div>
+
+ <details id="details">
+ <summary id="summary">Summary</summary>
+ <p>Contents</p>
+ </details>
+</body>
+
+<script>
+ // This behavior is not specified by HTML standards, but setting focus on
+ // clicked summary tag is the current behavior on Chrome, Safari, and Firefox
+ // in both Windows and macOS.
+ async_test(t => {
+ const details = document.getElementById("details")
+ const summary = document.getElementById("summary")
+
+ t.step_timeout(() => {
+ details.addEventListener("toggle", t.step_func_done(function () {
+ assert_equals(details.open, true, "details should be open")
+ assert_equals(document.activeElement, summary, "active element should be summary")
+ t.done()
+ }))
+
+ new test_driver.click(summary)
+ }, 200)
+ }, "clicking on summary should open details and set focus on summary")
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/display-table-with-rt-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/display-table-with-rt-crash.html
new file mode 100644
index 0000000000..57cc45478e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/display-table-with-rt-crash.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=969619">
+<summary style="display:table;"><rt></rt></summary>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ test(()=> { }, "No crash");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/summary-untrusted-key-event.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/summary-untrusted-key-event.html
new file mode 100644
index 0000000000..21b66d52e7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-summary-element/summary-untrusted-key-event.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Summary</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<details>
+ <summary>Summary</summary>
+ Details
+</details>
+<script type="module">
+const details = document.querySelector("details");
+details.addEventListener("toggle",
+ (e) => assert_true(false, 'details should not be toggled'));
+
+const summary = document.querySelector("summary");
+summary.addEventListener("click",
+ (e) => assert_true(false, `summary should not be clicked`));
+
+test(() => {
+ // keyCode: Enter
+ summary.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ keyCode: 13,
+ })
+ );
+
+ // key: Enter
+ summary.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ key: "Enter",
+ })
+ );
+
+ // keyCode: Space
+ summary.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ keyCode: 32,
+ })
+ );
+
+ // key: Space
+ summary.dispatchEvent(
+ new KeyboardEvent("keypress", {
+ key: " ",
+ })
+ );
+}, `Dispatching untrusted keypress events to summary should not cause click event`);
+
+test(() => {
+ // keyCode: Enter
+ summary.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ keyCode: 13,
+ })
+ );
+ summary.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ keyCode: 13,
+ })
+ );
+
+ // key: Enter
+ summary.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ key: "Enter",
+ })
+ );
+ summary.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ key: "Enter",
+ })
+ );
+
+ // keyCode: Space
+ summary.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ keyCode: 32,
+ })
+ );
+ summary.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ keyCode: 32,
+ })
+ );
+
+ // key: Space
+ summary.dispatchEvent(
+ new KeyboardEvent("keydown", {
+ key: " ",
+ })
+ );
+ summary.dispatchEvent(
+ new KeyboardEvent("keyup", {
+ key: " ",
+ })
+ );
+}, `Dispatching untrusted keyup/keydown events to summary should not cause click event`);
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interfaces.html b/testing/web-platform/tests/html/semantics/interfaces.html
new file mode 100644
index 0000000000..dc7b600e0a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interfaces.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test of interfaces</title>
+<link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/">
+<link rel="help" href="https://webidl.spec.whatwg.org/#host-objects">
+<link rel="help" href="http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf#page=96">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=interfaces.js></script>
+<div id="log"></div>
+<script>
+function do_test(local_name, iface, variant) {
+ test(function() {
+ var e;
+ var i = "HTML" + iface + "Element";
+ if (variant === "useNS") {
+ // Use createElementNS here to preserve the case of local_name.
+ e = document.createElementNS("http://www.w3.org/1999/xhtml", local_name);
+ } else if (variant === "useParser") {
+ e = new DOMParser().parseFromString("<" + local_name + ">", "text/html").querySelector(local_name);
+ } else {
+ e = document.createElement(local_name);
+ }
+ assert_class_string(e, i,
+ "Element " + local_name + " should have " + i +
+ " as its primary interface.");
+ assert_true(e instanceof window[i],
+ "Element " + local_name + " should implement " + i + ".");
+ assert_true(e instanceof HTMLElement,
+ "Element " + local_name + " should implement HTMLElement.");
+ assert_true(e instanceof Element,
+ "Element " + local_name + " should implement Element.");
+ assert_true(e instanceof Node,
+ "Element " + local_name + " should implement Node.");
+ }, "Interfaces for " + local_name + ": " + variant);
+}
+
+// Some elements have weird parser behavior / insertion modes and would be
+// skipped by the parser, so skip those.
+function should_do_parser_test(local_name) {
+ return ![
+ "foo-BAR",
+ "tbody",
+ "td",
+ "tfoot",
+ "th",
+ "thead",
+ "tr",
+ "å-bar",
+ "caption",
+ "col",
+ "colgroup",
+ "frame",
+ "image",
+ "frameset",
+ ].includes(local_name)
+}
+
+elements.forEach(function(a) {
+ do_test(a[0], a[1], "useNS");
+
+ if (should_do_parser_test(a[0])) {
+ do_test(a[0], a[1], "useParser");
+ }
+
+ // Only run the createElement variant if the input is all-lowercase, because createElement
+ // case-folds to lowercase. Custom elements are required to use all-lowercase to implement
+ // HTMLElement, otherwise they use HTMLUnknownElement per spec. Example: "foo-BAR".
+ if (a[0] === a[0].toLowerCase()) {
+ do_test(a[0].toUpperCase(), a[1], "createElement");
+ }
+})
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interfaces.js b/testing/web-platform/tests/html/semantics/interfaces.js
new file mode 100644
index 0000000000..05fc82f767
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interfaces.js
@@ -0,0 +1,150 @@
+var elements = [
+ ["a", "Anchor"],
+ ["abbr", ""],
+ ["acronym", ""],
+ ["address", ""],
+ ["applet", "Unknown"],
+ ["area", "Area"],
+ ["article", ""],
+ ["aside", ""],
+ ["audio", "Audio"],
+ ["b", ""],
+ ["base", "Base"],
+ ["basefont", ""],
+ ["bdi", ""],
+ ["bdo", ""],
+ ["bgsound", "Unknown"],
+ ["big", ""],
+ ["blink", "Unknown"],
+ ["blockquote", "Quote"],
+ ["body", "Body"],
+ ["br", "BR"],
+ ["button", "Button"],
+ ["canvas", "Canvas"],
+ ["caption", "TableCaption"],
+ ["center", ""],
+ ["cite", ""],
+ ["code", ""],
+ ["col", "TableCol"],
+ ["colgroup", "TableCol"],
+ ["command", "Unknown"],
+ ["data", "Data"],
+ ["datalist", "DataList"],
+ ["dd", ""],
+ ["del", "Mod"],
+ ["details", "Details"],
+ ["dfn", ""],
+ ["dialog", "Dialog"],
+ ["dir", "Directory"],
+ ["directory", "Unknown"],
+ ["div", "Div"],
+ ["dl", "DList"],
+ ["dt", ""],
+ ["em", ""],
+ ["embed", "Embed"],
+ ["fieldset", "FieldSet"],
+ ["figcaption", ""],
+ ["figure", ""],
+ ["font", "Font"],
+ ["foo-BAR", "Unknown"], // not a valid custom element name
+ ["foo-bar", ""], // valid custom element name
+ ["foo", "Unknown"],
+ ["footer", ""],
+ ["form", "Form"],
+ ["frame", "Frame"],
+ ["frameset", "FrameSet"],
+ ["h1", "Heading"],
+ ["h2", "Heading"],
+ ["h3", "Heading"],
+ ["h4", "Heading"],
+ ["h5", "Heading"],
+ ["h6", "Heading"],
+ ["head", "Head"],
+ ["header", ""],
+ ["hgroup", ""],
+ ["hr", "HR"],
+ ["html", "Html"],
+ ["i", ""],
+ ["iframe", "IFrame"],
+ ["image", "Unknown"],
+ ["img", "Image"],
+ ["input", "Input"],
+ ["ins", "Mod"],
+ ["isindex", "Unknown"],
+ ["kbd", ""],
+ ["keygen", "Unknown"],
+ ["label", "Label"],
+ ["legend", "Legend"],
+ ["li", "LI"],
+ ["link", "Link"],
+ ["listing", "Pre"],
+ ["main", ""],
+ ["map", "Map"],
+ ["mark", ""],
+ ["marquee", "Marquee"],
+ ["menu", "Menu"],
+ ["meta", "Meta"],
+ ["meter", "Meter"],
+ ["mod", "Unknown"],
+ ["multicol", "Unknown"],
+ ["nav", ""],
+ ["nextid", "Unknown"],
+ ["nobr", ""],
+ ["noembed", ""],
+ ["noframes", ""],
+ ["noscript", ""],
+ ["object", "Object"],
+ ["ol", "OList"],
+ ["optgroup", "OptGroup"],
+ ["option", "Option"],
+ ["output", "Output"],
+ ["p", "Paragraph"],
+ ["param", "Param"],
+ ["permission", "Permission"],
+ ["picture", "Picture"],
+ ["plaintext", ""],
+ ["pre", "Pre"],
+ ["progress", "Progress"],
+ ["q", "Quote"],
+ ["quasit", "Unknown"],
+ ["rb", ""],
+ ["rp", ""],
+ ["rt", ""],
+ ["rtc", ""],
+ ["ruby", ""],
+ ["s", ""],
+ ["samp", ""],
+ ["script", "Script"],
+ ["section", ""],
+ ["select", "Select"],
+ ["slot", "Slot"],
+ ["small", ""],
+ ["source", "Source"],
+ ["spacer", "Unknown"],
+ ["span", "Span"],
+ ["strike", ""],
+ ["strong", ""],
+ ["style", "Style"],
+ ["sub", ""],
+ ["summary", ""],
+ ["sup", ""],
+ ["table", "Table"],
+ ["tbody", "TableSection"],
+ ["td", "TableCell"],
+ ["textarea", "TextArea"],
+ ["tfoot", "TableSection"],
+ ["th", "TableCell"],
+ ["thead", "TableSection"],
+ ["time", "Time"],
+ ["title", "Title"],
+ ["tr", "TableRow"],
+ ["track", "Track"],
+ ["tt", ""],
+ ["u", ""],
+ ["ul", "UList"],
+ ["var", ""],
+ ["video", "Video"],
+ ["wbr", ""],
+ ["xmp", "Pre"],
+ ["\u00E5-bar", "Unknown"], // not a valid custom element name
+];
diff --git a/testing/web-platform/tests/html/semantics/invokers/idlharness.tentative.html b/testing/web-platform/tests/html/semantics/invokers/idlharness.tentative.html
new file mode 100644
index 0000000000..b215f65813
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/idlharness.tentative.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:keithamus@github.com" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+
+<script>
+ idl_test(["invokers.tentative"], ["html", "dom"], (idl_array) => {
+ idl_array.add_objects({
+ InvokeEvent: ['new InvokeEvent("invoke")'],
+ });
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invokeelement-interface.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invokeelement-interface.tentative.html
new file mode 100644
index 0000000000..b003daf20d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invokeelement-interface.tentative.html
@@ -0,0 +1,93 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:keithamus@github.com" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<button id="invoker" invoketarget="invokee"></button>
+<div id="invokee"></div>
+
+<script>
+ test(function () {
+ assert_equals(invoker.invokeTargetElement, invokee);
+ }, "invokeTargetElement reflects invokee HTML element");
+
+ test(function () {
+ const div = document.body.appendChild(document.createElement("div"));
+ invoker.invokeTargetElement = div;
+ assert_equals(invoker.invokeTargetElement, div);
+ assert_equals(invoker.getAttribute("invoketarget"), "");
+ assert_false(invoker.hasAttribute("invokeaction"));
+ }, "invokeTargetElement reflects set value");
+
+ test(function () {
+ const host = document.body.appendChild(document.createElement("div"));
+ const shadow = host.attachShadow({ mode: "open" });
+ const button = shadow.appendChild(document.createElement("button"));
+ button.invokeTargetElement = invokee;
+ assert_equals(button.invokeTargetElement, invokee);
+ assert_equals(invoker.getAttribute("invoketarget"), "");
+ assert_false(invoker.hasAttribute("invokeaction"));
+ }, "invokeTargetElement reflects set value across shadow root into light dom");
+
+ test(function () {
+ const host = document.body.appendChild(document.createElement("div"));
+ const shadow = host.attachShadow({ mode: "open" });
+ const div = shadow.appendChild(document.createElement("div"));
+ invoker.invokeTargetElement = div;
+ assert_equals(invoker.invokeTargetElement, null);
+ assert_equals(invoker.getAttribute("invoketarget"), "");
+ assert_false(invoker.hasAttribute("invokeaction"));
+ }, "invokeTargetElement does not reflect set value inside shadowroot");
+
+ test(function () {
+ assert_throws_js(
+ TypeError,
+ function () {
+ invoker.invokeTargetElement = {};
+ },
+ "invokeTargetElement attribute must be an instance of Element",
+ );
+ }, "invokeTargetElement throws error on assignment of non Element");
+
+ test(function () {
+ assert_false(invoker.hasAttribute("invokeaction"));
+ assert_equals(invoker.invokeAction, "auto");
+ }, "invokeAction reflects 'auto' when attribute not present");
+
+ test(function () {
+ invoker.setAttribute("invokeaction", "");
+ assert_equals(invoker.getAttribute("invokeaction"), "");
+ assert_equals(invoker.invokeAction, "auto");
+ }, "invokeAction reflects 'auto' when attribute empty");
+
+ test(function () {
+ invoker.invokeAction = "fooBarBaz";
+ assert_equals(invoker.getAttribute("invokeaction"), "fooBarBaz");
+ assert_equals(invoker.invokeAction, "fooBarBaz");
+ }, "invokeAction reflects same casing");
+
+ test(function () {
+ invoker.invokeAction = "";
+ assert_equals(invoker.getAttribute("invokeaction"), "");
+ assert_equals(invoker.invokeAction, "auto");
+ }, "invokeAction reflects 'auto' when attribute empty 2");
+
+ test(function () {
+ invoker.invokeAction = [1, 2, 3];
+ assert_equals(invoker.getAttribute("invokeaction"), "1,2,3");
+ assert_equals(invoker.invokeAction, "1,2,3");
+ }, "invokeAction reflects tostring value");
+
+ test(function () {
+ invoker.invokeAction = [];
+ assert_equals(invoker.getAttribute("invokeaction"), "");
+ assert_equals(invoker.invokeAction, "auto");
+ }, "invokeAction reflects 'auto' when attribute set to []");
+
+ test(function () {
+ invoker.invokeAction = {};
+ assert_equals(invoker.invokeAction, "[object Object]");
+ }, "invokeAction reflects tostring value 2");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invokeevent-dispatch-shadow.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invokeevent-dispatch-shadow.tentative.html
new file mode 100644
index 0000000000..84337d5723
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invokeevent-dispatch-shadow.tentative.html
@@ -0,0 +1,104 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:keithamus@github.com" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<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>
+<script src="resources/invoker-utils.js"></script>
+
+<div id="div"></div>
+<button id="button"></button>
+
+<script>
+ test(function () {
+ const host = document.createElement("div");
+ const child = host.appendChild(document.createElement("p"));
+ const shadow = host.attachShadow({ mode: "closed" });
+ const slot = shadow.appendChild(document.createElement("slot"));
+ let childEvent = null;
+ let childEventTarget = null;
+ let childEventInvoker = null;
+ let hostEvent = null;
+ let hostEventTarget = null;
+ let hostEventInvoker = null;
+ slot.addEventListener(
+ "invoke",
+ (e) => {
+ childEvent = e;
+ childEventTarget = e.target;
+ childEventInvoker = e.invoker;
+ },
+ { once: true },
+ );
+ host.addEventListener(
+ "invoke",
+ (e) => {
+ hostEvent = e;
+ hostEventTarget = e.target;
+ hostEventInvoker = e.invoker;
+ },
+ { once: true },
+ );
+ const event = new InvokeEvent("invoke", {
+ bubbles: true,
+ invoker: slot,
+ composed: true,
+ });
+ slot.dispatchEvent(event);
+ assert_true(childEvent instanceof InvokeEvent, "slot saw invoke event");
+ assert_equals(
+ childEventTarget,
+ slot,
+ "target is child inside shadow boundary",
+ );
+ assert_equals(
+ childEventInvoker,
+ slot,
+ "invoker is child inside shadow boundary",
+ );
+ assert_equals(
+ hostEvent,
+ childEvent,
+ "event dispatch propagates across shadow boundary",
+ );
+ assert_equals(
+ hostEventTarget,
+ host,
+ "target is retargeted to shadowroot host",
+ );
+ assert_equals(
+ hostEventInvoker,
+ host,
+ "invoker is retargeted to shadowroot host",
+ );
+ }, "InvokeEvent propagates across shadow boundaries retargeting invoker");
+
+ test(function (t) {
+ const host = document.createElement("div");
+ document.body.append(host);
+ t.add_cleanup(() => host.remove());
+ const shadow = host.attachShadow({ mode: "open" });
+ const button = shadow.appendChild(document.createElement("button"));
+ const invokee = host.appendChild(document.createElement("div"));
+ button.invokeTargetElement = invokee;
+ let event = null;
+ let eventTarget = null;
+ let eventInvoker = null;
+ invokee.addEventListener(
+ "invoke",
+ (e) => {
+ event = e;
+ eventTarget = e.target;
+ eventInvoker = e.invoker;
+ },
+ { once: true },
+ );
+ button.click();
+ assert_true(event instanceof InvokeEvent);
+ assert_equals(eventTarget, invokee, "target is invokee");
+ assert_equals(eventInvoker, host, "invoker is host");
+ }, "cross shadow InvokeEvent retargets invoker to host element");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invokeevent-interface.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invokeevent-interface.tentative.html
new file mode 100644
index 0000000000..82910b3d44
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invokeevent-interface.tentative.html
@@ -0,0 +1,166 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:keithamus@github.com" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<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>
+<script src="resources/invoker-utils.js"></script>
+
+<div id="div"></div>
+<button id="button"></button>
+
+<script>
+ test(function () {
+ const event = new InvokeEvent("test");
+ assert_equals(event.action, "auto");
+ assert_readonly(event, "action", "readonly attribute value");
+ }, "action is a readonly defaulting to 'auto'");
+
+ test(function () {
+ const event = new InvokeEvent("test");
+ assert_equals(event.invoker, null);
+ assert_readonly(event, "invoker", "readonly attribute value");
+ }, "invoker is readonly defaulting to null");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: "sAmPle" });
+ assert_equals(event.action, "sAmPle");
+ }, "action reflects initialized attribute");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: undefined });
+ assert_equals(event.action, "auto");
+ }, "action set to undefined");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: null });
+ assert_equals(event.action, "null");
+ }, "action set to null");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: false });
+ assert_equals(event.action, "false");
+ }, "action set to false");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: "" });
+ assert_equals(event.action, "");
+ }, "action explicitly set to empty string");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: true });
+ assert_equals(event.action, "true");
+ }, "action set to true");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: 0.5 });
+ assert_equals(event.action, "0.5");
+ }, "action set to a number");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: [] });
+ assert_equals(event.action, "");
+ }, "action set to []");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: [1, 2, 3] });
+ assert_equals(event.action, "1,2,3");
+ }, "action set to [1, 2, 3]");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: { sample: 0.5 } });
+ assert_equals(event.action, "[object Object]");
+ }, "action set to an object");
+
+ test(function () {
+ const event = new InvokeEvent("test", {
+ action: {
+ toString() {
+ return "sample";
+ },
+ },
+ });
+ assert_equals(event.action, "sample");
+ }, "action set to an object with a toString function");
+
+ test(function () {
+ const eventInit = { action: "sample", invoker: document.body };
+ const event = new InvokeEvent("test", eventInit);
+ assert_equals(event.action, "sample");
+ assert_equals(event.invoker, document.body);
+ }, "InvokeEventInit properties set value");
+
+ test(function () {
+ const eventInit = {
+ action: "open",
+ invoker: document.getElementById("div"),
+ };
+ const event = new InvokeEvent("beforetoggle", eventInit);
+ assert_equals(event.action, "open");
+ assert_equals(event.invoker, document.getElementById("div"));
+ }, "InvokeEventInit properties set value 2");
+
+ test(function () {
+ const eventInit = {
+ action: "closed",
+ invoker: document.getElementById("button"),
+ };
+ const event = new InvokeEvent("toggle", eventInit);
+ assert_equals(event.action, "closed");
+ assert_equals(event.invoker, document.getElementById("button"));
+ }, "InvokeEventInit properties set value 3");
+
+ test(function () {
+ const event = new InvokeEvent("test", { invoker: undefined });
+ assert_equals(event.invoker, null);
+ }, "invoker set to undefined");
+
+ test(function () {
+ const event = new InvokeEvent("test", { invoker: null });
+ assert_equals(event.invoker, null);
+ }, "invoker set to null");
+
+ test(function () {
+ assert_throws_js(
+ TypeError,
+ function () {
+ new InvokeEvent("test", { invoker: false });
+ },
+ "invoker is not an object",
+ );
+ }, "invoker set to false");
+
+ test(function () {
+ assert_throws_js(
+ TypeError,
+ function () {
+ const event = new InvokeEvent("test", { invoker: true });
+ },
+ "invoker is not an object",
+ );
+ }, "invoker set to true");
+
+ test(function () {
+ assert_throws_js(
+ TypeError,
+ function () {
+ const event = new InvokeEvent("test", { invoker: {} });
+ },
+ "invoker is not an object",
+ );
+ }, "invoker set to {}");
+
+ test(function () {
+ assert_throws_js(
+ TypeError,
+ function () {
+ const eventInit = { action: "closed", invoker: new XMLHttpRequest() };
+ const event = new InvokeEvent("toggle", eventInit);
+ },
+ "invoker is not an Element",
+ );
+ }, "invoker set to non-Element EventTarget");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html
new file mode 100644
index 0000000000..b19c1d3adc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html
@@ -0,0 +1,119 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:keithamus@github.com" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<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>
+<script src="resources/invoker-utils.js"></script>
+
+<div id="invokee"></div>
+<button id="invokerbutton" invoketarget="invokee"></button>
+
+<script>
+ promise_test(async function (t) {
+ let event = null;
+ invokee.addEventListener("invoke", (e) => (event = e), { once: true });
+ await clickOn(invokerbutton);
+ assert_true(event instanceof InvokeEvent, "event is InvokeEvent");
+ assert_equals(event.type, "invoke", "type");
+ assert_equals(event.bubbles, false, "bubbles");
+ assert_equals(event.composed, true, "composed");
+ assert_equals(event.isTrusted, true, "isTrusted");
+ assert_equals(event.action, "auto", "action");
+ assert_equals(event.target, invokee, "target");
+ assert_equals(event.invoker, invokerbutton, "invoker");
+ }, "event dispatches on click");
+
+ promise_test(async function (t) {
+ let event = null;
+ invokee.addEventListener("invoke", (e) => (event = e), { once: true });
+ invokerbutton.invokeAction = "fooBar";
+ await clickOn(invokerbutton);
+ assert_true(event instanceof InvokeEvent, "event is InvokeEvent");
+ assert_equals(event.type, "invoke", "type");
+ assert_equals(event.bubbles, false, "bubbles");
+ assert_equals(event.composed, true, "composed");
+ assert_equals(event.isTrusted, true, "isTrusted");
+ assert_equals(event.action, "fooBar", "action");
+ assert_equals(event.target, invokee, "target");
+ assert_equals(event.invoker, invokerbutton, "invoker");
+ }, "event action is set to invokeAction");
+
+ promise_test(async function (t) {
+ let event = null;
+ invokee.addEventListener("invoke", (e) => (event = e), { once: true });
+ invokerbutton.setAttribute("invokeaction", "BaRbAz");
+ await clickOn(invokerbutton);
+ assert_true(event instanceof InvokeEvent, "event is InvokeEvent");
+ assert_equals(event.type, "invoke", "type");
+ assert_equals(event.bubbles, false, "bubbles");
+ assert_equals(event.composed, true, "composed");
+ assert_equals(event.isTrusted, true, "isTrusted");
+ assert_equals(event.action, "BaRbAz", "action");
+ assert_equals(event.target, invokee, "target");
+ assert_equals(event.invoker, invokerbutton, "invoker");
+ }, "event action is set to invokeaction attribute");
+
+ promise_test(async function (t) {
+ let called = false;
+ invokerbutton.addEventListener(
+ "click",
+ (event) => {
+ event.preventDefault();
+ },
+ { once: true },
+ );
+ invokee.addEventListener(
+ "invoke",
+ (event) => {
+ called = true;
+ },
+ { once: true },
+ );
+ await clickOn(invokerbutton);
+ assert_false(called, "event was not called");
+ }, "event does not dispatch if click:preventDefault is called");
+
+ promise_test(async function (t) {
+ t.add_cleanup(() => invokerbutton.removeAttribute('disabled'));
+ let called = false;
+ invokee.addEventListener(
+ "invoke",
+ (event) => {
+ called = true;
+ },
+ { once: true },
+ );
+ invokerbutton.setAttribute("disabled", "");
+ await clickOn(invokerbutton);
+ assert_false(called, "event was not called");
+ }, "event does not dispatch if invoker is disabled");
+
+ promise_test(async function (t) {
+ svgInvokee = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ t.add_cleanup(() => {
+ invokerbutton.invokeTargetElement = invokee;
+ svgInvokee.remove();
+ });
+ document.body.append(svgInvokee);
+ let called = false;
+ assert_false(svgInvokee instanceof HTMLElement);
+ assert_true(svgInvokee instanceof Element);
+ let eventInvoker = null;
+ svgInvokee.addEventListener(
+ "invoke",
+ (event) => {
+ eventInvoker = event.invoker;
+ called = true;
+ },
+ { once: true },
+ );
+ invokerbutton.invokeTargetElement = svgInvokee;
+ await clickOn(invokerbutton);
+ assert_true(called, "event was called");
+ assert_true(eventInvoker == svgInvokee, "event invoker is set to right element");
+ }, "event dispatches if invoker is non-HTML Element");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-fullscreen-behavior.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-fullscreen-behavior.tentative.html
new file mode 100644
index 0000000000..b72020283e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-fullscreen-behavior.tentative.html
@@ -0,0 +1,175 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Luke Warlow" href="mailto:luke@warlow.dev" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<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>
+<script src="resources/invoker-utils.js"></script>
+
+<div id="invokee">
+ Fullscreen content
+ <button id="invokerbutton" invoketarget="invokee"></button>
+</div>
+
+<script>
+ // auto
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking div with auto action is no-op");
+
+ // toggleFullscreen
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ invokerbutton.setAttribute("invokeaction", "toggleFullscreen");
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches(":fullscreen"));
+ }, "invoking div with toggleFullscreen action makes div fullscreen");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ invokerbutton.setAttribute("invokeaction", "toggleFullscreen");
+ invokerbutton.click();
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking div with toggleFullscreen action (without user activation) is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ invokerbutton.setAttribute("invokeaction", "toggleFullscreen");
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking div with toggleFullscreen action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokerbutton.setAttribute("invokeaction", "toggleFullscreen");
+ await test_driver.bless('go fullscreen');
+ await invokee.requestFullscreen();
+ assert_true(invokee.matches(":fullscreen"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking fullscreen div with toggleFullscreen action exits fullscreen");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokerbutton.setAttribute("invokeaction", "tOgGlEFullscreen");
+ await test_driver.bless('go fullscreen');
+ await invokee.requestFullscreen();
+ assert_true(invokee.matches(":fullscreen"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking fullscreen div with toggleFullscreen (case-insensitive) action exits fullscreen");
+
+ // requestFullscreen
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ invokerbutton.setAttribute("invokeaction", "requestFullscreen");
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches(":fullscreen"));
+ }, "invoking div with requestFullscreen action makes div fullscreen");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ invokerbutton.setAttribute("invokeaction", "requestFullscreen");
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking div with requestFullscreen action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokerbutton.setAttribute("invokeaction", "requestFullscreen");
+ await test_driver.bless('go fullscreen');
+ await invokee.requestFullscreen();
+ assert_true(invokee.matches(":fullscreen"));
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches(":fullscreen"));
+ }, "invoking fullscreen div with requestFullscreen action is a no-op");
+
+ // exitFullscreen
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ invokerbutton.setAttribute("invokeaction", "exitFullscreen");
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking div with exitFullscreen action is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokerbutton.setAttribute("invokeaction", "exitFullscreen");
+ await test_driver.bless('go fullscreen');
+ await invokee.requestFullscreen();
+ assert_true(invokee.matches(":fullscreen"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking fullscreen div with exitFullscreen action exits fullscreen");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ invokerbutton.setAttribute("invokeaction", "exitFullscreen");
+ await test_driver.bless('go fullscreen');
+ await invokee.requestFullscreen();
+ assert_true(invokee.matches(":fullscreen"));
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches(":fullscreen"));
+ }, "invoking fullscreen div with exitFullscreen action and preventDefault is a no-op");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-generic-eventtarget-crash.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-generic-eventtarget-crash.tentative.html
new file mode 100644
index 0000000000..b2179640dd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-generic-eventtarget-crash.tentative.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:wpt@keithcirkel.co.uk" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<div id="invoker"></div>
+
+<script type="module">
+ const invokeEvent = new InvokeEvent('invoke', { bubbles: true });
+ document.body.addEventListener('invoke', e => {
+ e.invoker.toString();
+ });
+ invoker.addEventListener('invoke', e => {
+ e.invoker.toString();
+ });
+ invoker.dispatchEvent(invokeEvent);
+ await Promise.resolve();
+ invokeEvent.invoker.toString();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html
new file mode 100644
index 0000000000..f3abeae165
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html
@@ -0,0 +1,285 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Luke Warlow" href="mailto:luke@warlow.dev" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<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>
+<script src="resources/invoker-utils.js"></script>
+
+<audio controls id="invokee" src="/media/sound_5.mp3"></audio>
+<button id="invokerbutton" invoketarget="invokee"></button>
+
+<script>
+ // auto
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking audio with auto action is no-op");
+
+ // playpause
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking audio with playpause action makes audio play");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ invokerbutton.click();
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking audio with playpause action (without user activation) is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking audio with playpause action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ await test_driver.bless('play audio');
+ invokee.play();
+ assert_false(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking playing audio with playpause action pauses it");
+
+ // play
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking audio with play action makes audio play");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ invokerbutton.click();
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking audio with play action (without user activation) is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking audio with play action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ await test_driver.bless('play audio');
+ invokee.play();
+ assert_false(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking playing audio with play action is a no-op");
+
+ // pause
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "pause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking audio with pause action is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "pause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking audio with pause action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ await test_driver.bless('play audio');
+ invokee.play();
+ assert_false(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "pause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking playing audio with pause action makes it pause");
+
+ // mute
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_false(invokee.muted);
+ invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.muted);
+ }, "invoking audio with toggleMuted action mutes it");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_false(invokee.muted);
+ invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.muted);
+ }, "invoking audio with toggleMuted action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.muted = true;
+ assert_true(invokee.muted);
+ invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.muted);
+ }, "invoking muted audio with toggleMuted action unmutes it");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-details-behavior.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-details-behavior.tentative.html
new file mode 100644
index 0000000000..c6735e2611
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-details-behavior.tentative.html
@@ -0,0 +1,218 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Luke Warlow" href="mailto:luke@warlow.dev" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<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>
+<script src="resources/invoker-utils.js"></script>
+
+<details id="invokee">
+ Details Contents
+</details>
+<button id="invokerbutton" invoketarget="invokee"></button>
+
+<script>
+ // auto
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ }, "invoking closed details with auto action opens");
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches("[open]"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_false(invokee.matches("[open]"));
+ }, "invoking closed details with auto action and preventDefault does not open");
+
+ promise_test(async function (t) {
+ invokee.setAttribute('open', '');
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches("[open]"));
+ }, "invoking open details with auto action closes");
+
+ promise_test(async function (t) {
+ invokee.setAttribute('open', '');
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches("[open]"));
+ }, "invoking open details with auto action and preventDefault does not close");
+
+ promise_test(async function (t) {
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ invokee.addEventListener("invoke", (e) => {
+ invokee.setAttribute('open', '');
+ }, {
+ once: true,
+ });
+ assert_false(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches("[open]"));
+ }, "invoking details with auto action where event listener opens leads to a closed details");
+
+ promise_test(async function (t) {
+ invokee.setAttribute('open', '');
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ invokee.addEventListener("invoke", (e) => {
+ invokee.removeAttribute('open');
+ }, {
+ once: true,
+ });
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches("[open]"));
+ }, "invoking open details with auto action where event listener closes leads to an open details");
+
+ // toggle
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches("[open]"));
+ invokerbutton.setAttribute("invokeaction", "toggle");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ }, "invoking closed details with toggle action opens");
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches("[open]"));
+ invokerbutton.setAttribute("invokeaction", "tOgGlE");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ }, "invoking closed details with toggle (case-insensitive) action opens");
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches("[open]"));
+ invokerbutton.setAttribute("invokeaction", "toggle");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_false(invokee.matches("[open]"));
+ }, "invoking closed details with toggle action and preventDefault does not open");
+
+ promise_test(async function (t) {
+ invokee.setAttribute('open', '');
+ invokerbutton.setAttribute("invokeaction", "toggle");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches("[open]"));
+ }, "invoking open details with toggle action closes");
+
+ promise_test(async function (t) {
+ invokee.setAttribute('open', '');
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ invokerbutton.setAttribute("invokeaction", "toggle");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches("[open]"));
+ }, "invoking open details with toggle action and preventDefault does not close");
+
+ // open
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "open");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ }, "invoking closed details with open action opens");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "oPeN");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ }, "invoking closed details with open (case insensitive) action opens");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "open");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.setAttribute('open', '');
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ }, "invoking open details with open action is noop");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "open");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches("[open]"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_false(invokee.matches("[open]"));
+ }, "invoking closed popover with open action and preventDefault does not open");
+
+ // close
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "close");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches("[open]"));
+ }, "invoking closed details with close action is noop");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "close");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.setAttribute('open', '');
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_false(invokee.matches("[open]"));
+ }, "invoking open details with close action closes");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "cLoSe");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.setAttribute('open', '');
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_false(invokee.matches("[open]"));
+ }, "invoking open details with close (case insensitive) action closes");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "close");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.setAttribute('open', '');
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches("[open]"));
+ }, "invoking open details with close action with preventDefault does not close");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-popover-behavior.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-popover-behavior.tentative.html
new file mode 100644
index 0000000000..03eba22285
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-popover-behavior.tentative.html
@@ -0,0 +1,209 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:keithamus@github.com" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<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>
+<script src="resources/invoker-utils.js"></script>
+
+<div id="invokee" popover>
+ <button id="invokerbutton2" invoketarget="invokee"></button>
+</div>
+<button id="invokerbutton" invoketarget="invokee"></button>
+
+<script>
+ // auto
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as auto) closed popover opens");
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches(":popover-open"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as auto) closed popover with preventDefault does not open");
+
+ promise_test(async function (t) {
+ invokee.showPopover();
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as auto) open popover closes");
+
+ promise_test(async function (t) {
+ invokee.showPopover();
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton2);
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as auto) from within open popover closes");
+
+ promise_test(async function (t) {
+ invokee.showPopover();
+ t.add_cleanup(() => invokee.hidePopover());
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton2);
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as auto) open popover with preventDefault does not close");
+
+ // togglepopover
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches(":popover-open"));
+ invokerbutton.setAttribute("invokeaction", "togglepopover");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as togglepopover) closed popover opens");
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches(":popover-open"));
+ invokerbutton.setAttribute("invokeaction", "tOgGlEpOpOvEr");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as togglepopover - case insensitive) closed popover opens");
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches(":popover-open"));
+ invokerbutton.setAttribute("invokeaction", "togglepopover");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as togglepopover) closed popover with preventDefault does not open");
+
+ promise_test(async function (t) {
+ invokee.showPopover();
+ invokerbutton2.setAttribute("invokeaction", "togglepopover");
+ t.add_cleanup(() => invokerbutton2.removeAttribute("invokeaction"));
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as togglepopover) open popover closes");
+
+ promise_test(async function (t) {
+ invokee.showPopover();
+ invokerbutton2.setAttribute("invokeaction", "togglepopover");
+ t.add_cleanup(() => invokerbutton2.removeAttribute("invokeaction"));
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton2);
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as togglepopover) from within open popover closes");
+
+ promise_test(async function (t) {
+ invokee.showPopover();
+ t.add_cleanup(() => invokee.hidePopover());
+ invokerbutton2.setAttribute("invokeaction", "togglepopover");
+ t.add_cleanup(() => invokerbutton2.removeAttribute("invokeaction"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton2);
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as togglepopover) open popover with preventDefault does not close");
+
+ // showpopover
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "showpopover");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as showpopover) closed popover opens");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "sHoWpOpOvEr");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as showpopover - case insensitive) closed popover opens");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "showpopover");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.showPopover();
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as showpopover) open popover is noop");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "showpopover");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches(":popover-open"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as showpopover) closed popover with preventDefault does not open");
+
+ // hidepopover
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "hidepopover");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as hidepopover) closed popover is noop");
+
+ promise_test(async function (t) {
+ invokerbutton2.setAttribute("invokeaction", "hidepopover");
+ t.add_cleanup(() => invokerbutton2.removeAttribute("invokeaction"));
+ invokee.showPopover();
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton2);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as hidepopover) open popover closes");
+
+ promise_test(async function (t) {
+ invokerbutton2.setAttribute("invokeaction", "hIdEpOpOvEr");
+ t.add_cleanup(() => invokerbutton2.removeAttribute("invokeaction"));
+ invokee.showPopover();
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton2);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as hidepopover - case insensitive) open popover closes");
+
+ promise_test(async function (t) {
+ invokerbutton2.setAttribute("invokeaction", "hidepopover");
+ t.add_cleanup(() => invokerbutton2.removeAttribute("invokeaction"));
+ invokee.showPopover();
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton2);
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as hidepopover) open popover with preventDefault does not close");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html
new file mode 100644
index 0000000000..5bbcd83e72
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html
@@ -0,0 +1,253 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Luke Warlow" href="mailto:luke@warlow.dev" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<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>
+<script src="resources/invoker-utils.js"></script>
+
+<video controls id="invokee" src="/media/movie_5.mp4"></video>
+<button id="invokerbutton" invoketarget="invokee"></button>
+
+<script>
+ // auto
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking video with auto action is no-op");
+
+ // playpause
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking video with playpause action makes video play");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking video with playpause action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ await test_driver.bless('play video');
+ invokee.play();
+ assert_false(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking playing video with playpause action pauses it");
+
+ // play
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking video with play action makes video play");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking video with play action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ await test_driver.bless('play video');
+ invokee.play();
+ assert_false(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking playing video with play action is a no-op");
+
+ // pause
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "pause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking video with pause action is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "pause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking video with pause action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ await test_driver.bless('play video');
+ invokee.play();
+ assert_false(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "pause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking playing video with pause action makes it pause");
+
+ // mute
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_false(invokee.muted);
+ invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.muted);
+ }, "invoking video with toggleMuted action mutes it");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_false(invokee.muted);
+ invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.muted);
+ }, "invoking video with toggleMuted action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.muted = true;
+ assert_true(invokee.muted);
+ invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.muted);
+ }, "invoking muted video with toggleMuted action unmutes it");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/resources/invoker-utils.js b/testing/web-platform/tests/html/semantics/invokers/resources/invoker-utils.js
new file mode 100644
index 0000000000..317945502d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/resources/invoker-utils.js
@@ -0,0 +1,12 @@
+function waitForRender() {
+ return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+}
+async function clickOn(element) {
+ const actions = new test_driver.Actions();
+ await waitForRender();
+ await actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ await waitForRender();
+}
diff --git a/testing/web-platform/tests/html/semantics/links/META.yml b/testing/web-platform/tests/html/semantics/links/META.yml
new file mode 100644
index 0000000000..b2167370d1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/META.yml
@@ -0,0 +1,2 @@
+suggested_reviewers:
+ - annevk
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-no-referrer-when-downgrade.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-no-referrer-when-downgrade.html
new file mode 100644
index 0000000000..466868dd7b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-no-referrer-when-downgrade.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Origin Header No Referrer When Downgrade Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='no-referrer-when-downgrade'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-origin.js"></script>
+ <script>
+ testOriginHeader(self.location.origin);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-no-referrer.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-no-referrer.html
new file mode 100644
index 0000000000..cd7a1804f3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-no-referrer.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Origin Header No Referrer Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='no-referrer'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-origin.js"></script>
+ <script>
+ testOriginHeader("null");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-origin-when-cross-origin.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-origin-when-cross-origin.html
new file mode 100644
index 0000000000..98115aa653
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-origin-when-cross-origin.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Origin Header Origin When Cross Origin Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='origin-when-cross-origin'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-origin.js"></script>
+ <script>
+ testOriginHeader(self.location.origin);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-origin.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-origin.html
new file mode 100644
index 0000000000..194ca9d4ad
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-origin.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Origin Header Origin Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='origin'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-origin.js"></script>
+ <script>
+ testOriginHeader(self.location.origin);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-same-origin.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-same-origin.html
new file mode 100644
index 0000000000..eb86708d5b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-same-origin.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Origin Header Same Origin Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='same-origin'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-origin.js"></script>
+ <script>
+ testOriginHeader(self.location.origin);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-strict-origin-when-cross-origin.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-strict-origin-when-cross-origin.html
new file mode 100644
index 0000000000..f6514ff2ae
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-strict-origin-when-cross-origin.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Origin Header Strict Origin When Cross Origin Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='strict-origin-when-cross-origin'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-origin.js"></script>
+ <script>
+ testOriginHeader(self.location.origin);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-strict-origin.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-strict-origin.html
new file mode 100644
index 0000000000..4aa311e833
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-strict-origin.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Origin Header Strict Origin Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='strict-origin'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-origin.js"></script>
+ <script>
+ testOriginHeader(self.location.origin);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-unsafe-url.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-unsafe-url.html
new file mode 100644
index 0000000000..59742404fe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin-unsafe-url.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Origin Header Unsafe Url Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='unsafe-url'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-origin.js"></script>
+ <script>
+ testOriginHeader(self.location.origin);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin.html
new file mode 100644
index 0000000000..189e2e66d4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Origin no Referrer Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-origin.js"></script>
+ <script>
+ testOriginHeader(self.location.origin);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin.js b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin.js
new file mode 100644
index 0000000000..acc62ef93b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-origin.js
@@ -0,0 +1,40 @@
+const RESOURCES_DIR = "/html/semantics/links/downloading-resources/resources/";
+
+function testOriginHeader(expectedOrigin) {
+ var id = self.token();
+ let testUrl = RESOURCES_DIR + "inspect-header.py?header=origin&cmd=put&id=" + id;
+
+ promise_test(function(test) {
+ const anchor = document.getElementById("a");
+ anchor.setAttribute("ping", testUrl);
+ anchor.click();
+ return pollResult(id) .then(result => {
+ assert_equals(result, expectedOrigin, "Correct origin header result");
+ });
+ }, "Test origin header " + RESOURCES_DIR);
+}
+
+// Sending a ping is an asynchronous and non-blocking request to a web server.
+// We may have to create a poll loop to get result from server
+function pollResult(id) {
+ let checkUrl = RESOURCES_DIR + "inspect-header.py?header=origin&cmd=get&id=" + id;
+
+ return new Promise(resolve => {
+ function checkResult() {
+ fetch(checkUrl).then(
+ function(response) {
+ assert_equals(response.status, 200, "Inspect header response's status is 200");
+ let result = response.headers.get("x-request-origin");
+
+ if (result != undefined) {
+ resolve(result);
+ } else {
+ step_timeout(checkResult.bind(this), 100);
+ }
+ });
+ }
+
+ checkResult();
+ });
+
+}
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-no-referrer-when-downgrade.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-no-referrer-when-downgrade.html
new file mode 100644
index 0000000000..96c19d1d0e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-no-referrer-when-downgrade.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Referrer Header No Referrer When Downgrade Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='no-referrer-when-downgrade'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-referrer.js"></script>
+ <script>
+ testReferrerHeader("");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-no-referrer.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-no-referrer.html
new file mode 100644
index 0000000000..065063075c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-no-referrer.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Referrer Header No Referrer Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='no-referrer'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-referrer.js"></script>
+ <script>
+ testReferrerHeader("");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-origin-when-cross-origin.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-origin-when-cross-origin.html
new file mode 100644
index 0000000000..f0394261a1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-origin-when-cross-origin.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Referrer Header Origin When Cross Origin Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='origin-when-cross-origin'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-referrer.js"></script>
+ <script>
+ testReferrerHeader("");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-origin.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-origin.html
new file mode 100644
index 0000000000..bef435581c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-origin.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Referrer Header Origin Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='origin'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-referrer.js"></script>
+ <script>
+ testReferrerHeader("");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-same-origin.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-same-origin.html
new file mode 100644
index 0000000000..19b2d022af
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-same-origin.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Referrer Header Same Origin Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='same-origin'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-referrer.js"></script>
+ <script>
+ testReferrerHeader("");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-strict-origin-when-cross-origin.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-strict-origin-when-cross-origin.html
new file mode 100644
index 0000000000..95132eee5c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-strict-origin-when-cross-origin.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Referrer Header Strict Origin When Cross Origin Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='strict-origin-when-cross-origin'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-referrer.js"></script>
+ <script>
+ testReferrerHeader("");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-strict-origin.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-strict-origin.html
new file mode 100644
index 0000000000..e2678e8de8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-strict-origin.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Referrer Header Strict Origin Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='strict-origin'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-referrer.js"></script>
+ <script>
+ testReferrerHeader("");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-unsafe-url.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-unsafe-url.html
new file mode 100644
index 0000000000..cc3d1dde86
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer-unsafe-url.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute Referrer Header Unsafe Url Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta name='referrer' content='unsafe-url'>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-referrer.js"></script>
+ <script>
+ testReferrerHeader("");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer.html b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer.html
new file mode 100644
index 0000000000..5e2d136443
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Ping attribute no Referrer Header given</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <a id="a" href="#">
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/resources/chromium/enable-hyperlink-auditing.js"></script>
+ <script src="header-referrer.js"></script>
+ <script>
+ testReferrerHeader("");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer.js b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer.js
new file mode 100644
index 0000000000..818649fbff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/header-referrer.js
@@ -0,0 +1,40 @@
+const RESOURCES_DIR = "/html/semantics/links/downloading-resources/resources/";
+
+function testReferrerHeader(expectedReferrer) {
+ let id = self.token();
+ let testUrl = RESOURCES_DIR + "inspect-header.py?header=referer&cmd=put&id=" + id;
+
+ promise_test(function(test) {
+ const anchor = document.getElementById("a");
+ anchor.setAttribute("ping", testUrl);
+ anchor.click();
+ return pollResult(id) .then(result => {
+ assert_equals(result, expectedReferrer, "Correct referrer header result");
+ });
+ }, "Test referer header " + RESOURCES_DIR);
+}
+
+// Sending a ping is an asynchronous and non-blocking request to a web server.
+// We may have to create a poll loop to get result from server
+function pollResult(id) {
+ let checkUrl = RESOURCES_DIR + "inspect-header.py?header=referer&cmd=get&id=" + id;
+
+ return new Promise(resolve => {
+ function checkResult() {
+ fetch(checkUrl).then(
+ function(response) {
+ assert_equals(response.status, 200, "Inspect header response's status is 200");
+ let result = response.headers.get("x-request-referer");
+
+ if (result != undefined) {
+ resolve(result);
+ } else {
+ step_timeout(checkResult.bind(this), 100);
+ }
+ });
+ }
+
+ checkResult();
+ });
+
+}
diff --git a/testing/web-platform/tests/html/semantics/links/downloading-resources/resources/inspect-header.py b/testing/web-platform/tests/html/semantics/links/downloading-resources/resources/inspect-header.py
new file mode 100644
index 0000000000..2c68e475ff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/downloading-resources/resources/inspect-header.py
@@ -0,0 +1,18 @@
+def main(request, response):
+ headers = [(b"Content-Type", b"text/plain")]
+ command = request.GET.first(b"cmd").lower()
+ test_id = request.GET.first(b"id")
+ header = request.GET.first(b"header")
+ if command == b"put":
+ request.server.stash.put(test_id, request.headers.get(header, b""))
+
+ elif command == b"get":
+ stashed_header = request.server.stash.take(test_id)
+ if stashed_header is not None:
+ headers.append((b"x-request-" + header, stashed_header))
+
+ else:
+ response.set_error(400, u"Bad Command")
+ return b"ERROR: Bad Command!"
+
+ return headers, b""
diff --git a/testing/web-platform/tests/html/semantics/links/following-hyperlinks/activation-behavior.window.js b/testing/web-platform/tests/html/semantics/links/following-hyperlinks/activation-behavior.window.js
new file mode 100644
index 0000000000..d530642b9e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/following-hyperlinks/activation-behavior.window.js
@@ -0,0 +1,50 @@
+["a",
+ "area"].forEach(type => {
+
+ const followed = type === "a" ? true : false;
+ async_test(t => {
+ const target = document.createElement("iframe"),
+ link = document.createElement(type);
+ t.add_cleanup(() => target.remove());
+ target.name = "certifiedrandom" + type;
+ link.target = "certifiedrandom" + type;
+ link.href = "/";
+ document.body.appendChild(target);
+ target.onload = t.step_func(() => {
+ if(target.contentWindow.location.href === "about:blank")
+ return;
+ if(followed) {
+ assert_equals(target.contentWindow.location.pathname, "/");
+ t.done();
+ } else {
+ assert_unreached();
+ }
+ });
+ link.click();
+ t.step_timeout(() => {
+ if(followed) {
+ assert_unreached();
+ } else {
+ t.done();
+ }
+ }, 500);
+ }, "<" + type + "> that is not connected should " + (followed ? "" : "not ") + "be followed");
+
+ async_test(t => {
+ const target = document.createElement("iframe"),
+ doc = document.implementation.createDocument("", ""),
+ link = doc.createElementNS("http://www.w3.org/1999/xhtml", type);
+ t.add_cleanup(() => target.remove());
+ target.name = "certifiedrandom2" + type;
+ link.target = "certifiedrandom2" + type;
+ link.href = "/";
+ document.body.appendChild(target);
+ target.onload = t.step_func(() => {
+ if(target.contentWindow.location.href === "about:blank")
+ return;
+ assert_unreached();
+ });
+ link.click();
+ t.step_timeout(() => t.done(), 500);
+ }, "<" + type + "> that is from an inactive document should not be followed");
+});
diff --git a/testing/web-platform/tests/html/semantics/links/following-hyperlinks/active-document.window.js b/testing/web-platform/tests/html/semantics/links/following-hyperlinks/active-document.window.js
new file mode 100644
index 0000000000..efa16e7d17
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/following-hyperlinks/active-document.window.js
@@ -0,0 +1,23 @@
+["a",
+ "area",
+ "link"].forEach(type => {
+ async_test(t => {
+ const frame = document.createElement("iframe"),
+ link = document.createElement(type);
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ // See https://github.com/whatwg/html/issues/490
+ if(frame.contentWindow.location.href === "about:blank")
+ return;
+ link.click(); // must be ignored because document is not active
+ t.step_timeout(() => {
+ assert_equals(frame.contentWindow.location.pathname, "/common/blank.html");
+ t.done();
+ }, 500);
+ });
+ document.body.appendChild(frame);
+ frame.contentDocument.body.appendChild(link);
+ link.href = "/";
+ frame.src = "/common/blank.html";
+ }, "<" + type + "> in navigated away <iframe>'s document cannot follow hyperlinks");
+});
diff --git a/testing/web-platform/tests/html/semantics/links/hyperlink-auditing/headers.optional.html b/testing/web-platform/tests/html/semantics/links/hyperlink-auditing/headers.optional.html
new file mode 100644
index 0000000000..dd524fa5fa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/hyperlink-auditing/headers.optional.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+
+<iframe id="i" src="/common/blank.html"></iframe>
+
+<!-- Test is optional because hyperlink auditing is optional. -->
+
+<script>
+promise_test(async t => {
+ await new Promise(resolve => window.onload = resolve);
+
+ const id = token();
+
+ const el = document.createElement("a");
+ el.ping = new URL(`resources/stash-headers.py?id=${id}`, location.href); // this will be a POST
+ el.href = "/common/blank.html?1";
+
+ i.contentDocument.body.append(el);
+ el.click();
+
+ let headers;
+ await pollForConditionFunc(t, async () => {
+ const res = await fetch(el.ping); // this will be a GET
+ const json = await res.json();
+
+ if (json !== "no headers yet") {
+ headers = json;
+ return true;
+ }
+ return false;
+ });
+
+ assert_equals(headers["content-type"], "text/ping", "content-type");
+ assert_equals(headers["ping-from"], i.src, "ping-from");
+ assert_equals(headers["ping-to"], el.href, "ping-to");
+});
+
+async function pollForConditionFunc(t, func, timeout = 3000, interval = 100) {
+ let remaining = Math.ceil(timeout / interval);
+
+ while (remaining > 0) {
+ --remaining;
+ await new Promise(resolve => t.step_timeout(resolve, interval));
+
+ if (await func()) {
+ return;
+ }
+ }
+
+ assert_true(false, "Condition never became true");
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/links/hyperlink-auditing/resources/stash-headers.py b/testing/web-platform/tests/html/semantics/links/hyperlink-auditing/resources/stash-headers.py
new file mode 100644
index 0000000000..a0d4a38812
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/hyperlink-auditing/resources/stash-headers.py
@@ -0,0 +1,27 @@
+import json
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ key = request.GET[b"id"]
+
+ if request.method == "POST":
+ content_type = request.headers.get(b"content-type", b"no content-type header")
+ ping_from = request.headers.get(b"ping-from", b"no ping-from header")
+ ping_to = request.headers.get(b"ping-to", b"no ping-to header")
+
+ value = json.dumps({
+ 'content-type': isomorphic_decode(content_type),
+ 'ping-from': isomorphic_decode(ping_from),
+ 'ping-to': isomorphic_decode(ping_to)
+ })
+ request.server.stash.put(key, value)
+
+ return (204, [], "")
+
+ elif request.method == "GET":
+ value = request.server.stash.take(key)
+ if value is None:
+ value = "\"no headers yet\""
+ return (200, [("Content-Type", "application/json")], str(value))
+
+ return (405, [], "")
diff --git a/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_attribute-getter-setter.html b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_attribute-getter-setter.html
new file mode 100644
index 0000000000..2db3082e21
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_attribute-getter-setter.html
@@ -0,0 +1,65 @@
+<!doctype html>
+<meta charset="utf-8">
+<html>
+<head>
+<title>HTMLAnchorElement getters and setters</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<a>anchor</a>
+<script>
+function test_gettersetter(property, oldresult, newval, newresult, oldurl, newurl) {
+ var a = document.querySelector('a');
+ a.href = oldurl;
+ var r1 = a[property];
+ assert_equals(r1, oldresult);
+ a[property] = newval;
+ var r2 = a[property];
+ assert_equals(r2, newresult);
+ var r3 = a.href;
+ assert_equals(r3, newurl);
+}
+
+//Elements for each test: [property, oldresult, newvalue, newresult, oldurl, newurl]
+// [0] [1] [2] [3] [4] [5]
+tests = [
+ ["hash", "#somehash", "someother", "#someother",
+ "http://google.com/index.html#somehash",
+ "http://google.com/index.html#someother"],
+ ["hash", "#somehash", "#someother", "#someother",
+ "http://google.com/index.html#somehash",
+ "http://google.com/index.html#someother"],
+ ["host", "google.com:1234", "github.com:4444", "github.com:4444",
+ "http://google.com:1234/somedir",
+ "http://github.com:4444/somedir"],
+ ["hostname", "google.com", "github.com", "github.com",
+ "http://google.com:1234/somedir",
+ "http://github.com:1234/somedir"],
+ ["href", "http://google.com:1234/somedir", "http://goo-gle.com:1234/other/x.html", "http://goo-gle.com:1234/other/x.html",
+ "http://google.com:1234/somedir",
+ "http://goo-gle.com:1234/other/x.html"],
+ ["password", "flabada", "blubb", "blubb",
+ "https://anonymous:flabada@developer.mozilla.org/en-US/docs/",
+ "https://anonymous:blubb@developer.mozilla.org/en-US/docs/"],
+ ["pathname", "/somedir/someotherdir/index.html", "/newpath/x.txt", "/newpath/x.txt",
+ "http://google.com:1234/somedir/someotherdir/index.html",
+ "http://google.com:1234/newpath/x.txt"],
+ ["port", "1234", "4444", "4444", "http://google.com:1234/somedir", "http://google.com:4444/somedir"],
+ ["protocol", "http:", "ftp:", "ftp:", "http://google.com/somedir", "ftp://google.com/somedir"],
+ ["protocol", "http:", "ftp", "ftp:", "http://google.com/somedir", "ftp://google.com/somedir"],
+ ["search", "?ho", "?hi", "?hi", "http://google.com/q.php?ho", "http://google.com/q.php?hi"],
+ ["search", "?ho", "hi", "?hi", "http://google.com/q.php?ho", "http://google.com/q.php?hi"],
+ ["search", "?ho", "?hi", "?hi", "http://google.com/?ho", "http://google.com/?hi"],
+ ["search", "?ho", "hi", "?hi", "http://google.com/?ho", "http://google.com/?hi"],
+ ["username", "anonymous", "wellknown", "wellknown",
+ "https://anonymous:pwd@developer.mozilla.org:1234/en-US/",
+ "https://wellknown:pwd@developer.mozilla.org:1234/en-US/"]
+];
+
+for (var i = 0; i < tests.length; i++) {
+ test(function() {
+ test_gettersetter(tests[i][0], tests[i][1], tests[i][2], tests[i][3], tests[i][4], tests[i][5])
+ }, "Getter and setter for attribute of anchor element(" + i + "):" + tests[i][0] );
+}
+</script>
+</head>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_getter.html b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_getter.html
new file mode 100644
index 0000000000..759eada220
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_getter.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<meta charset="utf-8">
+<html>
+<head>
+<title>HTMLAnchorElement getters test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<a id=a1 href="http://google.com?hi">a1</a>
+<a id=a2 href="http://google.com#somehash">a2</a>
+<a id=a3 href="http://google.com:1234/somedir">a3</a>
+<a id=a4 href="http://google.com:1234/somedir">a4</a>
+<a id=a5 href="http://google.com:1234/somedir">a5</a>
+<a id=a6 href="https://anonymous:flabada@developer.mozilla.org/en-US/docs/">a6</a>
+<a id=a7 href="http://google.com:1234/somedir/someotherdir/index.html">a7</a>
+<a id=a8 href="http://google.com:1234/somedir">a8</a>
+<a id=a9 href="http://google.com/somedir">a9</a>
+<a id=a10 href="https://anonymous:pwd@developer.mozilla.org:1234/en-US/">a10</a>
+<script>
+function test_getter(property, result, id) {
+ var a = document.getElementById(id);
+ var r = a[property];
+ assert_equals(r, result);
+}
+
+//Elements for each test: [property, result, id]
+// [0] [1] [2]
+tests = [
+ ["search", "?hi", "a1"],
+ ["hash", "#somehash", "a2"],
+ ["host", "google.com:1234", "a3"],
+ ["hostname", "google.com", "a4"],
+ ["href", "http://google.com:1234/somedir", "a5"],
+ ["password", "flabada", "a6"],
+ ["pathname", "/somedir/someotherdir/index.html", "a7"],
+ ["port", "1234", "a8"],
+ ["protocol", "http:", "a9"],
+ ["username", "anonymous", "a10"]
+];
+
+for (var i = 0; i < tests.length; i++) {
+ test(function() {
+ test_getter(tests[i][0], tests[i][1], tests[i][2])
+ }, "Getter for attribute of anchor element(" + i + "):" + tests[i][0]);
+}
+</script>
+</head>
+</html>
+
diff --git a/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_noopener.html b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_noopener.html
new file mode 100644
index 0000000000..95ab1c81fb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_noopener.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test behavior of rel="noopener" links</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe name="oursubframe"></iframe>
+<a href="support/noopener-target-2.html" rel="noopener" target="ourpopup"></a>
+<a href="support/noopener-target-2.html" rel="noopener" target="oursubframe"></a>
+<script>
+var tests = [];
+// First test the special targets
+function target1Loaded(win) {
+ // Find the relevant test
+ var test = tests.find((t) => t.openedWindow == win);
+ test.step(function() {
+ assert_equals(win.opener, window);
+ win.close();
+ test.done();
+ });
+}
+/**
+ * Test that <a rel="noopener"> targeted at one of _self, _parent, _top does the
+ * load in the appropriate existing browsing context instead of opening a new
+ * one. The test is run in a separate popup window we open and which we can
+ * navigate without causing the test harness going into conniptions.
+ */
+for (var target of ["self", "parent", "top"]) {
+ var t = async_test("Check that rel=noopener with target=_" + target + " does a normal load");
+ tests.push(t);
+ t.openedWindow = window.open("support/noopener-popup.html");
+ t.targetName = target;
+ t.openedWindow.onload = t.step_func(function() {
+ this.openedWindow.findLink(this.targetName).click();
+ });
+}
+
+/**
+ * And now check that a noopener load targeted at something other than one of
+ * the three special targets above is still able to reuse existing things with the
+ * given name. We do this in two ways. First, by opening a window named
+ * "ourpopup" and then doing a load via <a rel="noopener" target="ourpopup"> and
+ * verifying that the load happens in a window with a null opener, etc, while
+ * the opener of the thing we opened is not modified. And second, by targeting
+ * <a rel="noopener"> at a name that an existing subframe has, and ensuring that
+ * this subframe is not navigated.
+ */
+var t1 = async_test("Check that targeting of rel=noopener with a given name reuses an existing window with that name");
+var w;
+t1.add_cleanup(function() { w.close(); });
+var channel = new BroadcastChannel("ourpopup");
+channel.onmessage = t1.step_func_done(function(e) {
+ var data = e.data;
+ assert_true(data.hasOpener);
+ assert_false(data.hasParent);
+ assert_equals(data.name, "ourpopup");
+ assert_equals(w.opener, window);
+ assert_not_equals(w.location.href, "about:blank");
+});
+t1.step(function() {
+ w = window.open("", "ourpopup");
+ assert_equals(w.opener, window);
+ document.querySelectorAll("a")[0].click();
+});
+
+var t2 = async_test("Check that targeting of rel=noopener with a given name reuses an existing subframe with that name");
+var channel = new BroadcastChannel("oursubframe");
+channel.onmessage = t2.step_func_done(function(e) {
+ var data = e.data;
+ assert_false(data.hasOpener);
+ assert_true(data.hasParent);
+ assert_equals(data.name, "oursubframe");
+ assert_not_equals(document.querySelector("iframe").contentWindow.location.href,
+ "about:blank");
+});
+t2.step(function() {
+ document.querySelectorAll("a")[1].click();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/non-parsable-url-getter-setter.window.js b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/non-parsable-url-getter-setter.window.js
new file mode 100644
index 0000000000..587f9b9c46
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/non-parsable-url-getter-setter.window.js
@@ -0,0 +1,54 @@
+[
+ {
+ "property": "origin",
+ "set": null
+ },
+ {
+ "property": "protocol",
+ "get": ":",
+ "set": "https"
+ },
+ {
+ "property": "username"
+ },
+ {
+ "property": "password"
+ },
+ {
+ "property": "host"
+ },
+ {
+ "property": "hostname"
+ },
+ {
+ "property": "port",
+ "set": "8000"
+ },
+ {
+ "property": "pathname"
+ },
+ {
+ "property": "search"
+ },
+ {
+ "property": "hash"
+ }
+].forEach(({ property, get = "", set = "string" }) => {
+ ["a", "area"].forEach(name => {
+ test(() => {
+ const link = document.createElement(name);
+ link.href = "http://test:test/"; // non-parsable URL
+ assert_equals(link[property], get);
+ }, `<${name} href="http://test:test/">.${property} getter`);
+
+ if (set !== null) {
+ test(() => {
+ const link = document.createElement(name);
+ link.href = "http://test:test/"; // non-parsable URL
+ link[property] = set;
+ assert_equals(link[property], get);
+ assert_equals(link.href, "http://test:test/");
+ }, `<${name} href="http://test:test/">.${property} setter`);
+ }
+ });
+});
diff --git a/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/non-special-opaque-path-url-getter-setter.window.js b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/non-special-opaque-path-url-getter-setter.window.js
new file mode 100644
index 0000000000..9549c6e2a6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/non-special-opaque-path-url-getter-setter.window.js
@@ -0,0 +1,59 @@
+[
+ {
+ "property": "origin",
+ "get": "null",
+ "set": null
+ },
+ {
+ "property": "protocol",
+ "get": "non-special:",
+ "set": "super-special",
+ "setget": "super-special:"
+ },
+ {
+ "property": "username"
+ },
+ {
+ "property": "password"
+ },
+ {
+ "property": "host",
+ },
+ {
+ "property": "hostname",
+ },
+ {
+ "property": "port",
+ "set": "8000"
+ },
+ {
+ "property": "pathname",
+ "get": "opaque",
+ "setget": "opaque"
+ },
+ {
+ "property": "search",
+ "setget": "?string"
+ },
+ {
+ "property": "hash",
+ "setget": "#string"
+ }
+].forEach(({ property, get = "", set = "string", setget = get }) => {
+ ["a", "area"].forEach(name => {
+ test(() => {
+ const link = document.createElement(name);
+ link.href = "non-special:opaque";
+ assert_equals(link[property], get);
+ }, `<${name} href="non-special:opaque">.${property} getter`);
+
+ if (set !== null) {
+ test(() => {
+ const link = document.createElement(name);
+ link.href = "non-special:opaque";
+ link[property] = set;
+ assert_equals(link[property], setget);
+ }, `<${name} href="non-special:opaque">.${property} setter`);
+ }
+ });
+});
diff --git a/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/non-special-url-getter-setter.window.js b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/non-special-url-getter-setter.window.js
new file mode 100644
index 0000000000..de528a2f97
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/non-special-url-getter-setter.window.js
@@ -0,0 +1,63 @@
+[
+ {
+ "property": "origin",
+ "get": "null",
+ "set": null
+ },
+ {
+ "property": "protocol",
+ "get": "non-special:",
+ "set": "super-special",
+ "setget": "super-special:"
+ },
+ {
+ "property": "username"
+ },
+ {
+ "property": "password"
+ },
+ {
+ "property": "host",
+ "get": "test:9001",
+ "setget": "string:9001"
+ },
+ {
+ "property": "hostname",
+ "get": "test"
+ },
+ {
+ "property": "port",
+ "get": "9001",
+ "set": "8000"
+ },
+ {
+ "property": "pathname",
+ "get": "/",
+ "setget": "/string"
+ },
+ {
+ "property": "search",
+ "setget": "?string"
+ },
+ {
+ "property": "hash",
+ "setget": "#string"
+ }
+].forEach(({ property, get = "", set = "string", setget = set }) => {
+ ["a", "area"].forEach(name => {
+ test(() => {
+ const link = document.createElement(name);
+ link.href = "non-special://test:9001/";
+ assert_equals(link[property], get);
+ }, `<${name} href="non-special://test:9001/">.${property} getter`);
+
+ if (set !== null) {
+ test(() => {
+ const link = document.createElement(name);
+ link.href = "non-special://test:9001/";
+ link[property] = set;
+ assert_equals(link[property], setget);
+ }, `<${name} href="non-special://test:9001/">.${property} setter`);
+ }
+ });
+});
diff --git a/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/noopener-popup.html b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/noopener-popup.html
new file mode 100644
index 0000000000..2057dbf0be
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/noopener-popup.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script>
+ function findLink(arg) {
+ var doc;
+ if (arg == "self") {
+ doc = document;
+ } else {
+ doc = frames[0].document;
+ }
+ return doc.getElementById(arg + "target");
+ }
+</script>
+<a rel="noopener" target="_self" id="selftarget"
+ href="noopener-target-1.html"></a>
+<iframe srcdoc='
+ <a rel="noopener" target="_parent" id="parenttarget"
+ href="noopener-target-1.html"></a>
+ <a rel="noopener" target="_top" id="toptarget"
+ href="noopener-target-1.html"></a>'></iframe>
diff --git a/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/noopener-target-1.html b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/noopener-target-1.html
new file mode 100644
index 0000000000..0dbd14275c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/noopener-target-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+ opener.target1Loaded(this);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/noopener-target-2.html b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/noopener-target-2.html
new file mode 100644
index 0000000000..dd2d719134
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/noopener-target-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+ var channel = new BroadcastChannel(this.name);
+ channel.postMessage({ hasOpener: opener !== null ,
+ hasParent: parent != this,
+ name: window.name });
+ window.close();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/target_blank_implicit_noopener.html b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/target_blank_implicit_noopener.html
new file mode 100644
index 0000000000..bf6a1ae5bf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/support/target_blank_implicit_noopener.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+ let bc = new BroadcastChannel(window.location.search.substring(1));
+ bc.postMessage({ hasOpener: opener !== null });
+ window.close();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/target_blank_implicit_noopener.html b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/target_blank_implicit_noopener.html
new file mode 100644
index 0000000000..73eebaff70
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/target_blank_implicit_noopener.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <meta name="timeout" content="long">
+ <title>Test behavior of target=_blank links</title>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+</head>
+<body>
+ <a href="support/target_blank_implicit_noopener.html?a1" id="a1" rel="noopener" target="_blank">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a2" id="a2" rel="opener" target="_blank">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a3" id="a3" target="_blank">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a4" id="a4" rel="opener noopener" target="_blank">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a5" id="a5" rel="noopener opener" target="_blank">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a6" id="a6" rel="noreferrer" target="_blank">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a7" id="a7" rel="opener noreferrer" target="_blank">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a8" id="a8" rel="noopener opener noreferrer" target="_blank">Click me</a>
+
+ <!-- Although this is not valid, per the processing model of area it ought to work -->
+ <area shape="rect" coords="0,0,99,50" href="support/target_blank_implicit_noopener.html?area1" id="area1" rel="noopener" target="_blank">
+ <area shape="rect" coords="0,0,99,50" href="support/target_blank_implicit_noopener.html?area2" id="area2" rel="opener" target="_blank">
+ <area shape="rect" coords="0,0,99,50" href="support/target_blank_implicit_noopener.html?area3" id="area3" target="_blank">
+ <area shape="rect" coords="0,0,99,50" href="support/target_blank_implicit_noopener.html?area4" id="area4" rel="opener noopener" target="_blank">
+ <area shape="rect" coords="0,0,99,50" href="support/target_blank_implicit_noopener.html?area5" id="area5" rel="noopener opener" target="_blank">
+
+ <script>
+
+ let tests = [
+ { id: "a1", hasOpener: false, name: "Anchor element with target=_blank with rel=noopener" },
+ { id: "a2", hasOpener: true, name: "Anchor element with target=_blank with rel=opener" },
+ { id: "a3", hasOpener: false, name: "Anchor element with target=_blank with implicit rel=noopener" },
+ { id: "a4", hasOpener: false, name: "Anchor element with target=_blank with rel=opener+noopener" },
+ { id: "a5", hasOpener: false, name: "Anchor element with target=_blank with rel=noopener+opener" },
+ { id: "a6", hasOpener: false, name: "Anchor element with target=_blank with rel=noreferrer" },
+ { id: "a7", hasOpener: false, name: "Anchor element with target=_blank with rel=opener+noreferrer" },
+ { id: "a8", hasOpener: false, name: "Anchor element with target=_blank with rel=noopener+opener+noreferrer" },
+ { id: "area1", hasOpener: false, name: "Area element with target=_blank with rel=noopener" },
+ { id: "area2", hasOpener: true, name: "Area element with target=_blank with rel=opener" },
+ { id: "area3", hasOpener: false, name: "Area element with target=_blank with implicit rel=noopener" },
+ { id: "area4", hasOpener: false, name: "Area element with target=_blank with rel=opener+noopener" },
+ { id: "area5", hasOpener: false, name: "Area element with target=_blank with rel=noopener+opener" },
+ ];
+
+ tests.forEach(data => {
+ async_test(
+ test => {
+ let bc = new BroadcastChannel(data.id);
+ bc.addEventListener("message", test.step_func_done(e => {
+ assert_equals(e.data.hasOpener, data.hasOpener);
+ }), {once: true});
+
+ document.getElementById(data.id).click();
+ }, data.name);
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/target_blank_implicit_noopener_base.html b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/target_blank_implicit_noopener_base.html
new file mode 100644
index 0000000000..3da6a49ef8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/links-created-by-a-and-area-elements/target_blank_implicit_noopener_base.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <meta name="timeout" content="long">
+ <title>Test behavior of base target=_blank links</title>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <base target=_blank>
+</head>
+<body>
+ <a href="support/target_blank_implicit_noopener.html?a1" id="a1" rel="noopener">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a2" id="a2" rel="opener">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a3" id="a3">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a4" id="a4" rel="opener noopener">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a5" id="a5" rel="noopener opener">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a6" id="a6" rel="noreferrer">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a7" id="a7" rel="opener noreferrer">Click me</a>
+ <a href="support/target_blank_implicit_noopener.html?a8" id="a8" rel="noopener opener noreferrer">Click me</a>
+
+ <!-- Although this is not valid, per the processing model of area it ought to work -->
+ <area shape="rect" coords="0,0,99,50" href="support/target_blank_implicit_noopener.html?area1" id="area1" rel="noopener">
+ <area shape="rect" coords="0,0,99,50" href="support/target_blank_implicit_noopener.html?area2" id="area2" rel="opener">
+ <area shape="rect" coords="0,0,99,50" href="support/target_blank_implicit_noopener.html?area3" id="area3">
+ <area shape="rect" coords="0,0,99,50" href="support/target_blank_implicit_noopener.html?area4" id="area4" rel="opener noopener">
+ <area shape="rect" coords="0,0,99,50" href="support/target_blank_implicit_noopener.html?area5" id="area5" rel="noopener opener">
+
+ <script>
+
+ let tests = [
+ { id: "a1", hasOpener: false, name: "Anchor element with base target=_blank with rel=noopener" },
+ { id: "a2", hasOpener: true, name: "Anchor element with base target=_blank with rel=opener" },
+ { id: "a3", hasOpener: false, name: "Anchor element with base target=_blank with implicit rel=noopener" },
+ { id: "a4", hasOpener: false, name: "Anchor element with base target=_blank with rel=opener+noopener" },
+ { id: "a5", hasOpener: false, name: "Anchor element with base target=_blank with rel=noopener+opener" },
+ { id: "a6", hasOpener: false, name: "Anchor element with base target=_blank with rel=noreferrer" },
+ { id: "a7", hasOpener: false, name: "Anchor element with base target=_blank with rel=opener+noreferrer" },
+ { id: "a8", hasOpener: false, name: "Anchor element with base target=_blank with rel=noopener+opener+noreferrer" },
+ { id: "area1", hasOpener: false, name: "Area element with base target=_blank with rel=noopener" },
+ { id: "area2", hasOpener: true, name: "Area element with base target=_blank with rel=opener" },
+ { id: "area3", hasOpener: false, name: "Area element with base target=_blank with implicit rel=noopener" },
+ { id: "area4", hasOpener: false, name: "Area element with base target=_blank with rel=opener+noopener" },
+ { id: "area5", hasOpener: false, name: "Area element with base target=_blank with rel=noopener+opener" },
+ ];
+
+ tests.forEach(data => {
+ async_test(
+ test => {
+ let bc = new BroadcastChannel(data.id);
+ bc.addEventListener("message", test.step_func_done(e => {
+ assert_equals(e.data.hasOpener, data.hasOpener);
+ }), {once: true});
+
+ document.getElementById(data.id).click();
+ }, data.name);
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/links/linktypes/alternate-css-ref.html b/testing/web-platform/tests/html/semantics/links/linktypes/alternate-css-ref.html
new file mode 100644
index 0000000000..ec961eac15
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/linktypes/alternate-css-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Reference of Alternate css</title>
+<link rel="stylesheet" href="preferred.css" title="preferred">
+<div>foobar</div>
diff --git a/testing/web-platform/tests/html/semantics/links/linktypes/alternate-css.html b/testing/web-platform/tests/html/semantics/links/linktypes/alternate-css.html
new file mode 100644
index 0000000000..366d6c5593
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/linktypes/alternate-css.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Alternate css</title>
+<link rel="match" href="alternate-css-ref.html">
+<link rel="stylesheet" href="preferred.css" title="preferred">
+<link rel="alternate stylesheet" href="alternate.css" title="alternate">
+<div>foobar</div>
diff --git a/testing/web-platform/tests/html/semantics/links/linktypes/alternate-import.css b/testing/web-platform/tests/html/semantics/links/linktypes/alternate-import.css
new file mode 100644
index 0000000000..7db3df1d78
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/linktypes/alternate-import.css
@@ -0,0 +1,3 @@
+body {
+ background-color: black;
+}
diff --git a/testing/web-platform/tests/html/semantics/links/linktypes/alternate.css b/testing/web-platform/tests/html/semantics/links/linktypes/alternate.css
new file mode 100644
index 0000000000..b101ab91f0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/linktypes/alternate.css
@@ -0,0 +1,5 @@
+@import url("alternate-import.css");
+
+div {
+ background-color: red;
+}
diff --git a/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-lower.css b/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-lower.css
new file mode 100644
index 0000000000..a19c9dfd72
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-lower.css
@@ -0,0 +1 @@
+#z-lower:after { content: "PASS"; color: green; }
diff --git a/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-mixed.css b/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-mixed.css
new file mode 100644
index 0000000000..7389ea1a1a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-mixed.css
@@ -0,0 +1 @@
+#z-mixed:after { content: "PASS"; color: green; }
diff --git a/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-other.css b/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-other.css
new file mode 100644
index 0000000000..a6c2616d86
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-other.css
@@ -0,0 +1 @@
+#z-other:after { content: "FAIL"; color: red; }
diff --git a/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-ref.html b/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-ref.html
new file mode 100644
index 0000000000..5ac2432547
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+p:after { font-weight: bold; }
+p:after { content: "PASS"; color: green; }
+</style>
+<p>text/css treated as CSS?
+<p>TeXt/CsS treated as CSS?
+<p>text/cſs ignored?
diff --git a/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive.css b/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive.css
new file mode 100644
index 0000000000..5d647d0f2e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive.css
@@ -0,0 +1,3 @@
+p:after { font-weight: bold; }
+p:after { content: "FAIL"; color: red; }
+#z-other:after { content: "PASS"; color: green; }
diff --git a/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive.html b/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive.html
new file mode 100644
index 0000000000..39fd5520d2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/linktypes/link-type-stylesheet/process-stylesheet-linked-resource-ascii-case-insensitive.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/#link-type-stylesheet:process-the-linked-resource">
+<link rel="help" href="https://html.spec.whatwg.org/#content-type">
+<link rel="help" href="https://mimesniff.spec.whatwg.org/#mime-type-representation">
+<link rel="match" href="process-stylesheet-linked-resource-ascii-case-insensitive-ref.html">
+<meta name="assert" content="link@type values for stylesheet resources are ASCII case-insensitive">
+<link rel="stylesheet" href="process-stylesheet-linked-resource-ascii-case-insensitive.css">
+<link rel="stylesheet" href="process-stylesheet-linked-resource-ascii-case-insensitive-lower.css" type="text/css">
+<link rel="stylesheet" href="process-stylesheet-linked-resource-ascii-case-insensitive-mixed.css" type="TeXt/CsS">
+<link rel="stylesheet" href="process-stylesheet-linked-resource-ascii-case-insensitive-other.css" type="text/cſs">
+<p id="z-lower">text/css treated as CSS?
+<p id="z-mixed">TeXt/CsS treated as CSS?
+<p id="z-other">text/cſs ignored?
diff --git a/testing/web-platform/tests/html/semantics/links/linktypes/original-id.json b/testing/web-platform/tests/html/semantics/links/linktypes/original-id.json
new file mode 100644
index 0000000000..1e5f7b5ed3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/linktypes/original-id.json
@@ -0,0 +1 @@
+{"original_id":"linkTypes"} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/links/linktypes/preferred.css b/testing/web-platform/tests/html/semantics/links/linktypes/preferred.css
new file mode 100644
index 0000000000..54b95ac280
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/links/linktypes/preferred.css
@@ -0,0 +1,3 @@
+div {
+ border: 4px solid green;
+}
diff --git a/testing/web-platform/tests/html/semantics/permission-element/no-end-tag-no-contents.html b/testing/web-platform/tests/html/semantics/permission-element/no-end-tag-no-contents.html
new file mode 100644
index 0000000000..5fcce1421b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/permission-element/no-end-tag-no-contents.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<!--The permission element should have no end tag or content.
+ Therefore the parsing should stop after the beginning tag, and the following
+ element and text will become part of the 'body' element's contents.
+-->
+<permission type="geolocation"><span>this is some text</span></permission>
+
+<script>
+ test(function(){
+ assert_equals(3, document.body.childElementCount); // permission, span, script
+ assert_equals(document.body.innerText, "this is some text", "The text should be part of the 'body' element's text");
+
+ assert_true(document.body.children[0] instanceof HTMLPermissionElement, "First element should be a permission element");
+ var permission = document.body.children[0];
+ assert_equals(permission.innerText, "", "The permission element should have no text");
+ assert_equals(permission.childElementCount, 0, "The permission element should have no children");
+
+ assert_true(document.body.children[1] instanceof HTMLSpanElement, "Second element should be a span element");
+ var span = document.body.children[1];
+ assert_equals(span.innerText, "this is some text", "The span element should contain the text");
+ }, "The permission element should have no end tag or contents");
+</script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/popovers/WEB_FEATURES.yml b/testing/web-platform/tests/html/semantics/popovers/WEB_FEATURES.yml
new file mode 100644
index 0000000000..e1b9f82de3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: popover
+ files: "**"
diff --git a/testing/web-platform/tests/html/semantics/popovers/hide-other-popover-side-effects.html b/testing/web-platform/tests/html/semantics/popovers/hide-other-popover-side-effects.html
new file mode 100644
index 0000000000..7cc95a95e9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/hide-other-popover-side-effects.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://chromium-review.googlesource.com/c/chromium/src/+/4094463/8/third_party/blink/renderer/core/html/html_element.cc#1404">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id=popover1 popover=auto>popover1</div>
+<div id=popover2 popover=auto>popover2</div>
+
+<script>
+test(() => {
+ const popover2 = document.getElementById('popover2');
+ popover1.showPopover();
+ popover1.addEventListener('beforetoggle', () => {
+ popover2.remove();
+ });
+ assert_throws_dom('InvalidStateError', () => popover2.showPopover(),
+ "popover1's beforetoggle event handler removes popover2 so showPopover should throw.");
+ assert_false(popover2.matches(':popover-open'), 'popover2 should not match :popover-open once it is closed.');
+}, 'Removing a popover while it is opening and force closing another popover should throw an exception.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/invoker-show-crash.html b/testing/web-platform/tests/html/semantics/popovers/invoker-show-crash.html
new file mode 100644
index 0000000000..7da57f9788
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/invoker-show-crash.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1463384">
+<link rel=help href="https://github.com/whatwg/html/issues/9383">
+
+<button id=button popovertarget=popover>button</button>
+<div id=popover popover=auto>popover</div>
+
+<script>
+button.click();
+popover.showPopover();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/label-in-invoker.html b/testing/web-platform/tests/html/semantics/popovers/label-in-invoker.html
new file mode 100644
index 0000000000..bf8ab9710d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/label-in-invoker.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1523168">
+<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 popovertarget=mypopover>
+ <label>label</label>
+</button>
+<div id=mypopover popover=auto>popover</div>
+
+<script>
+promise_test(async() => {
+ const label = document.querySelector('label');
+ assert_false(mypopover.matches(':popover-open'),
+ 'Popover should be closed at the start of the test.');
+ await test_driver.click(label);
+ assert_true(mypopover.matches(':popover-open'),
+ 'The popover should be opened by clicking on the label.');
+}, 'Buttons with popovertarget should invoke targets even if there is a label in the button.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/light-dismiss-event-ordering.html b/testing/web-platform/tests/html/semantics/popovers/light-dismiss-event-ordering.html
new file mode 100644
index 0000000000..be39050ac6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/light-dismiss-event-ordering.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://chromium-review.googlesource.com/c/chromium/src/+/4023021">
+<link rel=help href="https://github.com/whatwg/html/pull/8221#discussion_r1041135388">
+<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/testdriver-actions.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<button id=target>target</button>
+<div id=popover popover=auto>popover</div>
+
+<script>
+for (const capture of [true, false]) {
+ for (const eventName of ['pointerdown', 'pointerup', 'mousedown', 'mouseup', 'click']) {
+ promise_test(async t => {
+ t.add_cleanup(() => {
+ try {
+ popover.hidePopover();
+ } catch {}
+ });
+
+ popover.showPopover();
+ document.addEventListener(eventName, event => {
+ event.preventDefault();
+ }, {capture, once: true});
+ // Click away from the popover to activate light dismiss.
+ await clickOn(target);
+ assert_equals(document.querySelectorAll(':popover-open').length, 0,
+ 'The popover should be closed via light dismiss even when preventDefault is called.');
+
+ popover.showPopover();
+ document.addEventListener(eventName, event => {
+ event.stopPropagation();
+ }, {capture, once: true});
+ // Click away from the popover to activate light dismiss.
+ await clickOn(target);
+ assert_equals(document.querySelectorAll(':popover-open').length, 0,
+ 'The popover should be closed via light dismiss even when stopPropagation is called.');
+
+ }, `Tests the interactions between popover light dismiss and pointer/mouse events. eventName: ${eventName}, capture: ${capture}`);
+ }
+}
+
+promise_test(async t => {
+ t.add_cleanup(() => {
+ try {
+ popover.hidePopover();
+ } catch {}
+ });
+ popover.showPopover();
+
+ const expectedEvents = [
+ 'pointerdown',
+ 'mousedown',
+ 'beforetoggle newState: closed',
+ 'pointerup',
+ 'mouseup',
+ 'click'
+ ];
+ const events = [];
+
+ for (const eventName of ['pointerdown', 'pointerup', 'mousedown', 'mouseup', 'click']) {
+ document.addEventListener(eventName, () => events.push(eventName));
+ }
+ popover.addEventListener('beforetoggle', event => {
+ events.push('beforetoggle newState: ' + event.newState);
+ });
+
+ // Click away from the popover to activate light dismiss.
+ await clickOn(target);
+
+ assert_array_equals(events, expectedEvents,
+ 'pointer and popover events should be fired in the correct order.');
+
+ assert_equals(document.querySelectorAll(':popover-open').length, 0,
+ 'The popover should be closed via light dismiss.');
+
+}, 'Tests the order of pointer/mouse events during popover light dismiss.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-anchor-change-display-ref.html b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-change-display-ref.html
new file mode 100644
index 0000000000..9530e7d3c4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-change-display-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<p>There should be a green box attached to the right side of each orange box.</p>
+<div class=ex><div class=anchor></div><div class=popover></div></div>
+<div class=ex><div class=anchor></div><div class=popover></div></div>
+
+<style>
+ .ex {
+ margin: 25px;
+ font-size: 0;
+ }
+ .ex div {
+ display:inline-block;
+ width: 100px;
+ height: 100px;
+ }
+ .anchor {
+ background: orange;
+ }
+ .popover {
+ background: lime;
+ }
+</style>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-anchor-change-display.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-change-display.tentative.html
new file mode 100644
index 0000000000..435929a6c1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-change-display.tentative.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:xiaochengh@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=match href="popover-anchor-change-display-ref.html">
+<script src="resources/popover-utils.js"></script>
+
+<p>There should be a green box attached to the right side of each orange box.</p>
+
+<div class=ex>
+ <div class=anchor id=anchor1></div>
+ <div id=popover1 popover=manual defaultopen></div>
+</div>
+
+<div class=ex>
+ <div class=anchor id=will-be-anchor2></div>
+ <div id=popover2 popover=manual anchor=anchor2 defaultopen></div>
+</div>
+
+<script>
+showDefaultopenPopoversOnLoad();
+
+function runTest() {
+ document.body.offsetLeft; // Force layout
+
+ document.getElementById('popover1').setAttribute('anchor', 'anchor1');
+ document.getElementById('will-be-anchor2').setAttribute('id', 'anchor2');
+}
+window.addEventListener('load', runTest);
+</script>
+
+<style>
+ .ex {
+ margin: 25px;
+ }
+ .ex div {
+ width: 100px;
+ height: 100px;
+ }
+ .anchor {
+ background: orange;
+ }
+ [popover] {
+ background: lime;
+ padding:0;
+ border:0;
+ left: anchor(right);
+ top: anchor(top);
+ }
+</style>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-anchor-display-none.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-display-none.tentative.html
new file mode 100644
index 0000000000..55a11fafdb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-display-none.tentative.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tests that a popover can be anchored to an unrendered element.</title>
+<link rel=author href="mailto:xiaochengh@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id=popover popover anchor=anchor></div>
+<div id=anchor></div>
+
+<style>
+ #anchor {
+ display: none;
+ }
+ [popover] {
+ background: lime;
+ padding: 0;
+ border: 0;
+ width: 100px;
+ height: 100px;
+ top: anchor(top, 100px);
+ left: anchor(left, 100px);
+ }
+</style>
+
+<script>
+test(() => {
+ popover.showPopover();
+ assert_equals(popover.offsetLeft, 100);
+ assert_equals(popover.offsetTop, 100);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-anchor-display-ref.html b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-display-ref.html
new file mode 100644
index 0000000000..f701810da2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-display-ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:masonf@chromium.org">
+
+<p>There should be a green box attached to the right side of each orange box.</p>
+<div class=ex id=ex1><div class=anchor></div><div class=popover></div></div>
+<div class=ex id=ex2><div class=anchor></div><div class=popover></div></div>
+<div class=ex id=ex3><div class=anchor></div><div class=popover></div></div>
+<div class=ex id=ex4><div class=anchor></div><div class=popover></div></div>
+<div class=ex id=ex5><div class=anchor></div><div class=popover></div></div>
+<div class=ex id=ex6><div class=anchor></div><div class=popover></div></div>
+
+<style>
+ .ex {
+ margin: 15px;
+ font-size: 0;
+ }
+ .ex div {
+ display:inline-block;
+ width: 50px;
+ height: 50px;
+ }
+ .anchor {
+ background: orange;
+ }
+ .popover {
+ background: lime;
+ }
+</style>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-anchor-display.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-display.tentative.html
new file mode 100644
index 0000000000..d50dd6c857
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-display.tentative.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=match href="popover-anchor-display-ref.html">
+<link rel=stylesheet href="/fonts/ahem.css">
+<script src="resources/popover-utils.js"></script>
+
+<p>There should be a green box attached to the right side of each orange box.</p>
+
+<!-- Example using the `anchor` implicit reference element -->
+<div class=ex>
+ <div class=anchor id=anchor1></div>
+ <div id=popover1 popover=manual anchor=anchor1 defaultopen></div>
+</div>
+
+<!-- Example with `anchor` attribute but not using it for anchor pos -->
+<div class=ex>
+ <div id=anchor2 class=anchor></div>
+ <div id=popover2 popover=manual anchor defaultopen></div>
+</div>
+
+<!-- Example using `anchor-name` plus inset, and no `anchor` attribute -->
+<div class=ex>
+ <div id=anchor3 class=anchor></div>
+ <div id=popover3 popover=manual defaultopen></div>
+</div>
+
+<!-- Example using implicit anchor reference and inline anchor element -->
+<div class=ex>
+ <span id=anchor4>X</span>
+ <div id=popover4 popover=manual anchor=anchor4 defaultopen></div>
+</div>
+
+<!-- Example using an implicit anchor which is not the default anchor -->
+<div class=ex>
+ <div class=anchor id=anchor5></div>
+ <div id=popover5 popover=manual anchor=anchor5 defaultopen></div>
+</div>
+
+<!-- Example using a default anchor which is not the implicit anchor -->
+<div class=ex>
+ <div class=anchor id=anchor6></div>
+ <div id=popover6 popover=manual anchor=anchor1 defaultopen></div>
+</div>
+
+<script>
+showDefaultopenPopoversOnLoad();
+</script>
+
+<style>
+ .ex {
+ margin: 15px;
+ }
+ .ex div {
+ width: 50px;
+ height: 50px;
+ }
+ .anchor {
+ background: orange;
+ }
+ [popover] {
+ background: lime;
+ padding:0;
+ border:0;
+ }
+ #popover1 {
+ left: anchor(right);
+ top: anchor(top);
+ }
+ #anchor2 {
+ anchor-name: --anchor2;
+ }
+ #popover2 {
+ left: anchor(--anchor2 right);
+ top: anchor(--anchor2 top);
+ }
+ #anchor3 {
+ anchor-name: --anchor3;
+ }
+ #popover3 {
+ inset:auto;
+ left: anchor(--anchor3 right);
+ top: anchor(--anchor3 top);
+ }
+ #anchor4 {
+ font-family: Ahem;
+ font-size: 50px;
+ color: orange;
+ }
+ #popover4 {
+ left: anchor(right);
+ top: anchor(top);
+ }
+ #popover5 {
+ anchor-default: --anchor1; /* shouldn't be used */
+ left: anchor(implicit right);
+ top: anchor(implicit top);
+ }
+ #anchor6 {
+ anchor-name: --anchor6;
+ }
+ #popover6 {
+ anchor-default: --anchor6;
+ left: anchor(right); /* shouldn't use the implicit anchor */
+ top: anchor(top);
+ }
+</style>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-anchor-idl-property.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-idl-property.tentative.html
new file mode 100644
index 0000000000..1e255339f8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-idl-property.tentative.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div>
+ <button id=b1>This is an anchor button</button>
+ <div popover id=p1 anchor=b1>This is a popover</div>
+ <button id=b2 popovertarget=p1>This button invokes the popover but isn't an anchor</button>
+</div>
+
+<script>
+ test(function() {
+ assert_equals(p1.anchorElement,b1);
+ }, "popover anchorElement IDL property returns the anchor element");
+
+ test(function() {
+ assert_equals(p1.anchorElement,b1);
+ p1.anchorElement = b2;
+ assert_equals(p1.anchorElement,b2);
+ assert_equals(p1.getAttribute('anchor'),'','Idref is empty after setting element');
+ p1.anchorElement = b1; // Reset
+ }, "popover anchorElement is settable");
+</script>
+
+<div>
+ <button id=b3>button</button>
+ <div id=p2>Anchored div</div>
+</div>
+<style>
+ * {margin:0;padding:0;}
+ #b3 {width: 200px;}
+ #p2 {
+ position: absolute;
+ left: anchor(right);
+ }
+</style>
+
+<script>
+ test(function() {
+ assert_equals(p2.anchorElement,null);
+ const button = document.getElementById('b3');
+ assert_true(!!button);
+ p2.anchorElement = button;
+ assert_equals(p2.getAttribute('anchor'),'','Idref should be empty after setting element');
+ assert_equals(p2.anchorElement,button,'Element reference should be button');
+ assert_equals(p2.offsetLeft, 200, 'The anchor relationship should be functional');
+ }, "anchorElement affects anchor positioning");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-anchor-multicol-display.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-multicol-display.tentative.html
new file mode 100644
index 0000000000..fe65ec5ba4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-multicol-display.tentative.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<title>Tests popovers with implicit anchors in out-of-flow boxes</title>
+<link rel="help" href="https://drafts.csswg.org/css-anchor-1/#determining">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-1/#propdef-anchor-name">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-1/#anchor-size">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<style>
+.relpos {
+ position: relative;
+}
+.columns {
+ column-count: 2;
+ column-fill: auto;
+ column-gap: 10px;
+ column-width: 100px;
+ width: 210px;
+ height: 50px;
+}
+#anchor1 {
+ position: absolute;
+ width: 10px;
+ height: 30px;
+ background: orange;
+}
+.target {
+ /*
+ * We need a popover to use implicit anchors, and force showing it with CSS
+ * so that it's not in the top layer.
+ */
+ display: block;
+ position: absolute;
+ margin: 0;
+ border: 0;
+ padding: 0;
+ width: anchor-size(width);
+ height: anchor-size(height);
+ background: lime;
+}
+</style>
+<body onload="checkLayout('.target')">
+ <div class="spacer" style="height: 10px"></div>
+ <div class="relpos">
+ <div class="columns">
+ <div class="spacer" style="height: 10px"></div>
+ <div class="relpos">
+ <div class="spacer" style="height: 10px"></div>
+ <div class="relpos">
+ <div class="spacer" style="height: 10px"></div>
+ <div id="anchor1"></div>
+ </div>
+ <div class="target" popover anchor="anchor1"
+ data-expected-height=50></div>
+ </div>
+ </div>
+ </div>
+
+</body>
+
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-anchor-nested-display-ref.html b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-nested-display-ref.html
new file mode 100644
index 0000000000..9942b41e36
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-nested-display-ref.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:xiaochengh@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+
+<button id=main-menu-button>Show menu</button>
+
+<div id=main-menu>
+ <div>Foo</div>
+ <button id=nested-menu-button>
+ Show nested menu
+ </button>
+ <div>Bar</div>
+</div>
+
+<div id=nested-menu>
+ Baz
+</div>
+
+<style>
+#main-menu-button {
+ position: absolute;
+ top: 200px;
+ left: 100px;
+ width: 100px;
+}
+
+#main-menu {
+ position: absolute;
+ top: 200px;;
+ left: 200px;
+ width: 150px;
+ line-height: 20px;
+}
+
+#nested-menu-button {
+ width: 100%;
+}
+
+#nested-menu {
+ position: absolute;
+ top: 220px;
+ left: 350px;
+}
+
+[popover] {
+ border: 0;
+ margin: 0;
+ padding: 0;
+}
+</style>
+
+<script>
+document.getElementById('main-menu-button').click();
+document.getElementById('nested-menu-button').click();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-anchor-nested-display.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-nested-display.tentative.html
new file mode 100644
index 0000000000..b60ff49e09
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-nested-display.tentative.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:xiaochengh@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=match href="popover-anchor-nested-display-ref.html">
+
+<button id=main-menu-button popovertarget=main-menu>Show menu</button>
+
+<div id=main-menu popover anchor=main-menu-button>
+ <div>Foo</div>
+ <button id=nested-menu-button popovertarget=nested-menu>
+ Show nested menu
+ </button>
+ <div>Bar</div>
+</div>
+
+<div id=nested-menu popover anchor=nested-menu-button>
+ Baz
+</div>
+
+<style>
+#main-menu-button {
+ position: absolute;
+ top: 200px;
+ left: 100px;
+ width: 100px;
+}
+
+#main-menu {
+ top: anchor(top);
+ left: anchor(right);
+ width: 150px;
+ line-height: 20px;
+}
+
+#nested-menu-button {
+ width: 100%;
+}
+
+#nested-menu {
+ top: anchor(top);
+ left: anchor(right);
+}
+
+[popover] {
+ border: 0;
+ margin: 0;
+ padding: 0;
+}
+</style>
+
+<script>
+document.getElementById('main-menu-button').click();
+document.getElementById('nested-menu-button').click();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-anchor-nesting.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-nesting.tentative.html
new file mode 100644
index 0000000000..c3ea4f2165
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-nesting.tentative.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover anchor nesting</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<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>
+<script src="resources/popover-utils.js"></script>
+
+<body>
+
+<!-- This example has the anchor (b1) for one popover (p1)
+ which contains a separate popover (p2) which is anchored
+ by a separate anchor (b2). -->
+<button id=b1 onclick='p1.showPopover()'>Popover 1
+ <div popover id=p2 anchor=b2>
+ <span id=inside2>Inside popover 2</span>
+ </div>
+</button>
+<div popover id=p1 anchor=b1>This is popover 1</div>
+<button id=b2 onclick='p2.showPopover()'>Popover 2</button>
+
+<style>
+ #p1 { top:50px; }
+ #p2 { top:50px; left:250px; }
+ [popover] { border: 5px solid red; }
+</style>
+
+
+<script>
+ const popover1 = document.querySelector('#p1');
+ const button1 = document.querySelector('#b1');
+ const popover2 = document.querySelector('#p2');
+
+ (async function() {
+ setup({ explicit_done: true });
+
+ popover2.showPopover();
+ assert_false(popover1.matches(':popover-open'));
+ assert_true(popover2.matches(':popover-open'));
+ await clickOn(button1);
+ test(t => {
+ // Button1 is the anchor for popover1, and an ancestor of popover2.
+ // Since popover2 is open, but not popover1, button1 should not be
+ // the anchor of any open popover. So popover2 should be closed.
+ assert_false(popover2.matches(':popover-open'));
+ assert_true(popover1.matches(':popover-open'));
+ },'Nested popovers (inside anchor elements) do not affect light dismiss');
+
+ done();
+ })();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-anchor-scroll-display-ref.html b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-scroll-display-ref.html
new file mode 100644
index 0000000000..926a171e9a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-scroll-display-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<div class=spacer style="height: 200px"></div>
+
+<p>There should be a green box attached to the right side of each orange box.</p>
+<div class=ex id=ex1><div class=anchor></div><div class=popover></div></div>
+<div class=ex id=ex2><div class=anchor></div><div class=popover></div></div>
+
+<div class=spacer style="height: 200vh"></div>
+
+<style>
+ .ex {
+ margin: 25px;
+ font-size: 0;
+ }
+ .ex div {
+ display:inline-block;
+ width: 100px;
+ height: 100px;
+ }
+ .anchor {
+ background: orange;
+ }
+ .popover {
+ background: lime;
+ }
+</style>
+
+<script>
+document.documentElement.scrollTop = 100;
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-anchor-scroll-display.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-scroll-display.tentative.html
new file mode 100644
index 0000000000..7ed6cf1adf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-scroll-display.tentative.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<link rel=author href="mailto:xiaochengh@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=match href="popover-anchor-scroll-display-ref.html">
+
+<div class=spacer style="height: 200px"></div>
+
+<p>There should be a green box attached to the right side of each orange box.</p>
+
+<!-- Example using the `anchor` implicit reference element -->
+<div class=ex>
+ <div class=anchor id=anchor1></div>
+ <div id=popover1 popover=manual anchor=anchor1></div>
+</div>
+
+<!-- Example using a default anchor that is not the implicit anchor -->
+<div class=ex>
+ <div class=anchor id=anchor2></div>
+ <div id=popover2 popover=manual anchor=fake-anchor></div>
+</div>
+
+<!-- A position:fixed fake anchor. Any popover anchored to it won't move when
+ the document is scrolled. -->
+<div id=fake-anchor></div>
+
+<div class=spacer style="height: 200vh"></div>
+
+<style>
+ .ex {
+ margin: 25px;
+ }
+ .ex div {
+ width: 100px;
+ height: 100px;
+ }
+ .anchor {
+ background: orange;
+ }
+ [popover] {
+ background: lime;
+ padding:0;
+ border:0;
+ }
+ #popover1 {
+ left: anchor(right);
+ top: anchor(top);
+ }
+ #fake-anchor {
+ position: fixed;
+ anchor-name: --fake-anchor;
+ }
+ #anchor2 {
+ anchor-name: --anchor2;
+ }
+ #popover2 {
+ anchor-default: --anchor2;
+ left: anchor(right);
+ top: anchor(top);
+ }
+</style>
+
+<script>
+function raf() {
+ return new Promise(resolve => requestAnimationFrame(resolve));
+}
+
+async function runTest() {
+ document.querySelectorAll('[popover]').forEach(
+ popover => popover.showPopover());
+
+ // Render a frame at the intial scroll position.
+ await raf();
+ await raf();
+
+ document.documentElement.scrollTop = 100;
+ document.documentElement.classList.remove('reftest-wait');
+
+ // The popover should still be attached to the anchor.
+}
+runTest();
+</script>
+
+</html>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-anchor-transition.tentative.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-transition.tentative.tentative.html
new file mode 100644
index 0000000000..ae2a3a8e41
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-anchor-transition.tentative.tentative.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tests transitioning display property of anchored popover</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel="help" href="https://github.com/whatwg/html/pull/9144">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+body {
+ margin: 0;
+}
+
+#target {
+ transition: display 2s;
+}
+</style>
+
+<div popover anchor id="target">
+ Popover
+</div>
+
+<script>
+test(() => {
+ target.showPopover();
+ const xBefore = target.offsetLeft;
+ const yBefore = target.offsetTop;
+
+ target.hidePopover();
+ assert_equals(target.offsetLeft, xBefore, 'Should not shift in x axis');
+ assert_equals(target.offsetTop, yBefore, 'Should not shift in y axis')
+}, 'Transitioning display property of an anchored popover should not cause a position shift');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-and-svg-ref.html b/testing/web-platform/tests/html/semantics/popovers/popover-and-svg-ref.html
new file mode 100644
index 0000000000..db52e77d2b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-and-svg-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover is only effective on HTMLElement, not on svg element</title>
+<style>
+svg {
+ width: 100px;
+ height: 100px;
+ background-color:green;
+}
+</style>
+<svg ></svg>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-and-svg.html b/testing/web-platform/tests/html/semantics/popovers/popover-and-svg.html
new file mode 100644
index 0000000000..c5e8bb42a8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-and-svg.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover is only effective on HTMLElement, not on svg element</title>
+<link rel="author" href="mailto:cathiechen@igalia.com">
+<link rel=help href="https://html.spec.whatwg.org/#the-popover-attribute">
+<link rel="match" href="popover-and-svg-ref.html">
+<style>
+svg {
+ width: 100px;
+ height: 100px;
+ background-color:green;
+}
+[popover] {
+ top: 100px;
+ bottom: auto;
+}
+</style>
+<svg popover></svg>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-appearance-ref.html b/testing/web-platform/tests/html/semantics/popovers/popover-appearance-ref.html
new file mode 100644
index 0000000000..7ceca94559
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-appearance-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover element appearance</title>
+<link rel="stylesheet" href="resources/popover-styles.css">
+
+<style>
+.fake-popover {top: 100px; bottom: auto;}
+#blank {left: -300px;}
+#auto {left: -100px;}
+#manual {left: 100px;}
+#invalid {left: 300px;}
+</style>
+
+<p>There should be four popovers with similar appearance.</p>
+<div class="fake-popover" id=blank>Blank</div>
+<div class="fake-popover" id=auto>Auto</div>
+<div class="fake-popover" id=manual>Manual</div>
+<div class="fake-popover" id=invalid>Invalid</div>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-appearance.html b/testing/web-platform/tests/html/semantics/popovers/popover-appearance.html
new file mode 100644
index 0000000000..e9050bdeb9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-appearance.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover element appearance</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel="match" href="popover-appearance-ref.html">
+
+<style>
+[popover] {top: 100px; bottom: auto;}
+[popover=""] {left: -300px}
+[popover=auto] {left: -100px; }
+[popover=manual] {left: 100px; }
+[popover=invalid] {left: 300px; }
+</style>
+
+<p>There should be four popovers with similar appearance.</p>
+<div popover>Blank
+ <div popover=auto>Auto</div>
+</div>
+<div popover=manual>Manual</div>
+<!-- This ensures unsupported popover values are treated as popover=manual -->
+<div popover=invalid>Invalid</div>
+<script>
+ document.querySelectorAll('[popover]').forEach(p => p.showPopover());
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-attribute-all-elements.html b/testing/web-platform/tests/html/semantics/popovers/popover-attribute-all-elements.html
new file mode 100644
index 0000000000..5a536f026e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-attribute-all-elements.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<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-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+<script src="../../resources/common.js"></script>
+
+<body>
+<script>
+setup({ explicit_done: true });
+window.onload = () => {
+ // Loop through all HTML elements that render a box by default:
+ let elementsThatDontRender = ['area', 'audio','base','br','datalist','dialog','embed','head','link','meta','noscript','optgroup','option','param','rp','script','slot','style','template','title','wbr'];
+ const elements = HTML5_ELEMENTS.filter(el => !elementsThatDontRender.includes(el));
+ elements.forEach(tag => {
+ test((t) => {
+ const element = document.createElement(tag);
+ element.setAttribute('popover','auto');
+ document.body.appendChild(element);
+ t.add_cleanup(() => element.remove());
+ assertIsFunctionalPopover(element, true);
+ }, `A <${tag} popover> element should behave as a popover.`);
+ test((t) => {
+ const element = document.createElement(tag);
+ document.body.appendChild(element);
+ t.add_cleanup(() => element.remove());
+ assertNotAPopover(element);
+ }, `A <${tag}> element should *not* behave as a popover.`);
+ });
+ elementsThatDontRender.forEach(tag => {
+ test((t) => {
+ const element = document.createElement(tag);
+ element.setAttribute('popover','auto');
+ document.body.appendChild(element);
+ t.add_cleanup(() => element.remove());
+ assertIsFunctionalPopover(element, false);
+ }, `A <${tag} popover> element should not be rendered.`);
+ });
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-attribute-basic.html b/testing/web-platform/tests/html/semantics/popovers/popover-attribute-basic.html
new file mode 100644
index 0000000000..2af3bbc137
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-attribute-basic.html
@@ -0,0 +1,359 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<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-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<div id=popovers>
+ <div popover id=boolean>Popover</div>
+ <div popover="">Popover</div>
+ <div popover=auto>Popover</div>
+ <div popover=hint>Popover</div>
+ <div popover=manual>Popover</div>
+ <article popover>Different element type</article>
+ <header popover>Different element type</header>
+ <nav popover>Different element type</nav>
+ <input type=text popover value="Different element type">
+ <dialog popover>Dialog with popover attribute</dialog>
+ <dialog popover="manual">Dialog with popover=manual</dialog>
+ <div popover=true>Invalid popover value - defaults to popover=manual</div>
+ <div popover=popover>Invalid popover value - defaults to popover=manual</div>
+ <div popover=invalid>Invalid popover value - defaults to popover=manual</div>
+</div>
+
+<div id=nonpopovers>
+ <div>Not a popover</div>
+ <dialog open>Dialog without popover attribute</dialog>
+</div>
+
+<div id=outside></div>
+<style>
+[popover] {
+ inset:auto;
+ top:0;
+ left:0;
+}
+#outside {
+ position:fixed;
+ top:200px;
+ left:200px;
+ height:10px;
+ width:10px;
+}
+</style>
+
+<script>
+setup({ explicit_done: true });
+window.onload = () => {
+ const outsideElement = document.getElementById('outside');
+
+ // Start with the provided examples:
+ Array.from(document.getElementById('popovers').children).forEach(popover => {
+ test((t) => {
+ assertIsFunctionalPopover(popover, true);
+ }, `The element ${popover.outerHTML} should behave as a popover.`);
+ });
+ Array.from(document.getElementById('nonpopovers').children).forEach(nonPopover => {
+ test((t) => {
+ assertNotAPopover(nonPopover);
+ }, `The element ${nonPopover.outerHTML} should *not* behave as a popover.`);
+ });
+
+ function createPopover(t) {
+ const popover = document.createElement('div');
+ document.body.appendChild(popover);
+ t.add_cleanup(() => popover.remove());
+ popover.setAttribute('popover','auto');
+ return popover;
+ }
+
+ test((t) => {
+ // You can set the `popover` attribute to anything.
+ // Setting the `popover` IDL to a string sets the content attribute to exactly that, always.
+ // Getting the `popover` IDL value only retrieves valid values.
+ const popover = createPopover(t);
+ assert_equals(popover.popover,'auto');
+ popover.setAttribute('popover','auto');
+ assert_equals(popover.popover,'auto');
+ popover.setAttribute('popover','AuTo');
+ assert_equals(popover.popover,'auto','Case is normalized in IDL');
+ assert_equals(popover.getAttribute('popover'),'AuTo','Case is *not* normalized/changed in the content attribute');
+ popover.popover='aUtO';
+ assert_equals(popover.popover,'auto','Case is normalized in IDL');
+ assert_equals(popover.getAttribute('popover'),'aUtO','Value set from IDL is propagated exactly to the content attribute');
+ popover.setAttribute('popover','invalid');
+ assert_equals(popover.popover,'manual','Invalid values should reflect as "manual"');
+ popover.removeAttribute('popover');
+ assert_equals(popover.popover,null,'No value should reflect as null');
+ if (popoverHintSupported()) {
+ popover.popover='hint';
+ assert_equals(popover.getAttribute('popover'),'hint');
+ }
+ popover.popover='auto';
+ assert_equals(popover.getAttribute('popover'),'auto');
+ popover.popover='';
+ assert_equals(popover.getAttribute('popover'),'');
+ assert_equals(popover.popover,'auto');
+ popover.popover='AuTo';
+ assert_equals(popover.getAttribute('popover'),'AuTo');
+ assert_equals(popover.popover,'auto');
+ popover.popover='invalid';
+ assert_equals(popover.getAttribute('popover'),'invalid','IDL setter allows any value');
+ assert_equals(popover.popover,'manual','but IDL getter reflects "manual"');
+ popover.popover='';
+ assert_equals(popover.getAttribute('popover'),'','IDL setter propagates exactly');
+ assert_equals(popover.popover,'auto','Empty should map to auto in IDL');
+ popover.popover='auto';
+ popover.popover=null;
+ assert_equals(popover.getAttribute('popover'),null,'Setting null for the IDL property should remove the content attribute');
+ assert_equals(popover.popover,null,'Null returns null');
+ popover.popover='auto';
+ popover.popover=undefined;
+ assert_equals(popover.getAttribute('popover'),null,'Setting undefined for the IDL property should remove the content attribute');
+ assert_equals(popover.popover,null,'undefined returns null');
+ },'IDL attribute reflection');
+
+ test((t) => {
+ const popover = createPopover(t);
+ assertIsFunctionalPopover(popover, true);
+ popover.removeAttribute('popover');
+ assertNotAPopover(popover);
+ popover.setAttribute('popover','AuTo');
+ assertIsFunctionalPopover(popover, true);
+ popover.removeAttribute('popover');
+ popover.setAttribute('PoPoVeR','AuTo');
+ assertIsFunctionalPopover(popover, true);
+ // Via IDL also
+ popover.popover = 'auto';
+ assertIsFunctionalPopover(popover, true);
+ popover.popover = 'aUtO';
+ assertIsFunctionalPopover(popover, true);
+ popover.popover = 'invalid'; // treated as "manual"
+ assertIsFunctionalPopover(popover, true);
+ },'Popover attribute value should be case insensitive');
+
+ test((t) => {
+ const popover = createPopover(t);
+ assertIsFunctionalPopover(popover, true);
+ popover.setAttribute('popover','manual'); // Change popover type
+ assertIsFunctionalPopover(popover, true);
+ popover.setAttribute('popover','invalid'); // Change popover type to something invalid
+ assertIsFunctionalPopover(popover, true);
+ popover.popover = 'manual'; // Change popover type via IDL
+ assertIsFunctionalPopover(popover, true);
+ popover.popover = 'invalid'; // Make invalid via IDL (treated as "manual")
+ assertIsFunctionalPopover(popover, true);
+ },'Changing attribute values for popover should work');
+
+ test((t) => {
+ const popover = createPopover(t);
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ if (popoverHintSupported()) {
+ popover.setAttribute('popover','hint'); // Change popover type
+ assert_false(popover.matches(':popover-open'));
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ }
+ popover.setAttribute('popover','manual');
+ assert_false(popover.matches(':popover-open'));
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ popover.setAttribute('popover','invalid');
+ assert_true(popover.matches(':popover-open'),'From "manual" to "invalid" (which is interpreted as "manual") should not close the popover');
+ popover.setAttribute('popover','auto');
+ assert_false(popover.matches(':popover-open'),'From "invalid" ("manual") to "auto" should hide the popover');
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ popover.setAttribute('popover','invalid');
+ assert_false(popover.matches(':popover-open'),'From "auto" to "invalid" (which is interpreted as "manual") should close the popover');
+ },'Changing attribute values should close open popovers');
+
+ const validTypes = popoverHintSupported() ? ["auto","hint","manual"] : ["auto","manual"];
+ validTypes.forEach(type => {
+ test((t) => {
+ const popover = createPopover(t);
+ popover.setAttribute('popover',type);
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ popover.remove();
+ assert_false(popover.matches(':popover-open'));
+ document.body.appendChild(popover);
+ assert_false(popover.matches(':popover-open'));
+ },`Removing a visible popover=${type} element from the document should close the popover`);
+
+ test((t) => {
+ const popover = createPopover(t);
+ popover.setAttribute('popover',type);
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ assert_false(popover.matches(':modal'));
+ popover.hidePopover();
+ },`A showing popover=${type} does not match :modal`);
+
+ test((t) => {
+ const popover = createPopover(t);
+ popover.setAttribute('popover',type);
+ assert_false(popover.matches(':popover-open'));
+ // FIXME: Once :open/:closed are defined in HTML we should remove these two constants.
+ const openPseudoClassIsSupported = CSS.supports('selector(:open))');
+ const closePseudoClassIsSupported = CSS.supports('selector(:closed))');
+ assert_false(openPseudoClassIsSupported && popover.matches(':open'),'popovers never match :open');
+ assert_false(closePseudoClassIsSupported && popover.matches(':closed'),'popovers never match :closed');
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ assert_false(openPseudoClassIsSupported && popover.matches(':open'),'popovers never match :open');
+ assert_false(closePseudoClassIsSupported && popover.matches(':closed'),'popovers never match :closed');
+ popover.hidePopover();
+ },`A popover=${type} never matches :open or :closed`);
+ });
+
+ test((t) => {
+ const other_popover = createPopover(t);
+ other_popover.setAttribute('popover','auto');
+ other_popover.showPopover();
+ const popover = createPopover(t);
+ popover.setAttribute('popover','auto');
+ other_popover.addEventListener('beforetoggle', (e) => {
+ if (e.newState !== "closed")
+ return;
+ popover.setAttribute('popover','manual');
+ },{once: true});
+ assert_true(other_popover.matches(':popover-open'));
+ assert_false(popover.matches(':popover-open'));
+ assert_throws_dom('InvalidStateError', () => popover.showPopover());
+ assert_false(other_popover.matches(':popover-open'),'unrelated popover is hidden');
+ assert_false(popover.matches(':popover-open'),'popover is not shown if its type changed during show');
+ },`Changing the popover type in a "beforetoggle" event handler should throw an exception (during showPopover())`);
+
+ test((t) => {
+ const popover = createPopover(t);
+ popover.setAttribute('popover','auto');
+ const other_popover = createPopover(t);
+ other_popover.setAttribute('popover','auto');
+ popover.appendChild(other_popover);
+ popover.showPopover();
+ other_popover.showPopover();
+ let nested_popover_hidden=false;
+ other_popover.addEventListener('beforetoggle', (e) => {
+ if (e.newState !== "closed")
+ return;
+ nested_popover_hidden = true;
+ popover.setAttribute('popover','manual');
+ },{once: true});
+ popover.addEventListener('beforetoggle', (e) => {
+ if (e.newState !== "closed")
+ return;
+ assert_true(nested_popover_hidden,'The nested popover should be hidden first');
+ },{once: true});
+ assert_true(popover.matches(':popover-open'));
+ assert_true(other_popover.matches(':popover-open'));
+ popover.hidePopover(); // Calling hidePopover on a hidden popover should not throw.
+ assert_false(other_popover.matches(':popover-open'),'unrelated popover is hidden');
+ assert_false(popover.matches(':popover-open'),'popover is still hidden if its type changed during hide event');
+ other_popover.hidePopover(); // Calling hidePopover on a hidden popover should not throw.
+ },`Changing the popover type in a "beforetoggle" event handler during hidePopover() should not throw an exception`);
+
+ test(t => {
+ const popover = document.createElement('div');
+ assert_throws_dom('NotSupportedError', () => popover.hidePopover(),
+ 'Calling hidePopover on an element without a popover attribute should throw.');
+ popover.setAttribute('popover', 'auto');
+ popover.hidePopover(); // Calling hidePopover on a disconnected popover should not throw.
+ assert_throws_dom('InvalidStateError', () => popover.showPopover(),
+ 'Calling showPopover on a disconnected popover should throw.');
+ },'Calling hidePopover on a disconnected popover should not throw.');
+
+ function interpretedType(typeString,method) {
+ if (validTypes.includes(typeString))
+ return typeString;
+ if (typeString === undefined)
+ return "invalid-value-undefined";
+ if (method === "idl" && typeString === null)
+ return "invalid-value-idl-null";
+ return "manual"; // Invalid types default to "manual"
+ }
+ function setPopoverValue(popover,type,method) {
+ switch (method) {
+ case "attr":
+ if (type === undefined) {
+ popover.removeAttribute('popover');
+ } else {
+ popover.setAttribute('popover',type);
+ }
+ break;
+ case "idl":
+ popover.popover = type;
+ break;
+ default:
+ assert_notreached();
+ }
+ }
+ ["attr","idl"].forEach(method => {
+ validTypes.forEach(type => {
+ [...validTypes,"invalid",null,undefined].forEach(newType => {
+ [...validTypes,"invalid",null,undefined].forEach(inEventType => {
+ promise_test(async (t) => {
+ const popover = createPopover(t);
+ setPopoverValue(popover,type,method);
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ let gotEvent = false;
+ popover.addEventListener('beforetoggle', (e) => {
+ if (e.newState !== "closed")
+ return;
+ gotEvent = true;
+ setPopoverValue(popover,inEventType,method);
+ },{once:true});
+ setPopoverValue(popover,newType,method);
+ if (type===interpretedType(newType,method)) {
+ // Keeping the type the same should not hide it or fire events.
+ assert_true(popover.matches(':popover-open'),'popover should remain open when not changing the type');
+ assert_false(gotEvent);
+ try {
+ popover.hidePopover(); // Cleanup
+ } catch (e) {}
+ } else {
+ // Changing the type at all should hide the popover. The hide event
+ // handler should run, set a new type, and that type should end up
+ // as the final result.
+ assert_false(popover.matches(':popover-open'));
+ assert_true(gotEvent);
+ if (inEventType === undefined || (method ==="idl" && inEventType === null)) {
+ assert_throws_dom("NotSupportedError",() => popover.showPopover(),'We should have removed the popover attribute, so showPopover should throw');
+ } else {
+ // Make sure the attribute is correct.
+ assert_equals(popover.getAttribute('popover'),String(inEventType),'Content attribute');
+ assert_equals(popover.popover, interpretedType(inEventType,method),'IDL attribute');
+ // Make sure the type is really correct, via behavior.
+ popover.showPopover(); // Show it
+ assert_true(popover.matches(':popover-open'),'Popover should function');
+ await clickOn(outsideElement); // Try to light dismiss
+ switch (interpretedType(inEventType,method)) {
+ case 'manual':
+ assert_true(popover.matches(':popover-open'),'A popover=manual should not light-dismiss');
+ popover.hidePopover();
+ break;
+ case 'auto':
+ case 'hint':
+ assert_false(popover.matches(':popover-open'),'A popover=auto should light-dismiss');
+ break;
+ }
+ }
+ }
+ },`Changing a popover from ${type} to ${newType} (via ${method}), and then ${inEventType} during 'beforetoggle' works`);
+ });
+ });
+ });
+ });
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-backdrop-appearance-ref.html b/testing/web-platform/tests/html/semantics/popovers/popover-backdrop-appearance-ref.html
new file mode 100644
index 0000000000..bf2b16c3f5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-backdrop-appearance-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover ::backdrop pseudo element appearance</title>
+<link rel="stylesheet" href="resources/popover-styles.css">
+
+<style>
+#bottom { top: 70px; left: 70px; }
+#middle { top: 120px; left: 120px; }
+#top { top: 170px; left: 170px; }
+.fake-popover-backdrop {
+ height: 200px;
+ width: 200px;
+}
+#bottom-backdrop {
+ top: 50px;
+ left: 50px;
+ background-color: rgb(0, 50, 0);
+}
+#middle-backdrop {
+ top: 100px;
+ left: 100px;
+ background-color: rgb(0, 130, 0);
+}
+#top-backdrop {
+ top: 150px;
+ left: 150px;
+ background-color: rgb(0, 210, 0);
+}
+.fake-popover {
+ margin:0;
+}
+</style>
+<p>Test for [popover]::backdrop presence and stacking order. The test passes
+ if there are 3 stacked boxes, with the brightest green on top.</p>
+<div popover id=bottom>Bottom
+ <div popover id=middle>Middle
+ <div popover=manual id=top>Top</div>
+ </div>
+</div>
+<div id="bottom-backdrop" class="fake-popover-backdrop"></div>
+<div id="bottom" class="fake-popover">Bottom</div>
+<div id="middle-backdrop" class="fake-popover-backdrop"></div>
+<div id="middle" class="fake-popover">Middle</div>
+<div id="top-backdrop" class="fake-popover-backdrop"></div>
+<div id="top" class="fake-popover">Top</div>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-backdrop-appearance.html b/testing/web-platform/tests/html/semantics/popovers/popover-backdrop-appearance.html
new file mode 100644
index 0000000000..cf57aee69e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-backdrop-appearance.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover ::backdrop pseudo element appearance</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel="match" href="popover-backdrop-appearance-ref.html">
+
+<style>
+#bottom { top: 70px; left: 70px; }
+#middle { top: 120px; left: 120px; }
+#top { top: 170px; left: 170px; }
+::backdrop { height: 200px; width: 200px; }
+#bottom::backdrop {
+ top: 50px;
+ left: 50px;
+ background-color: rgb(0, 50, 0);
+ z-index: 100; /* z-index has no effect. */
+}
+#middle::backdrop {
+ top: 100px;
+ left: 100px;
+ background-color: rgb(0, 130, 0);
+ z-index: -100; /* z-index has no effect. */
+}
+#top::backdrop {
+ top: 150px;
+ left: 150px;
+ background-color: rgb(0, 210, 0);
+ z-index: 0; /* z-index has no effect. */
+}
+[popover] {
+ margin:0;
+}
+</style>
+<p>Test for [popover]::backdrop presence and stacking order. The test passes
+ if there are 3 stacked boxes, with the brightest green on top.</p>
+<div popover id=bottom>Bottom
+ <div popover=hint id=middle>Middle
+ <div popover=manual id=top>Top</div>
+ </div>
+</div>
+<script>
+document.getElementById('bottom').showPopover();
+document.getElementById('middle').showPopover();
+document.getElementById('top').showPopover();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-beforetoggle-opening-event.html b/testing/web-platform/tests/html/semantics/popovers/popover-beforetoggle-opening-event.html
new file mode 100644
index 0000000000..41bb9aa82c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-beforetoggle-opening-event.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover beforetoggle event</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div popover></div>
+
+<script>
+test(() => {
+ let frameCount = 0;
+ requestAnimationFrame(() => {++frameCount;});
+ const popover = document.querySelector('[popover]');
+ const testText = 'Show Event Occurred';
+ popover.addEventListener('beforetoggle',(e) => {
+ assert_false(e.bubbles, 'beforetoggle event does not bubble');
+ if (e.newState !== "open")
+ return;
+ popover.textContent = testText;
+ })
+ popover.offsetHeight;
+ assert_equals(popover.textContent,"");
+ assert_equals(frameCount,0);
+ popover.showPopover();
+ popover.offsetHeight;
+ assert_equals(popover.textContent,testText);
+ assert_equals(frameCount,0,'nothing should be rendered before the popover is updated');
+ popover.hidePopover(); // Cleanup
+},'Ensure the `beforetoggle` event can be used to populate content before the popover renders');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-change-type.html b/testing/web-platform/tests/html/semantics/popovers/popover-change-type.html
new file mode 100644
index 0000000000..978d1d1495
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-change-type.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/9034">
+<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/testdriver-actions.js"></script>
+<script src="/html/semantics/popovers/resources/popover-utils.js"></script>
+
+<div id=mypopover>popover</div>
+
+<script>
+promise_test(async () => {
+ const mypopover = document.getElementById('mypopover');
+
+ mypopover.popover = "manual";
+ mypopover.showPopover();
+
+ await new Promise(resolve => {
+ mypopover.addEventListener("beforetoggle", (e) => {
+ if (e.newState === "closed") {
+ mypopover.remove();
+ requestAnimationFrame(() => {
+ document.body.append(mypopover);
+ mypopover.showPopover();
+ resolve();
+ });
+ }
+ }, {once: true});
+
+ mypopover.popover = "auto";
+ });
+
+ assert_true(mypopover.matches(':popover-open'),
+ 'The popover should be open after the toggling sequence.');
+
+ await sendEscape();
+ assert_false(mypopover.matches(':popover-open'),
+ 'The popover should light dismiss because it is in the auto state.');
+}, 'Changing the popover attribute should always update the auto/manual behavior.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-close-request.html b/testing/web-platform/tests/html/semantics/popovers/popover-close-request.html
new file mode 100644
index 0000000000..830a40e060
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-close-request.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Popover close request behavior</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>
+<script src="/common/top-layer.js"></script>
+<script src="/close-watcher/resources/helpers.js"></script>
+
+<div popover id=p1>
+ Inside popover 1
+ <div popover id=p2>Inside popover 2</div>
+</div>
+
+<script>
+promise_test(async () => {
+ const popover1 = document.querySelector('#p1');
+ const popover2 = document.querySelector('#p2');
+
+ popover1.showPopover();
+
+ // Bless the opening of popover2, so it doesn't get grouped with popover1 by
+ // the close watcher infrastructure.
+ await blessTopLayer(popover1);
+ popover2.showPopover();
+
+ assert_true(popover1.matches(':popover-open'), "Starting: popover1 must be open");
+ assert_true(popover2.matches(':popover-open'), "Starting: popover2 must be open");
+
+ await sendCloseRequest();
+ assert_true(popover1.matches(':popover-open'), "After one close request, popover1 must be open");
+ assert_false(popover2.matches(':popover-open'), "After one close request, popover2 must be closed");
+
+ await sendCloseRequest();
+ assert_false(popover1.matches(':popover-open'), "After two close requests, popover1 must be closed");
+ assert_false(popover2.matches(':popover-open'), "After two close requests, popover2 must be closed");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-css-properties.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-css-properties.tentative.html
new file mode 100644
index 0000000000..93d388b02b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-css-properties.tentative.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Popover API CSS parsing with computed values</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<div id=target></div>
+<div id=scratch></div>
+
+<script>
+function testprop(prop) {
+ // Computed values:
+ test_computed_value(prop, '0s');
+ test_computed_value(prop, '0ms', '0s');
+ test_computed_value(prop, '32s');
+ test_computed_value(prop, '123ms', '0.123s');
+
+ // Valid values:
+ test_valid_value(prop, '0s');
+ test_valid_value(prop, '0ms');
+ test_valid_value(prop, '32s');
+ test_valid_value(prop, '123ms');
+ test_valid_value(prop, 'inherit');
+
+ // Invalid values:
+ test_invalid_value(prop, '0');
+ test_invalid_value(prop, 'foo');
+ test_invalid_value(prop, '-1s');
+ test_invalid_value(prop, 'none');
+ test_invalid_value(prop, 'auto');
+
+ // Animations:
+ test_interpolation({
+ property: prop,
+ from: '1s',
+ to: '2000ms',
+ }, [
+ {at: -1.5, expect: '0s'}, // Clamping at 0
+ {at: -0.3, expect: '0.7s'},
+ {at: 0, expect: '1s'},
+ {at: 0.5, expect: '1.5s'},
+ {at: 1, expect: '2s'},
+ {at: 1.5, expect: '2.5s'},
+ ]);
+}
+
+testprop('popover-show-delay');
+testprop('popover-hide-delay');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-dialog-appearance-ref.html b/testing/web-platform/tests/html/semantics/popovers/popover-dialog-appearance-ref.html
new file mode 100644
index 0000000000..12efbb6b1e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-dialog-appearance-ref.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Dialog-Popover appearance</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+
+<p>Both dialogs should have the same shades of background.</p>
+<p>The popover should have a completely-transparent ::backdrop.</p>
+<dialog popover id=d1>This is a modal dialog</dialog>
+<dialog popover id=d2>This is a dialog popover</dialog>
+
+<style>
+ dialog {
+ left: 50px;
+ right: auto;
+ bottom: auto;
+ }
+ #d1 {top:100px;}
+ #d2 {top:150px;}
+ /* Force backdrop to spec: */
+ #d1::backdrop {
+ /* https://html.spec.whatwg.org/multipage/rendering.html#flow-content-3 */
+ background-color: rgba(0, 0, 0, 0.1);
+ }
+ #d2::backdrop {
+ /* When shown as a popover, backdrop must be transparent */
+ background-color: transparent;
+ }
+</style>
+
+<script>
+ document.getElementById('d1').showModal();
+ document.getElementById('d2').showPopover();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-dialog-appearance.html b/testing/web-platform/tests/html/semantics/popovers/popover-dialog-appearance.html
new file mode 100644
index 0000000000..8b4edadee9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-dialog-appearance.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Dialog-Popover appearance</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel="match" href="popover-dialog-appearance-ref.html">
+
+<p>Both dialogs should have the same shades of background.</p>
+<p>The popover should have a completely-transparent ::backdrop.</p>
+<dialog popover id=d1>This is a modal dialog</dialog>
+<dialog popover id=d2>This is a dialog popover</dialog>
+
+<style>
+ dialog {
+ left: 50px;
+ right: auto;
+ bottom: auto;
+ }
+ #d1 {top:100px;}
+ #d2 {top:150px;}
+</style>
+
+<script>
+ document.getElementById('d1').showModal();
+ document.getElementById('d2').showPopover();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-dialog-crash.html b/testing/web-platform/tests/html/semantics/popovers/popover-dialog-crash.html
new file mode 100644
index 0000000000..e7579d5a38
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-dialog-crash.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8" />
+<title>Dialog-Popover crash</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<p>This test passes if it does not crash.</p>
+<dialog popover>This is a modal dialog</dialog>
+<div popover>This is a popover</div>
+
+<script>
+ const dialog = document.querySelector('dialog[popover]');
+ const popover = document.querySelector('div[popover]');
+ dialog.showModal();
+ popover.showPopover();
+ clickOn(dialog)
+ .then(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-document-open.html b/testing/web-platform/tests/html/semantics/popovers/popover-document-open.html
new file mode 100644
index 0000000000..80ac86aced
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-document-open.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div popover id=popover1>Popover</div>
+
+<script>
+ window.onload = () => {
+ test((t) => {
+ const popover1 = document.querySelector('#popover1');
+ popover1.showPopover();
+ assert_true(popover1.matches(':popover-open'));
+ assert_true(!document.querySelector('#popover2'));
+ document.open();
+ document.write('<!DOCTYPE html><div popover id=popover2>Popover</div>');
+ document.close();
+ assert_true(!document.querySelector('#popover1'),'popover1 should be removed from the document');
+ assert_true(!!document.querySelector('#popover2'),'popover2 should be in the document');
+ assert_false(popover1.matches(':popover-open'),'popover1 should have been hidden when it was removed from the document');
+ assert_false(popover1.matches(':popover-open'),'popover2 shouldn\'t be showing yet');
+ popover2.showPopover();
+ assert_true(popover2.matches(':popover-open'),'popover2 should be able to be shown');
+ popover2.hidePopover();
+ },'document.open should not break popovers');
+ };
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-events.html b/testing/web-platform/tests/html/semantics/popovers/popover-events.html
new file mode 100644
index 0000000000..4d58001f7d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-events.html
@@ -0,0 +1,216 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover events</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<div popover>Popover</div>
+
+<script>
+function getPopoverAndSignal(t) {
+ const popover = document.querySelector('[popover]');
+ const controller = new AbortController();
+ const signal = controller.signal;
+ t.add_cleanup(() => controller.abort());
+ return {popover, signal};
+}
+window.onload = () => {
+ for(const method of ["listener","attribute"]) {
+ promise_test(async t => {
+ const {popover,signal} = getPopoverAndSignal(t);
+ assert_false(popover.matches(':popover-open'));
+ let showCount = 0;
+ let afterShowCount = 0;
+ let hideCount = 0;
+ let afterHideCount = 0;
+ function listener(e) {
+ assert_false(e.bubbles,'toggle events should not bubble');
+ if (e.type === "beforetoggle") {
+ if (e.newState === "open") {
+ ++showCount;
+ assert_equals(e.oldState,"closed",'The "beforetoggle" event should be fired before the popover is open');
+ assert_false(e.target.matches(':popover-open'),'The popover should *not* be in the :popover-open state when the opening event fires.');
+ assert_true(e.cancelable,'beforetoggle should be cancelable only for the "show" transition');
+ } else {
+ ++hideCount;
+ assert_equals(e.newState,"closed",'Popover toggleevent states should be "open" and "closed"');
+ assert_equals(e.oldState,"open",'The "beforetoggle" event should be fired before the popover is closed')
+ assert_true(e.target.matches(':popover-open'),'The popover should be in the :popover-open state when the hiding event fires.');
+ assert_false(e.cancelable,'beforetoggle should be cancelable only for the "show" transition');
+ e.preventDefault(); // beforetoggle should be cancelable only for the "show" transition
+ }
+ } else {
+ assert_equals(e.type,"toggle",'Popover events should be "beforetoggle" and "toggle"')
+ assert_false(e.cancelable,'toggle should never be cancelable');
+ e.preventDefault(); // toggle should never be cancelable
+ if (e.newState === "open") {
+ ++afterShowCount;
+ if (document.body.contains(e.target)) {
+ assert_true(e.target.matches(':popover-open'),'The popover should be in the :popover-open state when the after opening event fires.');
+ }
+ } else {
+ ++afterHideCount;
+ assert_equals(e.newState,"closed",'Popover toggleevent states should be "open" and "closed"');
+ assert_false(e.target.matches(':popover-open'),'The popover should *not* be in the :popover-open state when the after hiding event fires.');
+ }
+ e.preventDefault(); // "toggle" should not be cancelable.
+ }
+ };
+ switch (method) {
+ case "listener":
+ // These events do *not* bubble.
+ popover.addEventListener('beforetoggle', listener, {signal});
+ popover.addEventListener('toggle', listener, {signal});
+ break;
+ case "attribute":
+ assert_false(popover.hasAttribute('onbeforetoggle'));
+ t.add_cleanup(() => popover.removeAttribute('onbeforetoggle'));
+ popover.onbeforetoggle = listener;
+ assert_false(popover.hasAttribute('ontoggle'));
+ t.add_cleanup(() => popover.removeAttribute('ontoggle'));
+ popover.ontoggle = listener;
+ break;
+ default: assert_unreached();
+ }
+ assert_equals(0,showCount);
+ assert_equals(0,hideCount);
+ assert_equals(0,afterShowCount);
+ assert_equals(0,afterHideCount);
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ assert_equals(1,showCount);
+ assert_equals(0,hideCount);
+ assert_equals(0,afterShowCount);
+ assert_equals(0,afterHideCount);
+ await waitForRender();
+ assert_equals(1,afterShowCount,'toggle show is fired asynchronously');
+ assert_equals(0,afterHideCount);
+ assert_true(popover.matches(':popover-open'));
+ popover.hidePopover();
+ assert_false(popover.matches(':popover-open'));
+ assert_equals(1,showCount);
+ assert_equals(1,hideCount);
+ assert_equals(1,afterShowCount);
+ assert_equals(0,afterHideCount);
+ await waitForRender();
+ assert_equals(1,afterShowCount);
+ assert_equals(1,afterHideCount,'toggle hide is fired asynchronously');
+ // No additional events
+ await waitForRender();
+ await waitForRender();
+ assert_false(popover.matches(':popover-open'));
+ assert_equals(1,showCount);
+ assert_equals(1,hideCount);
+ assert_equals(1,afterShowCount);
+ assert_equals(1,afterHideCount);
+ }, `The "beforetoggle" event (${method}) get properly dispatched for popovers`);
+ }
+
+ promise_test(async t => {
+ const {popover,signal} = getPopoverAndSignal(t);
+ let cancel = true;
+ popover.addEventListener('beforetoggle',(e) => {
+ if (e.newState !== "open")
+ return;
+ if (cancel)
+ e.preventDefault();
+ }, {signal});
+ assert_false(popover.matches(':popover-open'));
+ popover.showPopover();
+ assert_false(popover.matches(':popover-open'),'The "beforetoggle" event should be cancelable for the "opening" transition');
+ cancel = false;
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ popover.hidePopover();
+ assert_false(popover.matches(':popover-open'));
+ }, 'The "beforetoggle" event is cancelable for the "opening" transition');
+
+ promise_test(async t => {
+ const {popover,signal} = getPopoverAndSignal(t);
+ popover.addEventListener('beforetoggle',(e) => {
+ assert_not_equals(e.newState,"closed",'The "beforetoggle" event was fired for the closing transition');
+ }, {signal});
+ assert_false(popover.matches(':popover-open'));
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ t.add_cleanup(() => {document.body.appendChild(popover);});
+ popover.remove();
+ await waitForRender(); // Check for async events also
+ await waitForRender(); // Check for async events also
+ assert_false(popover.matches(':popover-open'));
+ }, 'The "beforetoggle" event is not fired for element removal');
+
+ promise_test(async t => {
+ const {popover,signal} = getPopoverAndSignal(t);
+ let events;
+ function resetEvents() {
+ events = {
+ singleShow: false,
+ singleHide: false,
+ coalescedShow: false,
+ coalescedHide: false,
+ };
+ }
+ function setEvent(type) {
+ assert_equals(events[type],false,'event repeated');
+ events[type] = true;
+ }
+ function assertOnly(type,msg) {
+ Object.keys(events).forEach(val => {
+ assert_equals(events[val],val===type,`${msg} (${val})`);
+ });
+ }
+ popover.addEventListener('toggle',(e) => {
+ switch (e.newState) {
+ case "open":
+ switch (e.oldState) {
+ case "open": setEvent('coalescedShow'); break;
+ case "closed": setEvent('singleShow'); break;
+ default: assert_unreached();
+ }
+ break;
+ case "closed":
+ switch (e.oldState) {
+ case "closed": setEvent('coalescedHide'); break;
+ case "open": setEvent('singleHide'); break;
+ default: assert_unreached();
+ }
+ break;
+ default: assert_unreached();
+ }
+ }, {signal});
+
+ resetEvents();
+ assertOnly('none');
+ assert_false(popover.matches(':popover-open'));
+ popover.showPopover();
+ await waitForRender();
+ assert_true(popover.matches(':popover-open'));
+ assertOnly('singleShow','Single event should have been fired, which is a "show"');
+
+ resetEvents();
+ popover.hidePopover();
+ popover.showPopover(); // Immediate re-show
+ await waitForRender();
+ assert_true(popover.matches(':popover-open'));
+ assertOnly('coalescedShow','Single coalesced event should have been fired, which is a "show"');
+
+ resetEvents();
+ popover.hidePopover();
+ await waitForRender();
+ assertOnly('singleHide','Single event should have been fired, which is a "hide"');
+ assert_false(popover.matches(':popover-open'));
+
+ resetEvents();
+ popover.showPopover();
+ popover.hidePopover(); // Immediate re-hide
+ await waitForRender();
+ assertOnly('coalescedHide','Single coalesced event should have been fired, which is a "hide"');
+ assert_false(popover.matches(':popover-open'));
+ }, 'The "toggle" event is coalesced');
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-focus-2.html b/testing/web-platform/tests/html/semantics/popovers/popover-focus-2.html
new file mode 100644
index 0000000000..892e5fd68f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-focus-2.html
@@ -0,0 +1,174 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover focus behaviors</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<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>
+<script src="resources/popover-utils.js"></script>
+
+<div id=fixup>
+ <button id=button1 tabindex="0">Button1</button>
+ <div popover id=popover0 tabindex="0" style="top:300px">
+ </div>
+ <div popover id=popover1 style="top:100px">
+ <button id=inside_popover1 tabindex="0">Inside1</button>
+ <button id=invoker2 popovertarget=popover2 tabindex="0">Nested Invoker 2</button>
+ <button id=inside_popover2 tabindex="0">Inside2</button>
+ </div>
+ <button id=button2 tabindex="0">Button2</button>
+ <div popover id=popover_no_invoker tabindex="0" style="top:300px"></div>
+ <button popovertarget=popover0 id=invoker0 tabindex="0">Invoker0</button>
+ <button popovertarget=popover1 id=invoker1 tabindex="0">Invoker1</button>
+ <button id=button3 tabindex="0">Button3</button>
+ <div popover id=popover2 style="top:200px">
+ <button id=inside_popover3 tabindex="0">Inside3</button>
+ <button id=invoker3 popovertarget=popover3 tabindex="0">Nested Invoker 3</button>
+ </div>
+ <div popover id=popover3 style="top:300px">
+ Non-focusable popover
+ </div>
+ <button id=button4 tabindex="0">Button4</button>
+</div>
+<style>
+ #fixup [popover] {
+ bottom:auto;
+ }
+</style>
+<script>
+async function verifyFocusOrder(order,description) {
+ order[0].focus();
+ for(let i=0;i<order.length;++i) {
+ const control = order[i];
+ assert_equals(document.activeElement,control,`${description}: Step ${i+1}`);
+ await sendTab();
+ }
+ for(let i=order.length-1;i>=0;--i) {
+ const control = order[i];
+ await sendShiftTab();
+ assert_equals(document.activeElement,control,`${description}: Step ${i+1} (backwards)`);
+ }
+}
+promise_test(async t => {
+ button1.focus();
+ assert_equals(document.activeElement,button1);
+ await sendTab();
+ assert_equals(document.activeElement,button2,'Hidden popover should be skipped');
+ await sendShiftTab();
+ assert_equals(document.activeElement,button1,'Hidden popover should be skipped backwards');
+ popover_no_invoker.showPopover();
+ await sendTab();
+ await sendTab();
+ assert_equals(document.activeElement,popover_no_invoker,"Focusable popover that is opened without an invoker should get focused");
+ await sendTab();
+ assert_equals(document.activeElement,invoker0);
+ await sendEnter(); // Activate the invoker0
+ assert_true(popover0.matches(':popover-open'), 'popover0 should be invoked by invoker0');
+ assert_equals(document.activeElement,invoker0,'Focus should not move when popover is shown');
+ await sendTab();
+ await sendEnter(); // Activate the invoker
+ assert_true(popover1.matches(':popover-open'), 'popover1 should be invoked by invoker1');
+ assert_equals(document.activeElement,invoker1,'Focus should not move when popover is shown');
+ await sendTab();
+ // Make invoker1 non-focusable.
+ invoker1.disabled = true;
+ assert_equals(document.activeElement,inside_popover1,'Focus should move from invoker into the open popover');
+ await sendTab();
+ assert_equals(document.activeElement,invoker2,'Focus should move within popover');
+ await sendShiftTab();
+ await sendShiftTab();
+ assert_equals(document.activeElement, button1 ,'Focus should not move back to invoker as it is non-focusable');
+ // Reset invoker1 to focusable.
+ invoker1.disabled = false;
+ await verifyFocusOrder([button1, button2, invoker0, invoker1, inside_popover1, invoker2, inside_popover2, button3, button4],'set 1');
+ invoker2.focus();
+ await sendEnter(); // Activate the nested invoker
+ assert_true(popover2.matches(':popover-open'), 'popover2 should be invoked by nested invoker');
+ assert_equals(document.activeElement,invoker2,'Focus should stay on the invoker');
+ await sendTab();
+ assert_equals(document.activeElement,inside_popover3,'Focus should move into nested popover');
+ await sendTab();
+ assert_equals(document.activeElement,invoker3);
+ await sendEnter(); // Activate the (empty) nested invoker
+ assert_true(popover3.matches(':popover-open'), 'popover3 should be invoked by nested invoker');
+ assert_equals(document.activeElement,invoker3,'Focus should stay on the invoker');
+ await sendTab();
+ assert_equals(document.activeElement,inside_popover2,'Focus should skip popover without focusable content, going back to higher scope');
+ await sendShiftTab();
+ assert_equals(document.activeElement,invoker3,'Shift-tab from the higher scope should return to the lower scope');
+ await sendTab();
+ assert_equals(document.activeElement,inside_popover2);
+ await sendTab();
+ assert_equals(document.activeElement,button3,'Focus should exit popovers');
+ await sendTab();
+ assert_equals(document.activeElement,button4,'Focus should skip popovers');
+ button1.focus();
+ await verifyFocusOrder([button1, button2, invoker0, invoker1, inside_popover1, invoker2, inside_popover3, invoker3, inside_popover2, button3, button4],'set 2');
+}, "Popover focus navigation");
+</script>
+
+<button id=circular0 popovertarget=popover4 tabindex="0">Invoker</button>
+<div id=popover4 popover>
+ <button id=circular1 autofocus popovertarget=popover4 popovertargetaction=hide tabindex="0"></button>
+ <button id=circular2 popovertarget=popover4 popovertargetaction=show tabindex="0"></button>
+ <button id=circular3 popovertarget=popover4 tabindex="0"></button>
+</div>
+<button id=circular4 tabindex="0">after</button>
+<script>
+promise_test(async t => {
+ circular0.focus();
+ await sendEnter(); // Activate the invoker
+ await verifyFocusOrder([circular0, circular1, circular2, circular3, circular4],'circular reference');
+ popover4.hidePopover();
+}, "Circular reference tab navigation");
+</script>
+
+<div id=focus-return1>
+ <button popovertarget=focus-return1-p popovertargetaction=show tabindex="0">Show popover</button>
+ <div popover id=focus-return1-p>
+ <button popovertarget=focus-return1-p popovertargetaction=hide autofocus tabindex="0">Hide popover</button>
+ </div>
+</div>
+<script>
+promise_test(async t => {
+ const invoker = document.querySelector('#focus-return1>button');
+ const popover = document.querySelector('#focus-return1>[popover]');
+ const hideButton = popover.querySelector('[popovertargetaction=hide]');
+ invoker.focus(); // Make sure button is focused.
+ assert_equals(document.activeElement,invoker);
+ await sendEnter(); // Activate the invoker
+ assert_true(popover.matches(':popover-open'), 'popover should be invoked by invoker');
+ assert_equals(document.activeElement,hideButton,'Hide button should be focused due to autofocus attribute');
+ await sendEnter(); // Activate the hide invoker
+ assert_false(popover.matches(':popover-open'), 'popover should be hidden by invoker');
+ assert_equals(document.activeElement,invoker,'Focus should be returned to the invoker');
+}, "Popover focus returns when popover is hidden by invoker");
+</script>
+
+<div id=focus-return2>
+ <button popovertarget=focus-return2-p tabindex="0">Toggle popover</button>
+ <div popover id=focus-return2-p>Popover with <button tabindex="0">focusable element</button></div>
+ <span tabindex=0>Other focusable element</span>
+</div>
+<script>
+promise_test(async t => {
+ const invoker = document.querySelector('#focus-return2>button');
+ const popover = document.querySelector('#focus-return2>[popover]');
+ const otherElement = document.querySelector('#focus-return2>span');
+ invoker.focus(); // Make sure button is focused.
+ assert_equals(document.activeElement,invoker);
+ invoker.click(); // Activate the invoker
+ assert_true(popover.matches(':popover-open'), 'popover should be invoked by invoker');
+ assert_equals(document.activeElement,invoker,'invoker should still be focused');
+ await sendTab();
+ assert_equals(document.activeElement,popover.querySelector('button'),'next up is the popover');
+ await sendTab();
+ assert_equals(document.activeElement,otherElement,'next focus stop is outside the popover');
+ await sendEscape(); // Close the popover via ESC
+ assert_false(popover.matches(':popover-open'), 'popover should be hidden');
+ assert_equals(document.activeElement,otherElement,'focus does not move because it was not inside the popover');
+}, "Popover focus only returns to invoker when focus is within the popover");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-focus-harness.html b/testing/web-platform/tests/html/semantics/popovers/popover-focus-harness.html
new file mode 100644
index 0000000000..a22a68ea2b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-focus-harness.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover utils - harness test</title>
+<link rel="author" href="mailto:masonf@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>
+<script src="resources/popover-utils.js"></script>
+
+<button id=button1 tabindex="0">Button1</button>
+<button id=button2 tabindex="0">Button2</button>
+<button id=button3 tabindex="0">Button3</button>
+
+<script>
+promise_test(async t => {
+ button1.focus();
+ assert_equals(document.activeElement,button1);
+ await sendTab();
+ assert_equals(document.activeElement,button2,'Tab should move to button 2');
+ await sendShiftTab();
+ assert_equals(document.activeElement,button1,'Shift-Tab should move back to button 1');
+}, "Test sendShiftTab");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-focus.html b/testing/web-platform/tests/html/semantics/popovers/popover-focus.html
new file mode 100644
index 0000000000..230492022c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-focus.html
@@ -0,0 +1,292 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover focus behaviors</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<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-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<div popover data-test='default behavior - popover is not focused' data-no-focus>
+ <p>This is a popover</p>
+ <button tabindex="0">first button</button>
+</div>
+
+<div popover data-test='autofocus popover' autofocus tabindex=-1 class=should-be-focused>
+ <p>This is a popover</p>
+</div>
+
+<div popover data-test='autofocus empty popover' autofocus tabindex=-1 class=should-be-focused></div>
+
+<div popover data-test='autofocus popover with button' autofocus tabindex=-1 class=should-be-focused>
+ <p>This is a popover</p>
+ <button tabindex="0">button</button>
+</div>
+
+<div popover data-test='autofocus child'>
+ <p>This is a popover</p>
+ <button autofocus class=should-be-focused tabindex="0">autofocus button</button>
+</div>
+
+<div popover data-test='autofocus on tabindex=0 element'>
+ <p autofocus tabindex=0 class=should-be-focused>This is a popover with autofocus on a tabindex=0 element</p>
+ <button tabindex="0">button</button>
+</div>
+
+<div popover data-test='autofocus multiple children'>
+ <p>This is a popover</p>
+ <button autofocus class=should-be-focused tabindex="0">autofocus button</button>
+ <button autofocus tabindex="0">second autofocus button</button>
+</div>
+
+<div popover autofocus tabindex=-1 data-test='autofocus popover and multiple autofocus children' class=should-be-focused>
+ <p>This is a popover</p>
+ <button autofocus tabindex="0">autofocus button</button>
+ <button autofocus tabindex="0">second autofocus button</button>
+</div>
+
+<dialog popover=auto data-test='Opening dialogs as popovers should use dialog initial focus algorithm.'>
+ <button class=should-be-focused>button</button>
+</dialog>
+
+<dialog popover=auto autofocus class=should-be-focused data-test='Opening dialogs as popovers which have autofocus should focus the dialog.'>
+ <button>button</button>
+</dialog>
+
+<style>
+ [popover] {
+ border: 2px solid black;
+ top:150px;
+ left:150px;
+ }
+ :focus-within { border: 5px dashed red; }
+ :focus { border: 5px solid lime; }
+</style>
+
+<script>
+ function addInvoker(t, popover) {
+ const button = document.createElement('button');
+ button.innerText = 'Click me';
+ const popoverId = 'popover-id';
+ assert_equals(document.querySelectorAll('#' + popoverId).length, 0);
+ document.body.appendChild(button);
+ t.add_cleanup(function() {
+ popover.removeAttribute('id');
+ button.remove();
+ });
+ popover.id = popoverId;
+ button.setAttribute('tabindex', '0');
+ button.setAttribute('popovertarget', popoverId);
+ return button;
+ }
+ function addPriorFocus(t) {
+ const priorFocus = document.createElement('button');
+ priorFocus.setAttribute("tabindex", "0");
+ priorFocus.id = 'priorFocus';
+ document.body.appendChild(priorFocus);
+ t.add_cleanup(() => priorFocus.remove());
+ return priorFocus;
+ }
+ function activateAndVerify(popover) {
+ const testName = popover.getAttribute('data-test');
+ promise_test(async t => {
+ const priorFocus = addPriorFocus(t);
+ let expectedFocusedElement = popover.matches('.should-be-focused') ? popover : popover.querySelector('.should-be-focused');
+ const changesFocus = !popover.hasAttribute('data-no-focus');
+ if (!changesFocus) {
+ expectedFocusedElement = priorFocus;
+ }
+ assert_true(!!expectedFocusedElement);
+ assert_false(popover.matches(':popover-open'));
+
+ // Directly show and hide the popover:
+ priorFocus.focus();
+ assert_equals(document.activeElement, priorFocus);
+ popover.showPopover();
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by popover.showPopover()`);
+ popover.hidePopover();
+ assert_equals(document.activeElement, priorFocus, 'prior element should get focus on hide, or if focus didn\'t shift on show, focus should stay where it was');
+ assert_false(isElementVisible(popover));
+
+ // Manual popover does not restore focus
+ popover.popover = 'manual';
+ priorFocus.focus();
+ assert_equals(document.activeElement, priorFocus);
+ popover.showPopover();
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by popover.showPopover()`);
+ popover.hidePopover();
+ if (!popover.hasAttribute('data-no-focus')) {
+ assert_not_equals(document.activeElement, priorFocus, 'prior element should *not* get focus when the popover is manual');
+ }
+ assert_false(isElementVisible(popover));
+ popover.popover = 'auto';
+
+ // Hit Escape:
+ priorFocus.focus();
+ assert_equals(document.activeElement, priorFocus);
+ popover.showPopover();
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by popover.showPopover()`);
+ await sendEscape();
+ assert_equals(document.activeElement, priorFocus, 'prior element should get focus after Escape');
+ assert_false(isElementVisible(popover));
+
+ // Move focus into the popover, then hit Escape:
+ let containedButton = popover.querySelector('button');
+ if (containedButton) {
+ priorFocus.focus();
+ assert_equals(document.activeElement, priorFocus);
+ popover.showPopover();
+ containedButton.focus();
+ assert_equals(document.activeElement, containedButton);
+ await sendEscape();
+ assert_equals(document.activeElement, priorFocus, 'prior element should get focus after Escape');
+ assert_false(isElementVisible(popover));
+ }
+
+ // Change the popover type:
+ priorFocus.focus();
+ popover.showPopover();
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by popover.showPopover()`);
+ assert_equals(popover.popover, 'auto', 'All popovers in this test should start as popover=auto');
+ popover.popover = 'manual';
+ assert_false(popover.matches(':popover-open'), 'Changing the popover type should hide the popover');
+ assert_equals(document.activeElement, priorFocus, 'prior element should get focus when the type is changed');
+ assert_false(isElementVisible(popover));
+ popover.popover = 'auto';
+
+ // Remove from the document:
+ priorFocus.focus();
+ popover.showPopover();
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by popover.showPopover()`);
+ popover.remove();
+ assert_false(isElementVisible(popover), 'Removing the popover should hide it immediately');
+ if (!popover.hasAttribute('data-no-focus')) {
+ assert_not_equals(document.activeElement, priorFocus, 'prior element should *not* get focus when the popover is removed from the document');
+ }
+ document.body.appendChild(popover);
+
+ // Show a modal dialog:
+ priorFocus.focus();
+ popover.showPopover();
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by popover.showPopover()`);
+ const dialog = document.body.appendChild(document.createElement('dialog'));
+ dialog.showModal();
+ assert_false(popover.matches(':popover-open'), 'Opening a modal dialog should hide the popover');
+ assert_not_equals(document.activeElement, priorFocus, 'prior element should *not* get focus when a modal dialog is shown');
+ assert_false(isElementVisible(popover));
+ dialog.close();
+ dialog.remove();
+
+ // Use an activating element:
+ const button = addInvoker(t, popover);
+ priorFocus.focus();
+ button.click();
+ assert_true(popover.matches(':popover-open'));
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by button.click()`);
+
+ // Make sure Escape works in the invoker case:
+ await sendEscape();
+ assert_equals(document.activeElement, priorFocus, 'prior element should get focus after Escape (via invoker)');
+ assert_false(isElementVisible(popover));
+
+ // Make sure we can directly focus the (already open) popover:
+ priorFocus.focus();
+ button.click();
+ assert_true(popover.matches(':popover-open'));
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by button.click()`);
+ popover.focus();
+ assert_equals(document.activeElement, popover.hasAttribute('tabindex') || popover.tagName === 'DIALOG' ? popover : expectedFocusedElement, `${testName} directly focus with popover.focus()`);
+ button.click(); // Button is set to toggle the popover
+ assert_false(popover.matches(':popover-open'));
+ assert_equals(document.activeElement, priorFocus, 'prior element should get focus on button-toggled hide');
+ assert_false(isElementVisible(popover));
+ }, "Popover focus test: " + testName);
+
+ promise_test(async t => {
+ const priorFocus = addPriorFocus(t);
+ assert_false(popover.matches(':popover-open'), 'popover should start out hidden');
+ let button = addInvoker(t, popover);
+ assert_equals(button.getAttribute('popovertarget'), popover.id, 'This test assumes the button uses `popovertarget`.');
+ assert_not_equals(button, priorFocus, 'Stranger things have happened');
+ assert_false(popover.contains(button), 'Start with a non-contained button');
+ priorFocus.focus();
+ assert_equals(document.activeElement, priorFocus);
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ await clickOn(button); // This will *not* light dismiss, but will "toggle" the popover.
+ assert_false(popover.matches(':popover-open'));
+ assert_equals(document.activeElement, button, 'focus should move to the button when clicked, and should stay there when the popover closes');
+ assert_false(isElementVisible(popover));
+
+ // Same thing, but the button is contained within the popover
+ button.setAttribute('popovertarget', popover.id);
+ button.setAttribute('popovertargetaction', 'hide');
+ popover.appendChild(button);
+ t.add_cleanup(() => button.remove());
+ priorFocus.focus();
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ const changesFocus = !popover.hasAttribute('data-no-focus');
+ if (changesFocus) {
+ assert_not_equals(document.activeElement, priorFocus, 'focus should shift for this element');
+ }
+ await clickOn(button);
+ assert_false(popover.matches(':popover-open'), 'clicking button should hide the popover');
+ assert_equals(document.activeElement, priorFocus, 'Contained button should return focus to the previously focused element');
+ assert_false(isElementVisible(popover));
+
+ // Same thing, but the button is unrelated (no popovertarget)
+ button = document.createElement('button');
+ button.setAttribute("tabindex", "0");
+ document.body.appendChild(button);
+ priorFocus.focus();
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ await clickOn(button); // This will light dismiss the popover, focus the prior focus, then focus this button.
+ assert_false(popover.matches(':popover-open'), 'clicking button should hide the popover (via light dismiss)');
+ assert_equals(document.activeElement, button, 'Focus should go to unrelated button on light dismiss');
+ assert_false(isElementVisible(popover));
+ }, "Popover button click focus test: " + testName);
+
+ promise_test(async t => {
+ if (popover.hasAttribute('data-no-focus')) {
+ // This test only applies if the popover changes focus
+ return;
+ }
+ const priorFocus = addPriorFocus(t);
+ assert_false(popover.matches(':popover-open'), 'popover should start out hidden');
+
+ // Move the prior focus out of the document
+ priorFocus.focus();
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ const newFocus = document.activeElement;
+ assert_not_equals(newFocus, priorFocus, 'focus should shift for this element');
+ priorFocus.remove();
+ assert_equals(document.activeElement, newFocus, 'focus should not change when prior focus is removed');
+ popover.hidePopover();
+ assert_not_equals(document.activeElement, priorFocus, 'focused element has been removed');
+ assert_false(isElementVisible(popover));
+ document.body.appendChild(priorFocus); // Put it back
+
+ // Move the prior focus inside the (already open) popover
+ priorFocus.focus();
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ assert_false(popover.contains(priorFocus), 'Start with a non-contained prior focus');
+ popover.appendChild(priorFocus); // Move inside the popover
+ assert_true(popover.contains(priorFocus));
+ assert_true(popover.matches(':popover-open'), 'popover should stay open');
+ popover.hidePopover();
+ assert_false(isElementVisible(popover));
+ assert_not_equals(document.activeElement, priorFocus, 'focused element is display:none inside the popover');
+ document.body.appendChild(priorFocus); // Put it back
+ }, "Popover corner cases test: " + testName);
+ }
+
+ document.querySelectorAll('body > [popover]').forEach(popover => activateAndVerify(popover));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-hidden-display-ref.html b/testing/web-platform/tests/html/semantics/popovers/popover-hidden-display-ref.html
new file mode 100644
index 0000000000..2dc0d558b6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-hidden-display-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:masonf@chromium.org">
+<link rel="stylesheet" href="resources/popover-styles.css">
+
+
+<div class=fake-popover>This content should be visible and green</div>
+<div class=fake-popover style="top:100px;">This content should be visible and green</div>
+<div class=fake-popover style="top:200px;">This content should be visible and green</div>
+
+<style>
+ .fake-popover {
+ top: 0;
+ margin:10px;
+ width: 300px;
+ height: 50px;
+ background: green;
+ }
+</style>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-hidden-display.html b/testing/web-platform/tests/html/semantics/popovers/popover-hidden-display.html
new file mode 100644
index 0000000000..db61802db6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-hidden-display.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=match href="popover-hidden-display-ref.html">
+<meta name=fuzzy content="0-1;0-15">
+
+<div class=nottoplayer popover >This content should be visible and green</div>
+<div class=nottoplayer popover=invalid style="top:100px;">This content should be visible and green</div>
+<div class=toplayer popover style="top:200px;">This content should be visible and green</div>
+
+<style>
+ [popover] {
+ display: block; /* This should make the popover visible */
+ top: 0;
+ margin:10px;
+ width: 300px;
+ height: 50px;
+ }
+ [popover].nottoplayer {
+ background: green;
+ }
+ [popover].toplayer {
+ background: red;
+ }
+ [popover].toplayer:popover-open {
+ background: green;
+ }
+ [popover].nottoplayer:popover-open {
+ background: red;
+ }
+</style>
+<script>
+ const toplayer = document.querySelectorAll('[popover].toplayer');
+ if (toplayer.length !== 1)
+ document.write('FAIL');
+ toplayer[0].showPopover();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-hint-crash.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-hint-crash.tentative.html
new file mode 100644
index 0000000000..82f83538e9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-hint-crash.tentative.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8" />
+<title>Popover=hint crash test</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<p>This test passes if it does not crash.</p>
+
+<div popover id=popover1>Popover 1
+ <div popover id=popover2 style="top:100px">Popover 2</div>
+</div>
+<div popover=manual id=popover3 style="top:200px">Popover 3</div>
+<div popover=hint id=popover4 anchor=popover3 style="inset:0;top:300px">Popover 4 - Click me</div>
+<script>
+popover1.showPopover();
+popover2.showPopover();
+popover3.showPopover();
+popover4.showPopover();
+clickOn(popover4)
+ .then(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-hide.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-hide.tentative.html
new file mode 100644
index 0000000000..57ca5723de
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-hide.tentative.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>The popover-hide-delay CSS property</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<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-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<body>
+<script src="resources/popover-hover-hide-common.js"></script>
+<script>
+
+// See popover-hover-hide-common.js for documentation.
+runHoverHideTestsForInvokerAction('hide');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-hover.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-hover.tentative.html
new file mode 100644
index 0000000000..d0036c0fe7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-hover.tentative.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>The popover-hide-delay CSS property</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<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-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<body>
+<script src="resources/popover-hover-hide-common.js"></script>
+<script>
+
+// See popover-hover-hide-common.js for documentation.
+runHoverHideTestsForInvokerAction('hover');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-show.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-show.tentative.html
new file mode 100644
index 0000000000..7b3fa2b302
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-show.tentative.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>The popover-hide-delay CSS property</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<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-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<body>
+<script src="resources/popover-hover-hide-common.js"></script>
+<script>
+
+// See popover-hover-hide-common.js for documentation.
+runHoverHideTestsForInvokerAction('show');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-toggle.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-toggle.tentative.html
new file mode 100644
index 0000000000..d6d4079e7e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-hover-hide-toggle.tentative.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>The popover-hide-delay CSS property</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<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-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<body>
+<script src="resources/popover-hover-hide-common.js"></script>
+<script>
+
+// See popover-hover-hide-common.js for documentation.
+runHoverHideTestsForInvokerAction('toggle');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-inside-display-none-ref.html b/testing/web-platform/tests/html/semantics/popovers/popover-inside-display-none-ref.html
new file mode 100644
index 0000000000..3d58e4ca09
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-inside-display-none-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:masonf@chromium.org">
+
+No popover should be displayed here.<p>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-inside-display-none.html b/testing/web-platform/tests/html/semantics/popovers/popover-inside-display-none.html
new file mode 100644
index 0000000000..24ce7c6fc6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-inside-display-none.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=match href="popover-inside-display-none-ref.html">
+
+No popover should be displayed here.<p>
+
+<div style="display:none">
+ <div popover>This content should be hidden</div>
+</div>
+
+<script>
+ const popover = document.querySelector('[popover]');
+ popover.showPopover();
+ if (!popover.matches(':popover-open'))
+ document.body.appendChild(document.createTextNode('FAIL'));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-invoker-reset.html b/testing/web-platform/tests/html/semantics/popovers/popover-invoker-reset.html
new file mode 100644
index 0000000000..bfc79fd629
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-invoker-reset.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://github.com/whatwg/html/issues/9152">
+<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>
+<script src="resources/popover-utils.js"></script>
+
+<div id=p1 popover>Popover 1
+ <button popovertarget=p2>Button</button>
+</div>
+<div id=p2 popover>Popover 2</div>
+
+<script>
+ test((t) => {
+ p1.showPopover();
+ assert_true(p1.matches(':popover-open'));
+ const invoker = p1.querySelector('button');
+ p2.addEventListener('beforetoggle',(e) => {
+ assert_equals(e.newState,'open');
+ e.preventDefault();
+ },{once:true});
+ invoker.click(); // Will be cancelled
+ assert_false(p2.matches(':popover-open'));
+ assert_true(p1.matches(':popover-open'));
+ p2.showPopover();
+ assert_true(p2.matches(':popover-open'));
+ assert_false(p1.matches(':popover-open'),'invoker was not used to show p2, so p1 should close');
+ },'Invoker gets reset appropriately');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-invoking-attribute-hint.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-invoking-attribute-hint.tentative.html
new file mode 100644
index 0000000000..b531ddc460
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-invoking-attribute-hint.tentative.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover invoking attribute</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<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-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+<script src="resources/popover-invoking-attribute.js"></script>
+
+<body>
+<script>
+runPopoverInvokerTests(["hint"]);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-invoking-attribute.html b/testing/web-platform/tests/html/semantics/popovers/popover-invoking-attribute.html
new file mode 100644
index 0000000000..8e312e90d7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-invoking-attribute.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover invoking attribute</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<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-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+<script src="resources/popover-invoking-attribute.js"></script>
+
+<body>
+<script>
+runPopoverInvokerTests(["auto","manual"]);
+</script>
+
+<button popovertarget=p1>Toggle Popover 1</button>
+<div popover id=p1 style="border: 5px solid red;top: 100px;left: 100px;">This is popover #1</div>
+
+<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();
+}
+
+const popover = document.querySelector('[popover]');
+const button = document.querySelector('button');
+let showCount = 0;
+let hideCount = 0;
+popover.addEventListener('beforetoggle',(e) => {
+ if (e.newState === "open")
+ ++showCount;
+ else
+ ++hideCount;
+ });
+
+async function assertState(expectOpen,expectShow,expectHide) {
+ assert_equals(popover.matches(':popover-open'),expectOpen,'Popover open state is incorrect');
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ assert_equals(showCount,expectShow,'Show count is incorrect');
+ assert_equals(hideCount,expectHide,'Hide count is incorrect');
+}
+
+window.addEventListener('load', () => {
+ promise_test(async () => {
+ showCount = hideCount = 0;
+ await assertState(false,0,0);
+ await clickOn(button);
+ await assertState(true,1,0);
+ popover.hidePopover();
+ await assertState(false,1,1);
+ button.click();
+ await assertState(true,2,1);
+ popover.hidePopover();
+ await assertState(false,2,2);
+ }, "Clicking a popovertarget button opens a closed popover (also check event counts)");
+
+ promise_test(async () => {
+ showCount = hideCount = 0;
+ await assertState(false,0,0);
+ await clickOn(button);
+ await assertState(true,1,0);
+ await clickOn(button);
+ await assertState(false,1,1);
+ }, "Clicking a popovertarget button closes an open popover (also check event counts)");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-flat-tree-nested.html b/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-flat-tree-nested.html
new file mode 100644
index 0000000000..ef3b35aea4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-flat-tree-nested.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test that popover light dismiss uses the flat tree when nested shadow roots.</title>
+ <link rel="author" title="Luke Warlow" href="mailto:luke@warlow.dev">
+</head>
+<body>
+ <p>Test passes if the inner popover opens after clicking the inner toggle.</p>
+ <button popovertarget="outerPopover" popovertargetaction="toggle" id="outerPopoverToggle">Toggle</button>
+ <div id="outerPopover" popover>
+ <template shadowrootmode="open">
+ Outer
+ <button id="innerPopoverToggle">Toggle</button>
+ <div id="innerContainer">
+ <template shadowrootmode="open">
+ <div id="innerPopover" popover>
+ Inner
+ </div>
+ </template>
+ </div>
+ </template>
+ </div>
+
+ <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>
+ <script src="resources/popover-utils.js"></script>
+ <script>
+ promise_test(async () => {
+ const innerPopoverToggle = outerPopover.shadowRoot.querySelector("#innerPopoverToggle");
+ const innerContainer = outerPopover.shadowRoot.querySelector('#innerContainer');
+ const innerPopover = innerContainer.shadowRoot.querySelector("#innerPopover");
+ innerPopoverToggle.onclick = () => {
+ innerPopover.togglePopover();
+ }
+
+ assert_false(outerPopover.matches(":popover-open"), "outer popover is initially hidden");
+ assert_false(innerPopover.matches(":popover-open"), "inner popover is initially hidden");
+
+ await clickOn(outerPopoverToggle);
+
+ assert_true(outerPopover.matches(":popover-open"), "outer popover is open after clicking the toggle");
+ assert_false(innerPopover.matches(":popover-open"), "inner popover is initially hidden");
+
+ await clickOn(innerPopoverToggle);
+
+ assert_true(outerPopover.matches(":popover-open"), "outer popover is not dismissed after clicking the second toggle");
+ assert_true(innerPopover.matches(":popover-open"), "inner popover is open after clicking the second toggle");
+ }, "Popover light dismiss uses the flat tree when nested shadow root");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-flat-tree.html b/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-flat-tree.html
new file mode 100644
index 0000000000..65008e28ef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-flat-tree.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test that popover light dismiss uses the flat tree.</title>
+ <link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+</head>
+<body>
+ <p>Test passes if the inner popover opens after clicking the inner toggle.</p>
+ <button popovertarget="outerPopover" popovertargetaction="toggle" id="outerPopoverToggle">Toggle</button>
+ <div id="outerPopover" popover>
+ <template shadowrootmode="open">
+ Outer
+ <button popovertarget="innerPopover" popovertargetaction="toggle" id="innerPopoverToggle">Toggle</button>
+ <div id="innerPopover" popover>
+ Inner
+ </div>
+ </template>
+ </div>
+ <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>
+ <script src="resources/popover-utils.js"></script>
+ <script>
+ promise_test(async () => {
+ const innerPopoverToggle = outerPopover.shadowRoot.querySelector("#innerPopoverToggle");
+ const innerPopover = outerPopover.shadowRoot.querySelector("#innerPopover");
+
+ assert_false(outerPopover.matches(":popover-open"), "outer popover is initially hidden");
+ assert_false(innerPopover.matches(":popover-open"), "inner popover is initially hidden");
+
+ await clickOn(outerPopoverToggle);
+
+ assert_true(outerPopover.matches(":popover-open"), "outer popover is open after clicking the toggle");
+ assert_false(innerPopover.matches(":popover-open"), "inner popover is initially hidden");
+
+ await clickOn(innerPopoverToggle);
+
+ assert_true(outerPopover.matches(":popover-open"), "outer popover is not dismissed after clicking the second toggle");
+ assert_true(innerPopover.matches(":popover-open"), "inner popover is open after clicking the second toggle");
+ }, "Popover light dismiss uses the flat tree");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-hint.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-hint.tentative.html
new file mode 100644
index 0000000000..f07363115f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-hint.tentative.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover light dismiss behavior for hints</title>
+<meta name="timeout" content="long">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover-hint.research.explainer">
+<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>
+<script src="resources/popover-utils.js"></script>
+
+<div id=outside></div>
+<div popover id=auto1>auto 1
+ <div popover id=auto2>auto 2
+ <div popover=hint id=innerhint1>inner hint 1
+ <div popover=hint id=innerhint2>inner hint 2
+ <div popover id=invalidauto1>Improperly nested auto 1</div>
+ </div>
+ </div>
+ </div>
+</div>
+<div popover=hint id=hint1>hint 1
+ <div popover=hint id=hint2>hint 2
+ <div popover id=invalidauto2>Improperly nested auto 2</div>
+ </div>
+</div>
+<div popover=manual id=manual1>Manual</div>
+
+<style>
+ [popover] {right:auto;bottom:auto;}
+ #auto1 {left:100px; top:100px;}
+ #auto2 {left:100px; top:200px;}
+ #innerhint1 {left:100px; top:300px;}
+ #innerhint2 {left:100px; top:400px;}
+ #invalidauto1 {left:100px; top:500px;}
+ #hint1 {left:200px; top:100px;}
+ #hint2 {left:200px; top:200px;}
+ #invalidauto1 {left:200px; top:400px;}
+ #manual1 {left:300px; top:100px;}
+ #outside {width:25px;height:25px}
+</style>
+
+<script>
+ const popovers = [
+ document.querySelector('#auto1'),
+ document.querySelector('#auto2'),
+ document.querySelector('#innerhint1'),
+ document.querySelector('#innerhint2'),
+ document.querySelector('#hint1'),
+ document.querySelector('#hint2'),
+ document.querySelector('#manual1'),
+ ];
+ function assertState(expectedState,description) {
+ description = description || 'Error';
+ const n = popovers.length;
+ assert_equals(expectedState.length,n,'Invalid');
+ for(let i=0;i<n;++i) {
+ assert_equals(popovers[i].matches(':popover-open'),expectedState[i],`${description}, index ${i} (${popovers[i].id})`);
+ }
+ }
+ function openall(t) {
+ // All popovers can be open at once, if shown in order:
+ popovers.forEach((p) => p.hidePopover());
+ popovers.forEach((p) => p.showPopover());
+ assertState(Array(popovers.length).fill(true),'All popovers should be able to be open at once');
+ t.add_cleanup(() => popovers.forEach((p) => p.hidePopover()));
+ }
+ function nvals(n,val) {
+ return new Array(n).fill(val);
+ }
+ for(let i=0;i<(popovers.length-1);++i) {
+ promise_test(async (t) => {
+ openall(t);
+ await clickOn(popovers[i]);
+ let expectedState = [...nvals(i+1,true),...nvals(popovers.length-i-2,false),true];
+ assertState(expectedState);
+ },`Mixed auto/hint light dismiss behavior, click on ${popovers[i].id}`);
+ }
+
+ promise_test(async (t) => {
+ openall(t);
+ await clickOn(outside);
+ assertState([false,false,false,false,false,false,true]);
+ },'Clicking outside closes all');
+
+ promise_test(async (t) => {
+ openall(t);
+ invalidauto1.showPopover();
+ assertState([true,true,false,false,false,false,true],'auto inside hint ignores the hints and gets nested within auto2');
+ assert_true(invalidauto1.matches(':popover-open'),'the inner nested auto should be open');
+ invalidauto1.hidePopover();
+ assertState([true,true,false,false,false,false,true]);
+ assert_false(invalidauto1.matches(':popover-open'));
+ },'Auto cannot be nested inside hint (invalidauto1)');
+
+ promise_test(async (t) => {
+ openall(t);
+ invalidauto2.showPopover();
+ assertState([false,false,false,false,false,false,true],'auto inside hint works as an independent (non-nested) auto');
+ assert_true(invalidauto2.matches(':popover-open'),'the inner nested auto should be open');
+ invalidauto2.hidePopover();
+ assertState([false,false,false,false,false,false,true]);
+ assert_false(invalidauto2.matches(':popover-open'));
+ },'Auto cannot be nested inside hint (invalidauto2)');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-on-scroll.html b/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-on-scroll.html
new file mode 100644
index 0000000000..382addadef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-on-scroll.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8" />
+<title>Popover should *not* light dismiss on scroll</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=help href="https://github.com/openui/open-ui/issues/240">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id=scroller>
+ Scroll me<br><br>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ ut labore et dolore magna aliqua. Enim ut sem viverra aliquet eget sit amet tellus. Massa
+ sed elementum tempus egestas sed sed risus pretium. Felis bibendum ut tristique et egestas
+ quis. Tortor dignissim convallis aenean et. Eu mi bibendum neque egestas congue quisque
+</div>
+
+<div popover id=popover1>
+ This is popover 1
+ <div popover id=popover2 anchor=anchor>
+ This is popover 2
+ </div>
+</div>
+<button onclick='popover1.showPopover();popover2.showPopover();'>Open popovers</button>
+
+<style>
+ #popover1 { top:50px; left: 50px; }
+ #popover2 { top:150px; left: 50px; }
+ #scroller {
+ height: 150px;
+ width: 150px;
+ overflow-y: scroll;
+ border: 1px solid black;
+ }
+</style>
+
+<script>
+ const popovers = document.querySelectorAll('[popover]');
+ function assertAll(showing) {
+ for(let popover of popovers) {
+ assert_equals(popover.matches(':popover-open'),showing);
+ }
+ }
+ async_test(t => {
+ for(let popover of popovers) {
+ popover.addEventListener('beforetoggle',e => {
+ if (e.newState !== "closed")
+ return;
+ assert_unreached('Scrolling should not light-dismiss a popover');
+ });
+ }
+ assertAll(/*showing*/false);
+ popovers[0].showPopover();
+ popovers[1].showPopover();
+ assertAll(/*showing*/true);
+ scroller.scrollTo(0, 100);
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ assertAll(/*showing*/true);
+ t.done();
+ });
+ });
+ },'Scrolling should not light-dismiss popovers');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-with-anchor.tentative.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-with-anchor.tentative.tentative.html
new file mode 100644
index 0000000000..c4e545c4fb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss-with-anchor.tentative.tentative.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover light dismiss with anchor behavior</title>
+<meta name="timeout" content="long">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<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>
+<script src="resources/popover-utils.js"></script>
+
+<button id=p1anchor tabindex="0">Popover1 anchor (no action)</button>
+<div popover id=p1 anchor=p1anchor>
+ <span id=inside1>Inside popover 1</span>
+ <button id=b2 popovertarget='p2' popovertargetaction=show>Popover 2</button>
+</div>
+<div popover id=p2 anchor=b2>
+ <span id=inside2>Inside popover 2</span>
+</div>
+<style>
+ #p1 {top: 50px;}
+ #p2 {top: 120px;}
+ [popover] {bottom:auto;}
+ [popover]::backdrop {
+ /* This should *not* affect anything: */
+ pointer-events: auto;
+ }
+</style>
+<script>
+ const popover1 = document.querySelector('#p1');
+ const popover1anchor = document.querySelector('#p1anchor');
+ const popover2 = document.querySelector('#p2');
+ const inside1 = document.querySelector('#inside1');
+ const inside2 = document.querySelector('#inside2');
+
+ let popover1HideCount = 0;
+ popover1.addEventListener('beforetoggle',(e) => {
+ if (e.newState !== "closed")
+ return;
+ ++popover1HideCount;
+ e.preventDefault(); // 'beforetoggle' should not be cancellable.
+ });
+ let popover2HideCount = 0;
+ popover2.addEventListener('beforetoggle',(e) => {
+ if (e.newState !== "closed")
+ return;
+ ++popover2HideCount;
+ e.preventDefault(); // 'beforetoggle' should not be cancellable.
+ });
+
+ promise_test(async () => {
+ popover1.showPopover();
+ popover2.showPopover();
+ await waitForRender();
+ p1HideCount = popover1HideCount;
+ let p2HideCount = popover2HideCount;
+ await clickOn(inside2);
+ assert_true(popover1.matches(':popover-open'),'popover1 should be open');
+ assert_true(popover2.matches(':popover-open'),'popover2 should be open');
+ assert_equals(popover1HideCount,p1HideCount,'popover1');
+ assert_equals(popover2HideCount,p2HideCount,'popover2');
+ popover1.hidePopover();
+ assert_false(popover1.matches(':popover-open'));
+ assert_false(popover2.matches(':popover-open'));
+ },'Clicking inside a child popover shouldn\'t close either popover');
+
+ promise_test(async () => {
+ popover1.showPopover();
+ popover2.showPopover();
+ await waitForRender();
+ p1HideCount = popover1HideCount;
+ p2HideCount = popover2HideCount;
+ await clickOn(inside1);
+ assert_true(popover1.matches(':popover-open'));
+ assert_equals(popover1HideCount,p1HideCount);
+ assert_false(popover2.matches(':popover-open'));
+ assert_equals(popover2HideCount,p2HideCount+1);
+ popover1.hidePopover();
+ },'Clicking inside a parent popover should close child popover');
+
+ promise_test(async () => {
+ popover1.showPopover();
+ assert_true(popover1.matches(':popover-open'));
+ await waitForRender();
+ await clickOn(popover1anchor);
+ assert_false(popover1.matches(':popover-open'),'popover1 should close');
+ },'Clicking on anchor element (that isn\'t an invoking element) shouldn\'t prevent its popover from being closed');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss.html b/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss.html
new file mode 100644
index 0000000000..916d52ef5e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-light-dismiss.html
@@ -0,0 +1,607 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover light dismiss behavior</title>
+<meta name="timeout" content="long">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<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>
+<script src="resources/popover-utils.js"></script>
+
+<button id=b1t popovertarget='p1'>Popover 1</button>
+<button id=b1s popovertarget='p1' popovertargetaction=show>Popover 1</button>
+<span id=outside>Outside all popovers</span>
+<div popover id=p1>
+ <span id=inside1>Inside popover 1</span>
+ <button id=b2 popovertarget='p2' popovertargetaction=show>Popover 2</button>
+ <span id=inside1after>Inside popover 1 after button</span>
+ <div popover id=p2>
+ <span id=inside2>Inside popover 2</span>
+ </div>
+</div>
+<button id=after_p1 tabindex="0">Next control after popover1</button>
+<style>
+ #p1 {top: 50px;}
+ #p2 {top: 120px;}
+ [popover] {bottom:auto;}
+ [popover]::backdrop {
+ /* This should *not* affect anything: */
+ pointer-events: auto;
+ }
+</style>
+<script>
+ const popover1 = document.querySelector('#p1');
+ const button1toggle = document.querySelector('#b1t');
+ const button1show = document.querySelector('#b1s');
+ const inside1After = document.querySelector('#inside1after');
+ const button2 = document.querySelector('#b2');
+ const popover2 = document.querySelector('#p2');
+ const outside = document.querySelector('#outside');
+ const inside1 = document.querySelector('#inside1');
+ const inside2 = document.querySelector('#inside2');
+ const afterp1 = document.querySelector('#after_p1');
+
+ let popover1HideCount = 0;
+ popover1.addEventListener('beforetoggle',(e) => {
+ if (e.newState !== "closed")
+ return;
+ ++popover1HideCount;
+ e.preventDefault(); // 'beforetoggle' should not be cancellable.
+ });
+ let popover2HideCount = 0;
+ popover2.addEventListener('beforetoggle',(e) => {
+ if (e.newState !== "closed")
+ return;
+ ++popover2HideCount;
+ e.preventDefault(); // 'beforetoggle' should not be cancellable.
+ });
+ promise_test(async () => {
+ assert_false(popover1.matches(':popover-open'));
+ popover1.showPopover();
+ assert_true(popover1.matches(':popover-open'));
+ let p1HideCount = popover1HideCount;
+ await clickOn(outside);
+ assert_false(popover1.matches(':popover-open'));
+ assert_equals(popover1HideCount,p1HideCount+1);
+ },'Clicking outside a popover will dismiss the popover');
+
+ promise_test(async (t) => {
+ const controller = new AbortController();
+ t.add_cleanup(() => controller.abort());
+ function addListener(eventName) {
+ document.addEventListener(eventName,(e) => e.preventDefault(),{signal:controller.signal,capture: true});
+ }
+ addListener('pointerdown');
+ addListener('pointerup');
+ addListener('mousedown');
+ addListener('mouseup');
+ assert_false(popover1.matches(':popover-open'));
+ popover1.showPopover();
+ assert_true(popover1.matches(':popover-open'));
+ let p1HideCount = popover1HideCount;
+ await clickOn(outside);
+ assert_false(popover1.matches(':popover-open'),'preventDefault should not prevent light dismiss');
+ assert_equals(popover1HideCount,p1HideCount+1);
+ },'Canceling pointer events should not keep clicks from light dismissing popovers');
+
+ promise_test(async () => {
+ assert_false(popover1.matches(':popover-open'));
+ popover1.showPopover();
+ await waitForRender();
+ p1HideCount = popover1HideCount;
+ await clickOn(inside1);
+ assert_true(popover1.matches(':popover-open'));
+ assert_equals(popover1HideCount,p1HideCount);
+ popover1.hidePopover();
+ },'Clicking inside a popover does not close that popover');
+
+ promise_test(async () => {
+ assert_false(popover1.matches(':popover-open'));
+ popover1.showPopover();
+ await waitForRender();
+ assert_true(popover1.matches(':popover-open'));
+ await new test_driver.Actions()
+ .pointerMove(0, 0, {origin: outside})
+ .pointerDown()
+ .send();
+ await waitForRender();
+ assert_true(popover1.matches(':popover-open'),'pointerdown (outside the popover) should not hide the popover');
+ await new test_driver.Actions()
+ .pointerUp()
+ .send();
+ await waitForRender();
+ assert_false(popover1.matches(':popover-open'),'pointerup (outside the popover) should trigger light dismiss');
+ },'Popovers close on pointerup, not pointerdown');
+
+ promise_test(async (t) => {
+ t.add_cleanup(() => popover1.hidePopover());
+ assert_false(popover1.matches(':popover-open'));
+ popover1.showPopover();
+ assert_true(popover1.matches(':popover-open'));
+ async function testOne(eventName) {
+ document.body.dispatchEvent(new PointerEvent(eventName));
+ document.body.dispatchEvent(new MouseEvent(eventName));
+ document.body.dispatchEvent(new ProgressEvent(eventName));
+ await waitForRender();
+ assert_true(popover1.matches(':popover-open'),`A synthetic "${eventName}" event should not hide the popover`);
+ }
+ await testOne('pointerup');
+ await testOne('pointerdown');
+ await testOne('mouseup');
+ await testOne('mousedown');
+ },'Synthetic events can\'t close popovers');
+
+ promise_test(async (t) => {
+ t.add_cleanup(() => popover1.hidePopover());
+ popover1.showPopover();
+ await clickOn(inside1After);
+ assert_true(popover1.matches(':popover-open'));
+ await sendTab();
+ assert_equals(document.activeElement,afterp1,'Focus should move to a button outside the popover');
+ assert_true(popover1.matches(':popover-open'));
+ },'Moving focus outside the popover should not dismiss the popover');
+
+ promise_test(async () => {
+ popover1.showPopover();
+ popover2.showPopover();
+ await waitForRender();
+ p1HideCount = popover1HideCount;
+ let p2HideCount = popover2HideCount;
+ await clickOn(inside2);
+ assert_true(popover1.matches(':popover-open'),'popover1 should be open');
+ assert_true(popover2.matches(':popover-open'),'popover2 should be open');
+ assert_equals(popover1HideCount,p1HideCount,'popover1');
+ assert_equals(popover2HideCount,p2HideCount,'popover2');
+ popover1.hidePopover();
+ assert_false(popover1.matches(':popover-open'));
+ assert_false(popover2.matches(':popover-open'));
+ },'Clicking inside a child popover shouldn\'t close either popover');
+
+ promise_test(async () => {
+ popover1.showPopover();
+ popover2.showPopover();
+ await waitForRender();
+ p1HideCount = popover1HideCount;
+ p2HideCount = popover2HideCount;
+ await clickOn(inside1);
+ assert_true(popover1.matches(':popover-open'));
+ assert_equals(popover1HideCount,p1HideCount);
+ assert_false(popover2.matches(':popover-open'));
+ assert_equals(popover2HideCount,p2HideCount+1);
+ popover1.hidePopover();
+ },'Clicking inside a parent popover should close child popover');
+
+ promise_test(async () => {
+ await clickOn(button1show);
+ assert_true(popover1.matches(':popover-open'));
+ await waitForRender();
+ p1HideCount = popover1HideCount;
+ await clickOn(button1show);
+ assert_true(popover1.matches(':popover-open'),'popover1 should stay open');
+ assert_equals(popover1HideCount,p1HideCount,'popover1 should not get hidden and reshown');
+ popover1.hidePopover(); // Cleanup
+ assert_false(popover1.matches(':popover-open'));
+ },'Clicking on invoking element, after using it for activation, shouldn\'t close its popover');
+
+ promise_test(async () => {
+ popover1.showPopover();
+ assert_true(popover1.matches(':popover-open'));
+ assert_false(popover2.matches(':popover-open'));
+ await clickOn(button2);
+ assert_true(popover2.matches(':popover-open'),'button2 should activate popover2');
+ p2HideCount = popover2HideCount;
+ await clickOn(button2);
+ assert_true(popover2.matches(':popover-open'),'popover2 should stay open');
+ assert_equals(popover2HideCount,p2HideCount,'popover2 should not get hidden and reshown');
+ popover1.hidePopover(); // Cleanup
+ assert_false(popover1.matches(':popover-open'));
+ assert_false(popover2.matches(':popover-open'));
+ },'Clicking on invoking element, after using it for activation, shouldn\'t close its popover (nested case)');
+
+ promise_test(async () => {
+ popover1.showPopover();
+ popover2.showPopover();
+ assert_true(popover1.matches(':popover-open'));
+ assert_true(popover2.matches(':popover-open'));
+ p2HideCount = popover2HideCount;
+ await clickOn(button2);
+ assert_true(popover2.matches(':popover-open'),'popover2 should stay open');
+ assert_equals(popover2HideCount,p2HideCount,'popover2 should not get hidden and reshown');
+ popover1.hidePopover(); // Cleanup
+ assert_false(popover1.matches(':popover-open'));
+ assert_false(popover2.matches(':popover-open'));
+ },'Clicking on invoking element, after using it for activation, shouldn\'t close its popover (nested case, not used for invocation)');
+
+ promise_test(async () => {
+ popover1.showPopover(); // Directly show the popover
+ assert_true(popover1.matches(':popover-open'));
+ await waitForRender();
+ p1HideCount = popover1HideCount;
+ await clickOn(button1show);
+ assert_true(popover1.matches(':popover-open'),'popover1 should stay open');
+ assert_equals(popover1HideCount,p1HideCount,'popover1 should not get hidden and reshown');
+ popover1.hidePopover(); // Cleanup
+ assert_false(popover1.matches(':popover-open'));
+ },'Clicking on invoking element, even if it wasn\'t used for activation, shouldn\'t close its popover');
+
+ promise_test(async () => {
+ popover1.showPopover(); // Directly show the popover
+ assert_true(popover1.matches(':popover-open'));
+ await waitForRender();
+ p1HideCount = popover1HideCount;
+ await clickOn(button1toggle);
+ assert_false(popover1.matches(':popover-open'),'popover1 should be hidden by popovertarget');
+ assert_equals(popover1HideCount,p1HideCount+1,'popover1 should get hidden only once by popovertarget');
+ },'Clicking on popovertarget element, even if it wasn\'t used for activation, should hide it exactly once');
+
+ promise_test(async () => {
+ popover1.showPopover();
+ popover2.showPopover(); // Popover1 is an ancestral element for popover2.
+ assert_true(popover1.matches(':popover-open'));
+ assert_true(popover2.matches(':popover-open'));
+ const drag_actions = new test_driver.Actions();
+ // Drag *from* popover2 *to* popover1 (its ancestor).
+ await drag_actions.pointerMove(0,0,{origin: popover2})
+ .pointerDown({button: drag_actions.ButtonType.LEFT})
+ .pointerMove(0,0,{origin: popover1})
+ .pointerUp({button: drag_actions.ButtonType.LEFT})
+ .send();
+ assert_true(popover1.matches(':popover-open'),'popover1 should be open');
+ assert_true(popover2.matches(':popover-open'),'popover1 should be open');
+ popover1.hidePopover();
+ assert_false(popover2.matches(':popover-open'));
+ },'Dragging from an open popover outside an open popover should leave the popover open');
+</script>
+
+<button id=b3 popovertarget=p3>Popover 3 - button 3
+ <div popover id=p4>Inside popover 4</div>
+</button>
+<div popover id=p3>Inside popover 3</div>
+<div popover id=p5>Inside popover 5
+ <button popovertarget=p3>Popover 3 - button 4 - unused</button>
+</div>
+<style>
+ #p3 {top:100px;}
+ #p4 {top:200px;}
+ #p5 {top:200px;}
+</style>
+<script>
+ const popover3 = document.querySelector('#p3');
+ const popover4 = document.querySelector('#p4');
+ const popover5 = document.querySelector('#p5');
+ const button3 = document.querySelector('#b3');
+ promise_test(async () => {
+ await clickOn(button3);
+ assert_true(popover3.matches(':popover-open'),'invoking element should open popover');
+ popover4.showPopover();
+ assert_true(popover4.matches(':popover-open'));
+ assert_false(popover3.matches(':popover-open'),'popover3 is unrelated to popover4');
+ popover4.hidePopover(); // Cleanup
+ assert_false(popover4.matches(':popover-open'));
+ },'A popover inside an invoking element doesn\'t participate in that invoker\'s ancestor chain');
+
+ promise_test(async () => {
+ popover5.showPopover();
+ assert_true(popover5.matches(':popover-open'));
+ assert_false(popover3.matches(':popover-open'));
+ popover3.showPopover();
+ assert_true(popover3.matches(':popover-open'));
+ assert_false(popover5.matches(':popover-open'),'Popover 5 was not invoked from popover3\'s invoker');
+ popover3.hidePopover();
+ assert_false(popover3.matches(':popover-open'));
+ },'An invoking element that was not used to invoke the popover is not part of the ancestor chain');
+</script>
+
+<div popover id=p6>Inside popover 6
+ <div style="height:2000px;background:lightgreen"></div>
+ Bottom of popover6
+</div>
+<button popovertarget=p6>Popover 6</button>
+<style>
+ #p6 {
+ width: 300px;
+ height: 300px;
+ overflow-y: scroll;
+ }
+</style>
+<script>
+ const popover6 = document.querySelector('#p6');
+ promise_test(async () => {
+ popover6.showPopover();
+ assert_equals(popover6.scrollTop,0,'popover6 should start non-scrolled');
+ await new test_driver.Actions()
+ .scroll(0, 0, 0, 50, {origin: popover6})
+ .send();
+ await waitForRender();
+ assert_true(popover6.matches(':popover-open'),'popover6 should stay open');
+ assert_equals(popover6.scrollTop,50,'popover6 should be scrolled');
+ popover6.hidePopover();
+ },'Scrolling within a popover should not close the popover');
+</script>
+
+<my-element id="myElement">
+ <template shadowrootmode="open">
+ <button id=b7 popovertarget=p7 popovertargetaction=show tabindex="0">Popover7</button>
+ <div popover id=p7 style="top: 100px;">
+ <p>Popover content.</p>
+ <input id="inside7" type="text" placeholder="some text">
+ </div>
+ </template>
+</my-element>
+<script>
+ const button7 = document.querySelector('#myElement').shadowRoot.querySelector('#b7');
+ const popover7 = document.querySelector('#myElement').shadowRoot.querySelector('#p7');
+ const inside7 = document.querySelector('#myElement').shadowRoot.querySelector('#inside7');
+ promise_test(async () => {
+ button7.click();
+ assert_true(popover7.matches(':popover-open'),'invoking element should open popover');
+ inside7.click();
+ assert_true(popover7.matches(':popover-open'));
+ popover7.hidePopover();
+ },'Clicking inside a shadow DOM popover does not close that popover');
+
+ promise_test(async () => {
+ button7.click();
+ inside7.click();
+ assert_true(popover7.matches(':popover-open'));
+ await clickOn(outside);
+ assert_false(popover7.matches(':popover-open'));
+ },'Clicking outside a shadow DOM popover should close that popover');
+</script>
+
+<div popover id=p8>
+ <button tabindex="0">Button</button>
+ <span id=inside8after>Inside popover 8 after button</span>
+</div>
+<button id=p8invoker popovertarget=p8 tabindex="0">Popover8 invoker (no action)</button>
+<script>
+ promise_test(async () => {
+ const popover8 = document.querySelector('#p8');
+ const inside8After = document.querySelector('#inside8after');
+ const popover8Invoker = document.querySelector('#p8invoker');
+ assert_false(popover8.matches(':popover-open'));
+ popover8.showPopover();
+ await clickOn(inside8After);
+ assert_true(popover8.matches(':popover-open'));
+ await sendTab();
+ assert_equals(document.activeElement,popover8Invoker,'Focus should move to the invoker element');
+ assert_true(popover8.matches(':popover-open'),'popover should stay open');
+ popover8.hidePopover(); // Cleanup
+ },'Moving focus back to the invoker element should not dismiss the popover');
+</script>
+
+<!-- Convoluted ancestor relationship -->
+<div popover id=convoluted_p1>Popover 1
+ <button popovertarget=convoluted_p2>Open Popover 2</button>
+<div popover id=convoluted_p2>Popover 2
+ <button popovertarget=convoluted_p3>Open Popover 3</button>
+ <button popovertarget=convoluted_p2 popovertargetaction=show>Self-linked invoker</button>
+ </div>
+ <div popover id=convoluted_p3>Popover 3
+ <button popovertarget=convoluted_p4>Open Popover 4</button>
+ </div>
+ <div popover id=convoluted_p4><p>Popover 4</p></div>
+</div>
+<button onclick="convoluted_p1.showPopover()" tabindex="0">Open convoluted popover</button>
+<style>
+ #convoluted_p1 {top:50px;}
+ #convoluted_p2 {top:150px;}
+ #convoluted_p3 {top:250px;}
+ #convoluted_p4 {top:350px;}
+</style>
+<script>
+const convPopover1 = document.querySelector('#convoluted_p1');
+const convPopover2 = document.querySelector('#convoluted_p2');
+const convPopover3 = document.querySelector('#convoluted_p3');
+const convPopover4 = document.querySelector('#convoluted_p4');
+promise_test(async () => {
+ convPopover1.showPopover(); // Programmatically open p1
+ assert_true(convPopover1.matches(':popover-open'));
+ convPopover1.querySelector('button').click(); // Click to invoke p2
+ assert_true(convPopover1.matches(':popover-open'));
+ assert_true(convPopover2.matches(':popover-open'));
+ convPopover2.querySelector('button').click(); // Click to invoke p3
+ assert_true(convPopover1.matches(':popover-open'));
+ assert_true(convPopover2.matches(':popover-open'));
+ assert_true(convPopover3.matches(':popover-open'));
+ convPopover3.querySelector('button').click(); // Click to invoke p4
+ assert_true(convPopover1.matches(':popover-open'));
+ assert_true(convPopover2.matches(':popover-open'));
+ assert_true(convPopover3.matches(':popover-open'));
+ assert_true(convPopover4.matches(':popover-open'));
+ convPopover4.firstElementChild.click(); // Click within p4
+ assert_true(convPopover1.matches(':popover-open'));
+ assert_true(convPopover2.matches(':popover-open'));
+ assert_true(convPopover3.matches(':popover-open'));
+ assert_true(convPopover4.matches(':popover-open'));
+ convPopover1.hidePopover();
+ assert_false(convPopover1.matches(':popover-open'));
+ assert_false(convPopover2.matches(':popover-open'));
+ assert_false(convPopover3.matches(':popover-open'));
+ assert_false(convPopover4.matches(':popover-open'));
+},'Ensure circular/convoluted ancestral relationships are functional');
+
+promise_test(async () => {
+ convPopover1.showPopover(); // Programmatically open p1
+ convPopover1.querySelector('button').click(); // Click to invoke p2
+ assert_true(convPopover1.matches(':popover-open'));
+ assert_true(convPopover2.matches(':popover-open'));
+ assert_false(convPopover3.matches(':popover-open'));
+ assert_false(convPopover4.matches(':popover-open'));
+ convPopover4.showPopover(); // Programmatically open p4
+ assert_true(convPopover1.matches(':popover-open'),'popover1 stays open because it is a DOM ancestor of popover4');
+ assert_false(convPopover2.matches(':popover-open'),'popover2 closes because it isn\'t connected to popover4 via active invokers');
+ assert_true(convPopover4.matches(':popover-open'));
+ convPopover4.firstElementChild.click(); // Click within p4
+ assert_true(convPopover1.matches(':popover-open'),'nothing changes');
+ assert_false(convPopover2.matches(':popover-open'));
+ assert_true(convPopover4.matches(':popover-open'));
+ convPopover1.hidePopover();
+ assert_false(convPopover1.matches(':popover-open'));
+ assert_false(convPopover2.matches(':popover-open'));
+ assert_false(convPopover3.matches(':popover-open'));
+ assert_false(convPopover4.matches(':popover-open'));
+},'Ensure circular/convoluted ancestral relationships are functional, with a direct showPopover()');
+</script>
+
+<div popover id=p13>Popover 1
+ <div popover id=p14>Popover 2
+ <div popover id=p15>Popover 3</div>
+ </div>
+</div>
+<style>
+ #p13 {top: 100px;}
+ #p14 {top: 200px;}
+ #p15 {top: 300px;}
+</style>
+<script>
+promise_test(async () => {
+ const p13 = document.querySelector('#p13');
+ const p14 = document.querySelector('#p14');
+ const p15 = document.querySelector('#p15');
+ p13.showPopover();
+ p14.showPopover();
+ p15.showPopover();
+ p15.addEventListener('beforetoggle', (e) => {
+ if (e.newState !== "closed")
+ return;
+ p14.hidePopover();
+ },{once:true});
+ assert_true(p13.matches(':popover-open') && p14.matches(':popover-open') && p15.matches(':popover-open'),'all three should be open');
+ p14.hidePopover();
+ assert_true(p13.matches(':popover-open'),'p13 should still be open');
+ assert_false(p14.matches(':popover-open'));
+ assert_false(p15.matches(':popover-open'));
+ p13.hidePopover(); // Cleanup
+},'Hide the target popover during "hide all popovers until"');
+</script>
+
+<div id=p16 popover>Popover 16
+ <div id=p17 popover>Popover 17</div>
+ <div id=p18 popover>Popover 18</div>
+</div>
+
+<script>
+promise_test(async () => {
+ p16.showPopover();
+ p18.showPopover();
+ let events = [];
+ const logEvents = (e) => {events.push(`${e.newState==='open' ? 'show' : 'hide'} ${e.target.id}`)};
+ p16.addEventListener('beforetoggle', logEvents);
+ p17.addEventListener('beforetoggle', logEvents);
+ p18.addEventListener('beforetoggle', (e) => {
+ logEvents(e);
+ p17.showPopover();
+ });
+ p16.hidePopover();
+ assert_array_equals(events,['hide p18','show p17','hide p16'],'There should not be a hide event for p17');
+ assert_false(p16.matches(':popover-open'));
+ assert_false(p17.matches(':popover-open'));
+ assert_false(p18.matches(':popover-open'));
+},'Show a sibling popover during "hide all popovers until"');
+</script>
+
+<div id=p19 popover>Popover 19</div>
+<div id=p20 popover>Popover 20</div>
+<button id=example2 tabindex="0">Example 2</button>
+
+<script>
+promise_test(async () => {
+ p19.showPopover();
+ let events = [];
+ const logEvents = (e) => {events.push(`${e.newState==='open' ? 'show' : 'hide'} ${e.target.id}`)};
+ p19.addEventListener('beforetoggle', (e) => {
+ logEvents(e);
+ p20.showPopover();
+ });
+ p20.addEventListener('beforetoggle', logEvents);
+ p19.hidePopover();
+ assert_array_equals(events,['hide p19','show p20'],'There should not be a second hide event for 19');
+ assert_false(p19.matches(':popover-open'));
+ assert_true(p20.matches(':popover-open'));
+ p20.hidePopover(); // Cleanup
+},'Show an unrelated popover during "hide popover"');
+</script>
+
+<div id=p21 popover>21
+ <div id=p22 popover>22</div>
+ <div id=p23 popover>23</div>
+ <div id=p24 popover>24</div>
+</div>
+
+<script>
+promise_test(async () => {
+ p21.showPopover();
+ p22.showPopover();
+ let events = [];
+ const logEvents = (e) => { events.push(`${e.newState === 'open' ? 'show' : 'hide'} ${e.target.id}`) };
+ p22.addEventListener('beforetoggle', (e) => {
+ logEvents(e);
+ p24.showPopover()
+ });
+ p23.addEventListener('beforetoggle', logEvents);
+ p24.addEventListener('beforetoggle', logEvents);
+ p23.showPopover();
+ assert_array_equals(events, ['show p23', 'hide p22', 'show p24'], 'hiding p24 does not fire event');
+ assert_false(p22.matches(':popover-open'));
+ assert_true(p23.matches(':popover-open'));
+ assert_false(p24.matches(':popover-open'));
+ p21.hidePopover(); // Cleanup
+},'Show other auto popover during "hide all popover until"');
+</script>
+
+<div id=p25 popover>
+ <div id=p26 popover>26</div>
+ <div id=p27 popover>27</div>
+ <div id=p28 popover>28</div>
+</div>
+<script>
+promise_test(async () => {
+ p25.showPopover();
+ p26.showPopover();
+ let events = [];
+ const logEvents = (e) => { events.push(`${e.newState === 'open' ? 'show' : 'hide'} ${e.target.id}`) };
+ p26.addEventListener('beforetoggle', (e) => {
+ logEvents(e);
+ p28.showPopover();
+ });
+ p27.addEventListener('beforetoggle', logEvents);
+ p28.addEventListener('beforetoggle', (e) => {
+ logEvents(e);
+ p27.showPopover();
+ });
+ p27.showPopover();
+ assert_array_equals(events, ['show p27', 'hide p26', 'show p28', 'show p27'], 'Nested showPopover should not fire event for its HideAllPopoversUntil');
+ assert_false(p26.matches(':popover-open'));
+ assert_true(p27.matches(':popover-open'));
+ assert_false(p28.matches(':popover-open'));
+ p25.hidePopover(); // Cleanup
+}, 'Nested showPopover');
+</script>
+
+<div id=p29 popover>Popover 29</div>
+<button id=b29 popovertarget=p29>Open popover 29</button>
+<iframe id=iframe29 width=100 height=100></iframe>
+<script>
+promise_test(async () => {
+ let iframe_url = (new URL("/common/blank.html", location.href)).href;
+ iframe29.src = iframe_url;
+ iframe29.contentDocument.body.style.height = '100%';
+ assert_false(p29.matches(':popover-open'));
+ p29.showPopover();
+ assert_true(p29.matches(':popover-open'));
+ let actions = new test_driver.Actions();
+ await actions.pointerMove(0,0,{origin: b29})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .send();
+
+ actions = new test_driver.Actions();
+ await actions.pointerMove(0,0,{origin: iframe29.contentDocument.body})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ assert_true(p29.matches(':popover-open'));
+},`Pointer down in one document and pointer up in another document shouldn't dismiss popover`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-manual-crash.html b/testing/web-platform/tests/html/semantics/popovers/popover-manual-crash.html
new file mode 100644
index 0000000000..535eb4c7d1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-manual-crash.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8" />
+<title>Popover=manual crash test</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<style>
+[popover] {top: 100px; bottom: auto;}
+[popover=""] {left: -200px}
+[popover=auto] {left: 0; }
+[popover=manual] {left: 200px; }
+</style>
+
+<p>This test passes if it does not crash.</p>
+<div popover>Auto1
+ <div popover=auto>Auto2</div>
+</div>
+<div popover=manual>Manual</div>
+<script>
+ document.querySelectorAll('[popover]').forEach(p => p.showPopover());
+ const manual = document.querySelector('[popover=manual]');
+ clickOn(manual)
+ .then(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-move-documents.html b/testing/web-platform/tests/html/semantics/popovers/popover-move-documents.html
new file mode 100644
index 0000000000..11f52c2f2f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-move-documents.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/9177">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+async function iframeLoaded(iframe) {
+ return new Promise(resolve => {
+ if (iframe.contentWindow.document.readyState == 'complete') {
+ resolve();
+ } else {
+ iframe.onload = resolve;
+ }
+ });
+}
+</script>
+
+<iframe id=myframe srcdoc="<p>iframe</p>"></iframe>
+<div id=p1 popover=auto>p1</div>
+<script>
+promise_test(async () => {
+ await iframeLoaded(myframe);
+ await new Promise(resolve => {
+ if (myframe.contentWindow.document.readyState == 'complete') {
+ resolve();
+ } else {
+
+ }
+ });
+ p1.addEventListener('beforetoggle', () => {
+ myframe.contentWindow.document.body.appendChild(p1);
+ });
+ assert_throws_dom('InvalidStateError', () => p1.showPopover());
+}, 'Moving popovers between documents while showing should throw an exception.');
+</script>
+
+<iframe id=myframe2 srcdoc="<p>iframe</p>"></iframe>
+<div id=p2 popover=auto>p2</div>
+<script>
+promise_test(async () => {
+ await iframeLoaded(myframe2);
+ const p2 = document.getElementById('p2');
+ p2.showPopover();
+ p2.addEventListener('beforetoggle', () => {
+ myframe2.contentWindow.document.body.appendChild(p2);
+ });
+ assert_true(p2.matches(':popover-open'),
+ 'The popover should be open after calling showPopover()');
+
+ p2.hidePopover();
+ assert_false(p2.matches(':popover-open'),
+ 'The popover should be closed after moving it between documents.');
+}, 'Moving popovers between documents while hiding should not throw an exception.');
+</script>
+
+<iframe id=myframe3 srcdoc="<p>iframe</p>"></iframe>
+<div id=p3 popover=auto>
+ p3
+ <div id=p4 popover=auto>p4</div>
+ <div id=p5 popover=auto>p5</div>
+</div>
+<script>
+promise_test(async () => {
+ await iframeLoaded(myframe3);
+ p3.showPopover();
+ p4.showPopover();
+ p4.addEventListener('beforetoggle', event => {
+ if (event.newState === 'closed') {
+ assert_true(p3.matches(':popover-open'),
+ 'p3 should be showing in the event handler.');
+ assert_true(p4.matches(':popover-open'),
+ 'p4 should be showing in the event handler.');
+ assert_equals(event.target, p4,
+ 'The events target should be p4.');
+ myframe3.contentWindow.document.body.appendChild(p5);
+ }
+ });
+ assert_true(p3.matches(':popover-open'),
+ 'p3 should be open after calling showPopover on it.');
+ assert_true(p4.matches(':popover-open'),
+ 'p4 should be open after calling showPopover on it.');
+
+ const p5 = document.getElementById('p5');
+ assert_throws_dom('InvalidStateError', () => p5.showPopover());
+ assert_false(p5.matches(':popover-open'),
+ 'p5 should be closed after moving it between documents.');
+}, 'Moving popovers between documents during light dismiss should throw an exception.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-not-keyboard-focusable.html b/testing/web-platform/tests/html/semantics/popovers/popover-not-keyboard-focusable.html
new file mode 100644
index 0000000000..55c70aa643
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-not-keyboard-focusable.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover keyboard focus behaviors</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<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>
+
+<button id=firstfocus tabindex="0">Button 1</button>
+<div popover>
+ <p>This is a popover without a focusable element</p>
+</div>
+<button id=secondfocus tabindex="0">Button 2</button>
+
+<script>
+promise_test(async () => {
+ const b1 = document.getElementById('firstfocus');
+ const b2 = document.getElementById('secondfocus');
+ const popover = document.querySelector('[popover]');
+ b1.focus();
+ assert_equals(document.activeElement,b1);
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ assert_equals(document.activeElement,b1);
+ // Tab once
+ await new test_driver.send_keys(document.body,'\uE004'); // Tab
+ assert_equals(document.activeElement, b2, 'Keyboard focus should skip the open popover');
+ assert_true(popover.matches(':popover-open'),'changing focus should not close the popover');
+ popover.hidePopover();
+
+ // Add a focusable button to the popover and make sure we can focus that
+ const button = document.createElement('button');
+ button.setAttribute("tabindex", "0");
+ popover.appendChild(button);
+ b1.focus();
+ popover.showPopover();
+ assert_equals(document.activeElement,b1);
+ // Tab once
+ await new test_driver.send_keys(document.body,'\uE004'); // Tab
+ assert_equals(document.activeElement, button, 'Keyboard focus should go to the contained button');
+ assert_true(popover.matches(':popover-open'),'changing focus to the popover should leave it showing');
+ popover.hidePopover();
+ assert_false(popover.matches(':popover-open'));
+}, "Popover should not be keyboard focusable");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-open-display-ref.html b/testing/web-platform/tests/html/semantics/popovers/popover-open-display-ref.html
new file mode 100644
index 0000000000..144b81e645
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-open-display-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:masonf@chromium.org">
+<link rel="stylesheet" href="resources/popover-styles.css">
+
+<div class=topmost></div>
+<div class=fake-popover>This is a popover</div>
+
+<style>
+ .topmost {
+ position:fixed;
+ top:0;
+ left:0;
+ width:1000px;
+ height:1000px;
+ background:green;
+ margin:0;
+ padding:0;
+ }
+</style>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-open-display.html b/testing/web-platform/tests/html/semantics/popovers/popover-open-display.html
new file mode 100644
index 0000000000..bc4d16fe80
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-open-display.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=match href="popover-open-display-ref.html">
+
+<div popover>This is a popover</div>
+<div class=topmost></div>
+
+<style>
+ .topmost {
+ position:fixed;
+ z-index: 999999;
+ top:0;
+ left:0;
+ width:1000px;
+ height:1000px;
+ background:green;
+ margin:0;
+ padding:0;
+ }
+</style>
+
+<script>
+ document.querySelector('[popover]').showPopover();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-open-in-beforetoggle.html b/testing/web-platform/tests/html/semantics/popovers/popover-open-in-beforetoggle.html
new file mode 100644
index 0000000000..1e22b73c68
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-open-in-beforetoggle.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover beforetoggle event opening new popovers</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/indices.html#event-beforetoggle">
+<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>
+<script src="resources/popover-utils.js"></script>
+
+<div popover id=p1>Popover 1
+ <div popover id=p2>Popover 2
+ <div popover id=p3>Popover 3</div>
+ </div>
+</div>
+<div id=outside>Outside</div>
+<dialog id=dialog>Dialog</dialog>
+
+<script>
+ function getSignal(t) {
+ const controller = new AbortController();
+ t.add_cleanup(() => controller.abort());
+ return controller.signal;
+ }
+
+ test((t) => {
+ p1.showPopover();
+ p1.addEventListener('beforetoggle',() => p2.showPopover(),{signal: getSignal(t)});
+ p1.hidePopover(); // Potential crash here
+ assert_false(p1.matches(':popover-open'));
+ assert_false(p2.matches(':popover-open'));
+ },'Open popover from closing beforetoggle event');
+
+ test((t) => {
+ p1.showPopover();
+ p1.addEventListener('beforetoggle',() => p2.showPopover(),{signal: getSignal(t)});
+ p2.addEventListener('beforetoggle',() => p3.showPopover(),{signal: getSignal(t)});
+ p1.hidePopover(); // Potential crash here
+ assert_false(p1.matches(':popover-open'));
+ assert_false(p2.matches(':popover-open'));
+ assert_false(p3.matches(':popover-open'));
+ },'Open double-nested popovers from closing beforetoggle event');
+
+ test(t => {
+ p1.showPopover();
+ p1.addEventListener('beforetoggle',() => p2.showPopover(),{signal: getSignal(t)});
+ p2.addEventListener('beforetoggle',() => p3.showPopover(),{signal: getSignal(t)});
+ dialog.showModal(); // Potential crash here
+ assert_false(p1.matches(':popover-open'));
+ assert_false(p2.matches(':popover-open'));
+ assert_false(p3.matches(':popover-open'));
+ dialog.close();
+ },'Open double-nested popovers from closing beforetoggle event, dialog open');
+
+ promise_test(async t => {
+ p1.showPopover();
+ p1.addEventListener('beforetoggle',() => p2.showPopover(),{signal: getSignal(t)});
+ p2.addEventListener('beforetoggle',() => p3.showPopover(),{signal: getSignal(t)});
+ await clickOn(outside); // Potential crash here
+ assert_false(p1.matches(':popover-open'));
+ assert_false(p2.matches(':popover-open'));
+ assert_false(p3.matches(':popover-open'));
+ },'Open double-nested popovers from closing beforetoggle event, light dismiss');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-open-overflow-display-2.html b/testing/web-platform/tests/html/semantics/popovers/popover-open-overflow-display-2.html
new file mode 100644
index 0000000000..f2388b7642
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-open-overflow-display-2.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/popover-utils.js"></script>
+<script>
+async function checkStatus(p) {
+ p.showPopover();
+ await waitForRender();
+ assert_true(p.matches(":popover-open"));
+ p.hidePopover();
+ await waitForRender();
+}
+</script>
+
+<div id=container style="overflow: hidden; position: absolute;">
+ <div popover="auto" id=p1 style="position: absolute; top: 100px;">Absolute popover inside absolute element</div>
+</div>
+<script>
+promise_test(async () => {
+ await checkStatus(document.querySelector("#p1"));
+}, "Absolute popover inside absolute element");
+</script>
+
+<div id=p2 popover="auto" style="overflow: hidden; position: absolute;">
+ <div style="position: absolute; top: 100px;">Absolute element inside absolute popover</div>
+</div>
+<script>
+promise_test(async () => {
+ await checkStatus(document.querySelector("#p2"));
+}, "Absolute element inside absolute popover");
+</script>
+
+<div id=container style="overflow: hidden; position: fixed;">
+ <div popover="auto" id=p3 style="position: fixed; top: 100px;">Fixed popover inside fixed element</div>
+</div>
+<script>
+promise_test(async () => {
+ await checkStatus(document.querySelector("#p3"));
+}, "Fixed popover inside fixed element");
+</script>
+
+<div id=p4 popover="auto" style="overflow: hidden; position: fixed;">
+ <div style="position: fixed; top: 100px;">Fixed element inside fixed popover</div>
+</div>
+<script>
+promise_test(async () => {
+ await checkStatus(document.querySelector("#p4"));
+}, "Fixed element inside fixed popover");
+</script>
+
+<div id=container style="overflow: hidden; position: fixed;">
+ <div popover="auto" id=p5 style="position: absolute; top: 100px;">Absolute popover inside fixed element</div>
+</div>
+<script>
+promise_test(async () => {
+ await checkStatus(document.querySelector("#p5"));
+}, "Absolute popover inside fixed element");
+</script>
+
+<div id=p6 popover="auto" style="overflow: hidden; position: absolute;">
+ <div style="position: fixed; top: 100px;">Fixed element inside absolute popover</div>
+</div>
+<script>
+promise_test(async () => {
+ await checkStatus(document.querySelector("#p6"));
+}, "Fixed element inside absolute popover");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-open-overflow-display-ref.html b/testing/web-platform/tests/html/semantics/popovers/popover-open-overflow-display-ref.html
new file mode 100644
index 0000000000..0d14050e85
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-open-overflow-display-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:masonf@chromium.org">
+
+<div popover id=p1>This is popover 1<div id=anchor2></div></div>
+<div popover id=p2 anchor=anchor2>This is popover 2<div id=anchor3></div></div>
+<div popover id=p3 anchor=anchor3>This is popover 3</div>
+
+<style>
+ #p2 {
+ top: 100px;
+ }
+ #p3 {
+ top:200px;
+ }
+</style>
+
+<script>
+ document.querySelector('#p1').showPopover();
+ document.querySelector('#p2').showPopover();
+ document.querySelector('#p3').showPopover();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-open-overflow-display.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-open-overflow-display.tentative.html
new file mode 100644
index 0000000000..3d4d833063
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-open-overflow-display.tentative.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=match href="popover-open-overflow-display-ref.html">
+
+<div id=container>
+ <div popover id=p1>This is popover 1<div id=anchor2></div></div>
+ <div popover id=p2 anchor=anchor2>This is popover 2<div id=anchor3></div></div>
+ <div popover id=p3 anchor=anchor3>This is popover 3</div>
+</div>
+
+<style>
+ #container {
+ overflow:hidden;
+ position: absolute;
+ top: 100px;
+ left: 50px;
+ width: 30px;
+ height: 30px;
+ }
+ #p2 {
+ position: absolute;
+ top: 100px;
+ }
+ #p3 {
+ position: relative;
+ top:200px;
+ }
+</style>
+
+<script>
+ document.querySelector('#p1').showPopover();
+ document.querySelector('#p2').showPopover();
+ document.querySelector('#p3').showPopover();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-overlay.html b/testing/web-platform/tests/html/semantics/popovers/popover-overlay.html
new file mode 100644
index 0000000000..a607844aee
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-overlay.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<title>popover: overlay</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/popover.html#the-popover-attribute">
+<link rel="help" href="https://drafts.csswg.org/css-position-4/#overlay">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<dialog popover id="popover-show-dialog"></dialog>
+<dialog popover id="popover-show-modal-dialog"></dialog>
+<dialog popover id="popover-dialog"></dialog>
+<div popover id="popover-div"></div>
+<script>
+ test(() => {
+ const popover_show_dialog = document.getElementById("popover-show-dialog");
+ assert_equals(getComputedStyle(popover_show_dialog).overlay, "none",
+ "Computed overlay");
+ popover_show_dialog.show();
+ assert_equals(getComputedStyle(popover_show_dialog).overlay, "none",
+ "Computed overlay after show()");
+ popover_show_dialog.close();
+ }, "dialog.show() should not put popover dialog in top layer");
+
+ test(() => {
+ const popover_show_modal_dialog = document.getElementById("popover-show-modal-dialog");
+ assert_equals(getComputedStyle(popover_show_modal_dialog).overlay, "none",
+ "Computed overlay");
+ popover_show_modal_dialog.showModal();
+ assert_equals(getComputedStyle(popover_show_modal_dialog).overlay, "auto",
+ "Computed overlay after showModal()");
+ popover_show_modal_dialog.close();
+ }, "dialog.showModal() should put popover dialog in top layer");
+
+ test(() => {
+ const popover_dialog = document.getElementById("popover-dialog");
+ assert_equals(getComputedStyle(popover_dialog).overlay, "none",
+ "Computed overlay");
+ popover_dialog.showPopover();
+ assert_equals(getComputedStyle(popover_dialog).overlay, "auto",
+ "Computed overlay after showPopover()");
+ popover_dialog.hidePopover();
+ }, "dialog.showPopover() should put popover dialog in top layer");
+
+ test(() => {
+ const popover_div = document.getElementById("popover-div");
+ assert_equals(getComputedStyle(popover_div).overlay, "none",
+ "Computed overlay");
+ popover_div.showPopover();
+ assert_equals(getComputedStyle(popover_div).overlay, "auto",
+ "Computed overlay after showPopover()");
+ popover_div.hidePopover();
+ }, "div.showPopover() should put popover div in top layer");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-removal-2.html b/testing/web-platform/tests/html/semantics/popovers/popover-removal-2.html
new file mode 100644
index 0000000000..b21c0bb557
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-removal-2.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover document removal behavior</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe id=frame1 srcdoc="<div popover id=popover>Popover</div>"></iframe>
+<iframe id=frame2></iframe>
+
+<script>
+ window.onload = () => {
+ test(t => {
+ const frame1Doc = document.getElementById('frame1').contentDocument;
+ const frame2Doc = document.getElementById('frame2').contentDocument;
+ const popover = frame1Doc.querySelector('[popover]');
+ assert_true(!!popover);
+ assert_false(popover.matches(':popover-open'));
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ frame2Doc.body.appendChild(popover);
+ assert_false(popover.matches(':popover-open'));
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ }, 'Moving popover between documents shouldn\'t cause issues');
+ };
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-removal.html b/testing/web-platform/tests/html/semantics/popovers/popover-removal.html
new file mode 100644
index 0000000000..d2b664b464
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-removal.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover document removal behavior</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div popover id=popover>Popover</div>
+
+<script>
+promise_test(async t => {
+ function loadCompleted() {
+ return new Promise(resolve => {
+ window.addEventListener('load', resolve);
+ });
+ }
+ const popover = document.querySelector('[popover]');
+ assert_false(popover.matches(':popover-open'));
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ popover.remove(); // Shouldn't cause any issues
+ document.body.click(); // Shouldn't cause light dismiss problems
+ await loadCompleted(); // The document should finish loading
+}, 'Removal from the document shouldn\'t cause issues');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-shadow-dom.html b/testing/web-platform/tests/html/semantics/popovers/popover-shadow-dom.html
new file mode 100644
index 0000000000..62aa135b56
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-shadow-dom.html
@@ -0,0 +1,202 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<script>
+ function ensureShadowDom(host) {
+ host.querySelectorAll('my-element').forEach(host => {
+ if (host.shadowRoot)
+ return; // Declarative Shadow DOM is enabled
+ const template = host.firstElementChild;
+ assert_true(template instanceof HTMLTemplateElement);
+ const shadow = host.attachShadow({mode: 'open'});
+ shadow.appendChild(template.content);
+ template.remove();
+ })
+ }
+ function findPopovers(root) {
+ let popovers = [];
+ if (!root)
+ return popovers;
+ if (root instanceof Element && root.matches('[popover]'))
+ popovers.push(root);
+ popovers.push(...findPopovers(root.shadowRoot));
+ root.childNodes.forEach(child => {
+ popovers.push(...findPopovers(child));
+ })
+ return popovers;
+ }
+ function getPopoverReferences(testId) {
+ const testRoot = document.querySelector(`#${testId}`);
+ assert_true(!!testRoot);
+ ensureShadowDom(testRoot);
+ return findPopovers(testRoot);
+ }
+ function showTestPopover(testId,popoverNum) {
+ getPopoverReferences(testId)[popoverNum].showPopover();
+ }
+</script>
+
+<div id=test1>
+ <button onclick='showTestPopover("test1",0)'>Test1 Popover</button>
+ <my-element>
+ <template shadowrootmode=open>
+ <div popover>
+ <p>This should show, even though it is inside shadow DOM.</p>
+ </div>
+ </template>
+ </my-element>
+</div>
+
+<script>
+ test(function() {
+ const popover = getPopoverReferences('test1')[0];
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ assert_true(isElementVisible(popover));
+ popover.hidePopover(); // Cleanup
+ }, "Popovers located inside shadow DOM can still be shown");
+</script>
+
+
+<div id=test2>
+ <button id=t2b1 onclick='showTestPopover("test2",0)'>Test 2 Popover 1</button>
+ <div popover anchor=t2b1 style="top: 200px;">
+ <p>Popover 1</p>
+ <button id=t2b2 onclick='showTestPopover("test2",1)'>Test 2 Popover 2</button>
+ </div>
+ <my-element>
+ <template shadowrootmode=open>
+ <div popover anchor=t2b2 style="top: 400px;">
+ <p>Hiding this popover will hide *all* open popovers,</p>
+ <p>because t2b2 doesn't exist in this context.</p>
+ </div>
+ </template>
+ </my-element>
+</div>
+
+<script>
+ test(function() {
+ const [popover1,popover2] = getPopoverReferences('test2');
+ popover1.showPopover();
+ assert_true(popover1.matches(':popover-open'));
+ assert_true(isElementVisible(popover1));
+ popover2.showPopover();
+ assert_false(popover1.matches(':popover-open'), 'popover1 open'); // P1 was closed by P2
+ assert_false(isElementVisible(popover1), 'popover1 visible');
+ assert_true(popover2.matches(':popover-open'), 'popover2 open'); // P2 is open
+ assert_true(isElementVisible(popover2), 'popover2 visible');
+ popover2.hidePopover(); // Cleanup
+ }, "anchor references do not cross shadow boundaries");
+</script>
+
+
+<div id=test3>
+ <my-element>
+ <template shadowrootmode=open>
+ <button id=t3b1 onclick='showTestPopover("test3",0)'>Test 3 Popover 1</button>
+ <div popover anchor=t3b1>
+ <p>This popover will stay open when popover2 shows.</p>
+ <slot></slot>
+ </div>
+ </template>
+ <button id=t3b2 onclick='showTestPopover("test3",1)'>Test 3 Popover 2</button>
+ </my-element>
+ <div popover anchor=t3b2>Popover 2</div>
+</div>
+
+<script>
+ promise_test(async function() {
+ const [popover1,popover2] = getPopoverReferences('test3');
+ popover1.showPopover();
+ assert_true(popover1.matches(':popover-open'));
+ assert_true(isElementVisible(popover1));
+ // Showing popover2 should not close popover1, since it is a flat
+ // tree ancestor of popover2's anchor button.
+ popover2.showPopover();
+ assert_true(popover2.matches(':popover-open'));
+ assert_true(isElementVisible(popover2));
+ assert_true(popover1.matches(':popover-open'));
+ assert_true(isElementVisible(popover1));
+ popover1.hidePopover();
+ await waitForRender();
+ assert_false(popover1.matches(':popover-open'));
+ assert_false(isElementVisible(popover1));
+ assert_false(popover2.matches(':popover-open'));
+ assert_false(isElementVisible(popover2));
+ }, "anchor references use the flat tree not the DOM tree");
+</script>
+
+
+<div id=test4>
+ <button id=t4b1 onclick='showTestPopover("test4",0)'>Test 4 Popover 1</button>
+ <div popover anchor=t4b1>
+ <p>This should not get hidden when popover2 opens.</p>
+ <my-element>
+ <template shadowrootmode=open>
+ <button id=t4b2 onclick='showTestPopover("test4",1)'>Test 4 Popover 2</button>
+ <div popover anchor=t4b2>
+ <p>This should not hide popover1.</p>
+ </div>
+ </template>
+ </my-element>
+ </div>
+</div>
+
+<script>
+ promise_test(async function() {
+ const [popover1,popover2] = getPopoverReferences('test4');
+ popover1.showPopover();
+ popover2.showPopover();
+ // Both 1 and 2 should be open at this point.
+ assert_true(popover1.matches(':popover-open'), 'popover1 not open');
+ assert_true(isElementVisible(popover1));
+ assert_true(popover2.matches(':popover-open'), 'popover2 not open');
+ assert_true(isElementVisible(popover2));
+ // This should hide both of them.
+ popover1.hidePopover();
+ await waitForRender();
+ assert_false(popover1.matches(':popover-open'));
+ assert_false(isElementVisible(popover1));
+ assert_false(popover2.matches(':popover-open'));
+ assert_false(isElementVisible(popover2));
+ }, "The popover stack is preserved across shadow-inclusive ancestors");
+</script>
+
+
+<div id=test5>
+ <template shadowrootmode=open>
+ <button popovertarget=p1>Test 5 Popover 1</button>
+ <div popover id=p1>Popover 1
+ <p>This should not get hidden when popover2 opens.</p>
+ <button popovertarget=p2>Click</button>
+ </div>
+ <div popover id=p2>Popover 2
+ <p>This should not hide popover1.</p>
+ </div>
+ </template>
+</div>
+<script>
+ promise_test(async function() {
+ const [popover1,popover2] = getPopoverReferences('test5');
+ popover1.showPopover();
+ popover1.querySelector('button').click(); // Use invoker to keep 2 visible
+ // Both 1 and 2 should be open at this point.
+ assert_true(popover1.matches(':popover-open'), 'popover1 not open');
+ assert_true(isElementVisible(popover1));
+ assert_true(popover2.matches(':popover-open'), 'popover2 not open');
+ assert_true(isElementVisible(popover2));
+ // This should hide both of them.
+ popover1.hidePopover();
+ await waitForRender();
+ assert_false(popover1.matches(':popover-open'));
+ assert_false(isElementVisible(popover1));
+ assert_false(popover2.matches(':popover-open'));
+ assert_false(isElementVisible(popover2));
+ }, "Popover ancestor relationships are within a root, not within the document");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-shadowhost-focus.html b/testing/web-platform/tests/html/semantics/popovers/popover-shadowhost-focus.html
new file mode 100644
index 0000000000..91ee547d4e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-shadowhost-focus.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/8994">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div popover=auto tabindex=0 data-test="autofocus=true, delegatesfocus=false" autofocus class=should-be-focused>
+ <template shadowrootmode=open>
+ <button autofocus>autofocus button</button>
+ </template>
+</div>
+
+<!-- The autofocus popover is what focus() gets called on, but since it has a
+ delegatesFocus shadowroot, focus() itself goes into the shadowroot. -->
+<div popover=auto tabindex=0 data-test="autofocus=true, delegatesfocus=true" autofocus>
+ <template shadowrootmode=open shadowrootdelegatesfocus>
+ <button autofocus class=should-be-focused>autofocus button</button>
+ </template>
+</div>
+
+<div popover=auto tabindex=0 data-test="autofocus=false, delegatesfocus=false">
+ <template shadowrootmode=open>
+ <button autofocus>autofocus button</button>
+ </template>
+</div>
+
+<div popover=auto tabindex=0 data-test="autofocus=false, delegatesfocus=true">
+ <template shadowrootmode=open shadowrootdelegatesfocus>
+ <button autofocus>autofocus button</button>
+ </template>
+</div>
+
+<script>
+document.querySelectorAll('body > [popover]').forEach(popover => {
+ promise_test(async () => {
+ const expectedFocusedElement = (popover.matches('.should-be-focused') ? popover : null)
+ || popover.querySelector('.should-be-focused')
+ || popover.shadowRoot.querySelector('.should-be-focused')
+ || document.body;
+
+ popover.showPopover();
+
+ let actualFocusedElement = document.activeElement;
+ if (actualFocusedElement.shadowRoot && actualFocusedElement.shadowRoot.activeElement) {
+ actualFocusedElement = actualFocusedElement.shadowRoot.activeElement;
+ }
+
+ popover.hidePopover();
+
+ // Resetting focus may happen asynchronously
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ assert_equals(actualFocusedElement, expectedFocusedElement);
+ }, popover.getAttribute('data-test'));
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-stacking-anchor-attribute.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-stacking-anchor-attribute.tentative.html
new file mode 100644
index 0000000000..6895b8625a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-stacking-anchor-attribute.tentative.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://www.w3.org/TR/css-anchor-position-1/#implicit">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- Once this test is made non-tentative, it can be folded back into popover-stacking.html -->
+
+<div class="example">
+ <p>anchor attribute relationship</p>
+ <div id=anchor1 popover class=ancestor><p>Ancestor popover</p></div>
+ <div anchor=anchor1 popover class=child><p>Child popover</p></div>
+</div>
+
+<div class="example">
+ <p>indirect anchor attribute relationship</p>
+ <div popover class=ancestor>
+ <p>Ancestor popover</p>
+ <div>
+ <div>
+ <span id=anchor2>Anchor</span>
+ </div>
+ </div>
+ </div>
+ <div anchor=anchor2 popover class=child><p>Child popover</p></div>
+</div>
+
+<!-- Other examples -->
+
+<div popover id=p1 anchor=b1><p>This is popover #1</p>
+ <button id=b2 onclick='p2.showPopover()'>Popover 2</button>
+ <button id=b4 onclick='p4.showPopover()'>Popover 4</button>
+</div>
+<div popover id=p2 anchor=b2><p>This is popover #2</p>
+ <button id=b3 onclick='p3.showPopover()'>Popover 3</button>
+</div>
+<div popover id=p3 anchor=b3><p>This is popover #3</p></div>
+<div popover id=p4 anchor=b4><p>This is popover #4</p></div>
+<button id=b1 onclick='p1.showPopover()'>Popover 1</button>
+
+<dialog id=d1>This is a dialog<button onclick='this.parentElement.close()'>Close</button></dialog>
+<button id=b5 onclick='d1.showPopover()'>Dialog</button>
+
+<script>
+ // Test basic ancestor relationships
+ for(let example of document.querySelectorAll('.example')) {
+ const descr = example.querySelector('p').textContent;
+ const ancestor = example.querySelector('[popover].ancestor');
+ const child = example.querySelector('[popover].child');
+ const clickToActivate = example.querySelector('.clickme');
+ test(function() {
+ assert_true(!!descr && !!ancestor && !!child);
+ assert_false(ancestor.matches(':popover-open'));
+ assert_false(child.matches(':popover-open'));
+ ancestor.showPopover();
+ if (clickToActivate)
+ clickToActivate.click();
+ else
+ child.showPopover();
+ assert_true(child.matches(':popover-open'));
+ assert_true(ancestor.matches(':popover-open'));
+ ancestor.hidePopover();
+ assert_false(ancestor.matches(':popover-open'));
+ assert_false(child.matches(':popover-open'));
+ },descr);
+ }
+
+ const popovers = [p1, p2, p3, p4];
+
+ function assertState(...states) {
+ assert_equals(popovers.length,states.length);
+ for(let i=0;i<popovers.length;++i) {
+ assert_equals(popovers[i].matches(':popover-open'),states[i],`Popover #${i+1} incorrect state`);
+ }
+ }
+ test(function() {
+ assertState(false,false,false,false);
+ p1.showPopover();
+ assertState(true,false,false,false);
+ p2.showPopover();
+ assertState(true,true,false,false);
+ p3.showPopover();
+ assertState(true,true,true,false);
+ // P4 is a sibling of P2, so showing it should
+ // close P2 and P3.
+ p4.showPopover();
+ assertState(true,false,false,true);
+ // P2 should close P4 now.
+ p2.showPopover();
+ assertState(true,true,false,false);
+ // Hiding P1 should hide all.
+ p1.hidePopover();
+ assertState(false,false,false,false);
+ }, "more complex nesting, all using anchor ancestry")
+</script>
+
+<style>
+ #p1 { top:350px; }
+ #p2 { top:350px; left:200px; }
+ #p3 { top:500px; }
+ #p4 { top:500px;left:200px; }
+</style>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-stacking-context-ref.html b/testing/web-platform/tests/html/semantics/popovers/popover-stacking-context-ref.html
new file mode 100644
index 0000000000..4d4ca6973f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-stacking-context-ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="stylesheet" href="resources/popover-styles.css">
+
+<div class="fake-popover">
+ Inside popover
+ <div class=z style="z-index: 2; background:lightgreen">z-index 2
+ <div class=z style="z-index: 3; background:lightblue; left: 20px;">z-index 3</div>
+ <div class=z style="z-index: 1; background:pink; top:-20px; left: 10px;">z-index 1</div>
+ </div>
+ <div class=z style="background:green; top:-100px; left: 250px; width: 100px;">Outside</div>
+ Bottom of popover
+</div>
+
+<style>
+ .fake-popover {
+ width: 200px;
+ height: 230px;
+ border: 1px solid red;
+ top:50px;
+ left:50px;
+ }
+ .z {
+ position: relative;
+ border: 1px solid black;
+ padding: 1em;
+ }
+</style>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-stacking-context.html b/testing/web-platform/tests/html/semantics/popovers/popover-stacking-context.html
new file mode 100644
index 0000000000..ba4e85a897
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-stacking-context.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=match href="popover-stacking-context-ref.html">
+
+<div popover>
+ Inside popover
+ <div class=z style="z-index: 2; background:lightgreen">z-index 2
+ <div class=z style="z-index: 3; background:lightblue; left: 20px;">z-index 3</div>
+ <div class=z style="z-index: 1; background:pink; top:-20px; left: 10px;">z-index 1</div>
+ </div>
+ <div class=z style="background:green; top:-100px; left: 250px; width: 100px;">Outside</div>
+ Bottom of popover
+</div>
+
+<style>
+ [popover] {
+ width: 200px;
+ height: 230px;
+ border: 1px solid red;
+ top:50px;
+ left:50px;
+ }
+ .z {
+ position: relative;
+ border: 1px solid black;
+ padding: 1em;
+ }
+</style>
+
+<script>
+ document.querySelector('[popover]').showPopover();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-stacking.html b/testing/web-platform/tests/html/semantics/popovers/popover-stacking.html
new file mode 100644
index 0000000000..1c352d566e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-stacking.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- Enumerate all the ways of creating an ancestor popover relationship -->
+
+<div class="example">
+ <p>Direct DOM children</p>
+ <div popover class=ancestor><p>Ancestor popover</p>
+ <div popover class=child><p>Child popover</p></div>
+ </div>
+</div>
+
+<div class="example">
+ <p>Grandchildren</p>
+ <div popover class=ancestor><p>Ancestor popover</p>
+ <div>
+ <div>
+ <div popover class=child><p>Child popover</p></div>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div class="example">
+ <p>popovertarget attribute relationship</p>
+ <div popover class=ancestor><p>Ancestor popover</p>
+ <button popovertarget=trigger1 class=clickme>Button</button>
+ </div>
+ <div id=trigger1 popover class=child><p>Child popover</p></div>
+</div>
+
+<div class="example">
+ <p>nested popovertarget attribute relationship</p>
+ <div popover class=ancestor><p>Ancestor popover</p>
+ <div>
+ <div>
+ <button popovertarget=trigger2 class=clickme>Button</button>
+ </div>
+ </div>
+ </div>
+ <div id=trigger2 popover class=child><p>Child popover</p></div>
+</div>
+
+<!-- Other examples -->
+
+<button id=b1 onclick='p1.showPopover()'>Popover 1</button>
+<div popover id=p1><p>This is popover #1</p>
+ <button id=b2 onclick='p2.showPopover()'>Popover 2</button>
+ <div popover id=p2><p>This is popover #2</p>
+ <button id=b3 onclick='p3.showPopover()'>Popover 3</button>
+ <div popover id=p3><p>This is popover #3</p></div>
+ </div>
+</div>
+
+<dialog id=d1>This is a dialog<button onclick='this.parentElement.close()'>Close</button></dialog>
+<button id=b5 onclick='d1.showPopover()'>Dialog</button>
+
+<script>
+ // Test basic ancestor relationships
+ for(let example of document.querySelectorAll('.example')) {
+ const descr = example.querySelector('p').textContent;
+ const ancestor = example.querySelector('[popover].ancestor');
+ const child = example.querySelector('[popover].child');
+ const clickToActivate = example.querySelector('.clickme');
+ test(function() {
+ assert_true(!!descr && !!ancestor && !!child);
+ assert_false(ancestor.matches(':popover-open'));
+ assert_false(child.matches(':popover-open'));
+ ancestor.showPopover();
+ if (clickToActivate)
+ clickToActivate.click();
+ else
+ child.showPopover();
+ assert_true(child.matches(':popover-open'));
+ assert_true(ancestor.matches(':popover-open'));
+ ancestor.hidePopover();
+ assert_false(ancestor.matches(':popover-open'));
+ assert_false(child.matches(':popover-open'));
+ },descr);
+ }
+
+ const popovers = [p1, p2, p3];
+
+ function assertState(...states) {
+ assert_equals(popovers.length,states.length);
+ for(let i=0;i<popovers.length;++i) {
+ assert_equals(popovers[i].matches(':popover-open'),states[i],`Popover #${i+1} incorrect state`);
+ }
+ }
+
+ test(function() {
+ function openManyPopovers() {
+ p1.showPopover();
+ p2.showPopover();
+ p3.showPopover();
+ assertState(true,true,true);
+ }
+ openManyPopovers();
+ d1.show(); // Dialog.show() should hide all popovers.
+ assertState(false,false,false);
+ d1.close();
+ openManyPopovers();
+ d1.showModal(); // Dialog.showModal() should also hide all popovers.
+ assertState(false,false,false);
+ d1.close();
+ }, "popovers should be closed by dialogs")
+
+ test(function() {
+ // Note: d1 is a <dialog> element, not a popover.
+ assert_false(d1.open);
+ d1.show();
+ assert_true(d1.open);
+ p1.showPopover();
+ assertState(true,false,false);
+ assert_true(d1.open);
+ p1.hidePopover();
+ assert_true(d1.open);
+ d1.close();
+ assert_false(d1.open);
+ }, "dialogs should not be closed by popovers")
+</script>
+
+<style>
+ #p1 { top:350px; }
+ #p2 { top:350px; left:200px; }
+ #p3 { top:500px; }
+</style>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-target-action-hover.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-target-action-hover.tentative.html
new file mode 100644
index 0000000000..b03ec78ebf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-target-action-hover.tentative.html
@@ -0,0 +1,180 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>The popovertargetaction=hover behavior</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<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-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<body>
+<style>
+.unrelated {top:0;}
+.invoker {top:100px; width:fit-content; height:fit-content;}
+[popover] {top: 200px;}
+.offset-child {top:300px; left:300px;}
+</style>
+
+<script>
+const popoverShowDelay = 100; // The CSS delay setting.
+const hoverWaitTime = 200; // How long to wait to cover the delay for sure.
+async function makePopoverAndInvoker(test, popoverType, invokerType, delayMs) {
+ delayMs = delayMs || popoverShowDelay;
+ const popover = Object.assign(document.createElement('div'),{popover: popoverType});
+ document.body.appendChild(popover);
+ popover.textContent = 'Popover';
+ // Set popover-show-delay on the popover to 0 - it should be ignored.
+ popover.setAttribute('style',`popover-show-delay: 0; popover-hide-delay: 1000s;`);
+ let invoker = document.createElement('button');
+ invoker.setAttribute('class','invoker');
+ invoker.popoverTargetElement = popover;
+ invoker.popoverTargetAction = "hover";
+ // Set popover-hide-delay on the invoker to 0 - it should be ignored.
+ invoker.setAttribute('style',`popover-show-delay: ${delayMs}ms; popover-hide-delay: 0;`);
+ document.body.appendChild(invoker);
+ const actualHoverDelay = Number(getComputedStyle(invoker)['popoverShowDelay'].slice(0,-1))*1000;
+ assert_equals(actualHoverDelay,delayMs,'popover-show-delay is incorrect');
+ const originalInvoker = invoker;
+ const reassignPopoverFn = (p) => {originalInvoker.popoverTargetElement = p};
+ switch (invokerType) {
+ case 'plain':
+ // Invoker is just a button.
+ invoker.textContent = 'Invoker';
+ break;
+ case 'nested':
+ // Invoker is just a button containing a div.
+ const child1 = invoker.appendChild(document.createElement('div'));
+ child1.textContent = 'Invoker';
+ break;
+ case 'nested-offset':
+ // Invoker is a child of the invoking button, and is not contained within
+ // the bounds of the popovertarget element.
+ invoker.textContent = 'Invoker';
+ // Reassign invoker to the child:
+ invoker = invoker.appendChild(document.createElement('div'));
+ invoker.textContent = 'Invoker child';
+ invoker.setAttribute('class','offset-child');
+ break;
+ case 'none':
+ // No invoker.
+ invoker.remove();
+ break;
+ default:
+ assert_unreached(`Invalid invokerType ${invokerType}`);
+ }
+ const unrelated = document.createElement('div');
+ document.body.appendChild(unrelated);
+ unrelated.textContent = 'Unrelated';
+ unrelated.setAttribute('class','unrelated');
+ test.add_cleanup(async () => {
+ popover.remove();
+ invoker.remove();
+ originalInvoker.remove();
+ unrelated.remove();
+ await waitForRender();
+ });
+ await mouseOver(unrelated); // Start by mousing over the unrelated element
+ await waitForRender();
+ return {popover,invoker,reassignPopoverFn};
+}
+
+// NOTE about testing methodology:
+// This test checks whether popovers are triggered *after* the appropriate hover
+// delay. The delay used for testing is kept low, to avoid this test taking too
+// long, but that means that sometimes on a slow bot/client, the hover delay can
+// elapse before we are able to check the popover status. And that can make this
+// test flaky. To avoid that, the msSinceMouseOver() function is used to check
+// that not-too-much time has passed, and if it has, the test is simply skipped.
+
+["auto","hint","manual"].forEach(type => {
+ ["plain","nested","nested-offset"].forEach(invokerType => {
+ promise_test(async (t) => {
+ const {popover,invoker} = await makePopoverAndInvoker(t,type,invokerType);
+ assert_false(popover.matches(':popover-open'));
+ await mouseOver(invoker);
+ let showing = popover.matches(':popover-open');
+ // See NOTE above.
+ if (msSinceMouseOver() < popoverShowDelay)
+ assert_false(showing,'popover should not show immediately');
+ await waitForHoverTime(hoverWaitTime);
+ assert_true(msSinceMouseOver() >= hoverWaitTime,'waitForHoverTime should wait the specified time');
+ assert_true(popover.matches(':popover-open'),'popover should show after delay');
+ assert_true(hoverWaitTime > popoverShowDelay,'popoverShowDelay is the CSS setting, hoverWaitTime should be longer than that');
+ popover.hidePopover(); // Cleanup
+ },`popovertargetaction=hover shows a popover with popover=${type}, invokerType=${invokerType}`);
+
+ promise_test(async (t) => {
+ const {popover,invoker} = await makePopoverAndInvoker(t,type,invokerType);
+ assert_false(popover.matches(':popover-open'));
+ invoker.click(); // Click the invoker
+ assert_true(popover.matches(':popover-open'),'Clicking the invoker should show the popover, even when popovertargetaction=hover');
+ popover.hidePopover(); // Cleanup
+ },`popovertargetaction=hover should also allow click activation, for popover=${type}, invokerType=${invokerType}`);
+
+ promise_test(async (t) => {
+ const longerHoverDelay = hoverWaitTime*2;
+ const {popover,invoker} = await makePopoverAndInvoker(t,type,invokerType,longerHoverDelay);
+ await mouseOver(invoker);
+ let showing = popover.matches(':popover-open');
+ // See NOTE above.
+ if (msSinceMouseOver() >= longerHoverDelay)
+ return; // The WPT runner was too slow.
+ assert_false(showing,'popover should not show immediately');
+ await waitForHoverTime(hoverWaitTime);
+ showing = popover.matches(':popover-open');
+ if (msSinceMouseOver() >= longerHoverDelay)
+ return; // The WPT runner was too slow.
+ assert_false(showing,'popover should not show after not long enough of a delay');
+ },`popovertargetaction=hover popover-show-delay is respected (popover=${type}, invokerType=${invokerType})`);
+
+ promise_test(async (t) => {
+ const {popover,invoker} = await makePopoverAndInvoker(t,type,invokerType);
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+ await mouseOver(invoker);
+ assert_true(popover.matches(':popover-open'),'popover should stay showing on mouseover');
+ await waitForHoverTime(hoverWaitTime);
+ assert_true(popover.matches(':popover-open'),'popover should stay showing after delay');
+ popover.hidePopover(); // Cleanup
+ },`popovertargetaction=hover does nothing when popover is already showing (popover=${type}, invokerType=${invokerType})`);
+
+ promise_test(async (t) => {
+ const {popover,invoker} = await makePopoverAndInvoker(t,type,invokerType);
+ await mouseOver(invoker);
+ let showing = popover.matches(':popover-open');
+ popover.remove();
+ // See NOTE above.
+ if (msSinceMouseOver() >= popoverShowDelay)
+ return; // The WPT runner was too slow.
+ assert_false(showing,'popover should not show immediately');
+ await waitForHoverTime(hoverWaitTime);
+ assert_false(popover.matches(':popover-open'),'popover should not show even after a delay');
+ // Now put it back in the document and make sure it doesn't trigger.
+ document.body.appendChild(popover);
+ await waitForHoverTime(hoverWaitTime);
+ assert_false(popover.matches(':popover-open'),'popover should not show even when returned to the document');
+ },`popovertargetaction=hover does nothing when popover is moved out of the document (popover=${type}, invokerType=${invokerType})`);
+
+ promise_test(async (t) => {
+ const {popover,invoker,reassignPopoverFn} = await makePopoverAndInvoker(t,type,invokerType);
+ const popover2 = Object.assign(document.createElement('div'),{popover: type});
+ document.body.appendChild(popover2);
+ t.add_cleanup(() => popover2.remove());
+ await mouseOver(invoker);
+ let eitherShowing = popover.matches(':popover-open') || popover2.matches(':popover-open');
+ reassignPopoverFn(popover2);
+ // See NOTE above.
+ if (msSinceMouseOver() >= popoverShowDelay)
+ return; // The WPT runner was too slow.
+ assert_false(eitherShowing,'popover should not show immediately');
+ await waitForHoverTime(hoverWaitTime);
+ assert_false(popover.matches(':popover-open'),'popover #1 should not show since popovertarget was reassigned');
+ assert_false(popover2.matches(':popover-open'),'popover #2 should not show since popovertarget was reassigned');
+ },`popovertargetaction=hover does nothing when target changes (popover=${type}, invokerType=${invokerType})`);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-target-element-disabled.html b/testing/web-platform/tests/html/semantics/popovers/popover-target-element-disabled.html
new file mode 100644
index 0000000000..d5c951768c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-target-element-disabled.html
@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/pull/8221#discussion_r1049379113">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id=outerpopover popover=auto>
+ <button popovertarget=innerpopover disabled>toggle popover</button>
+</div>
+<div id=innerpopover popover=auto>popover</div>
+<script>
+test(() => {
+ outerpopover.showPopover();
+ outerpopover.querySelector('button').click(); // Invoke innerpopover
+ assert_false(innerpopover.matches(':popover-open'),
+ 'disabled button shouldn\'t open the target popover');
+ assert_true(outerpopover.matches(':popover-open'));
+ innerpopover.showPopover();
+ assert_true(innerpopover.matches(':popover-open'),
+ 'The inner popover should be able to open successfully.');
+ assert_false(outerpopover.matches(':popover-open'),
+ 'The outer popover should be closed by opening the inner one.');
+}, 'Disabled popover*target buttons should not affect the popover heirarchy.');
+</script>
+
+<div id=outerpopover2 popover=auto>
+ <button id=togglebutton2 popovertarget=innerpopover2>toggle popover</button>
+</div>
+<div id=innerpopover2 popover=auto>popover</div>
+<script>
+test(() => {
+ outerpopover2.showPopover();
+ outerpopover2.querySelector('button').click(); // Invoke innerpopover
+ assert_true(innerpopover2.matches(':popover-open'),
+ 'The inner popover should be able to open successfully.');
+ assert_true(outerpopover2.matches(':popover-open'),
+ 'The outer popover should stay open when opening the inner one.');
+
+ togglebutton2.disabled = true;
+ assert_true(innerpopover2.matches(':popover-open'),
+ 'Changing disabled states after popovers are open shouldn\'t close anything');
+ assert_true(outerpopover2.matches(':popover-open'),
+ 'Changing disabled states after popovers are open shouldn\'t close anything');
+}, 'Disabling popover*target buttons when popovers are open should not cause popovers to be closed.');
+</script>
+
+<div id=outerpopover4 popover=auto>
+ <button id=togglebutton4 popovertarget=innerpopover4>toggle popover</button>
+</div>
+<div id=innerpopover4 popover=auto>popover</div>
+<form id=submitform>form</form>
+<script>
+test(() => {
+ outerpopover4.showPopover();
+ outerpopover4.querySelector('button').click(); // Invoke innerpopover
+ assert_true(innerpopover4.matches(':popover-open'),
+ 'The inner popover should be able to open successfully.');
+ assert_true(outerpopover4.matches(':popover-open'),
+ 'The outer popover should stay open when opening the inner one.');
+
+ togglebutton4.setAttribute('form', 'submitform');
+ assert_true(innerpopover4.matches(':popover-open'),
+ 'The inner popover be should be not closed when invoking buttons cease to be invokers.');
+ assert_true(outerpopover4.matches(':popover-open'),
+ 'The outer popover be should be not closed when invoking buttons cease to be invokers.');
+}, 'Setting the form attribute on popover*target buttons when popovers are open should not close them.');
+</script>
+
+<div id=outerpopover5 popover=auto>
+ <input type=button id=togglebutton5 popovertarget=innerpopover5>toggle popover</button>
+</div>
+<div id=innerpopover5 popover=auto>popover</div>
+<script>
+test(() => {
+ outerpopover5.showPopover();
+ outerpopover5.querySelector('input').click(); // Invoke innerpopover
+ assert_true(innerpopover5.matches(':popover-open'),
+ 'The inner popover should be able to open successfully.');
+ assert_true(outerpopover5.matches(':popover-open'),
+ 'The outer popover should stay open when opening the inner one.');
+
+ togglebutton5.setAttribute('type', 'text');
+ assert_true(innerpopover5.matches(':popover-open'),
+ 'The inner popover be should be not closed when invoking buttons cease to be invokers.');
+ assert_true(outerpopover5.matches(':popover-open'),
+ 'The outer popover be should be not closed when invoking buttons cease to be invokers.');
+}, 'Changing the input type on a popover*target button when popovers are open should not close anything.');
+</script>
+
+<div id=outerpopover6 popover=auto>
+ <button id=togglebutton6 popovertarget=innerpopover6>toggle popover</button>
+</div>
+<div id=innerpopover6 popover=auto>popover</div>
+<script>
+test(() => {
+ outerpopover6.showPopover();
+ outerpopover6.querySelector('button').click(); // Invoke innerpopover
+ assert_true(innerpopover6.matches(':popover-open'),
+ 'The inner popover should be able to open successfully.');
+ assert_true(outerpopover6.matches(':popover-open'),
+ 'The outer popover should stay open when opening the inner one.');
+
+ togglebutton6.remove();
+ assert_true(innerpopover6.matches(':popover-open'),
+ 'The inner popover be should be not closed when invoking buttons are removed.');
+ assert_true(outerpopover6.matches(':popover-open'),
+ 'The outer popover be should be not closed when invoking buttons are removed.');
+}, 'Disconnecting popover*target buttons when popovers are open should not close anything.');
+</script>
+
+<div id=outerpopover7 popover=auto>
+ <button id=togglebutton7 popovertarget=innerpopover7>toggle popover</button>
+</div>
+<div id=innerpopover7 popover=auto>popover</div>
+<script>
+test(() => {
+ outerpopover7.showPopover();
+ outerpopover7.querySelector('button').click(); // Invoke innerpopover
+ assert_true(innerpopover7.matches(':popover-open'),
+ 'The inner popover should be able to open successfully.');
+ assert_true(outerpopover7.matches(':popover-open'),
+ 'The outer popover should stay open when opening the inner one.');
+
+ togglebutton7.setAttribute('popovertarget', 'otherpopover7');
+ assert_true(innerpopover7.matches(':popover-open'),
+ 'The inner popover be should be not closed when invoking buttons are retargeted.');
+ assert_true(outerpopover7.matches(':popover-open'),
+ 'The outer popover be should be not closed when invoking buttons are retargeted.');
+}, 'Changing the popovertarget attribute to break the chain should not close anything.');
+</script>
+
+<div id=outerpopover8 popover=auto>
+ <div id=middlepopover8 popover=auto>
+ <div id=innerpopover8 popover=auto>hello</div>
+ </div>
+</div>
+<div id=otherpopover8 popover=auto>other popover</div>
+<button id=togglebutton8 popovertarget=middlepopover8>other button</div>
+<script>
+test(() => {
+ outerpopover8.showPopover();
+ middlepopover8.showPopover();
+ innerpopover8.showPopover();
+ assert_true(innerpopover8.matches(':popover-open'),
+ 'The inner popover should be able to open successfully.');
+ assert_true(middlepopover8.matches(':popover-open'),
+ 'The middle popover should stay open when opening the inner one.');
+ assert_true(outerpopover8.matches(':popover-open'),
+ 'The outer popover should stay open when opening the inner one.');
+
+ togglebutton8.setAttribute('popovertarget', 'otherpopover8');
+ assert_true(innerpopover8.matches(':popover-open'),
+ 'The inner popover should remain open.');
+ assert_true(middlepopover8.matches(':popover-open'),
+ 'The middle popover should remain open.');
+ assert_true(outerpopover8.matches(':popover-open'),
+ 'The outer popover should remain open.');
+}, `Modifying popovertarget on a button which doesn't break the chain shouldn't close any popovers.`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-combinations.html b/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-combinations.html
new file mode 100644
index 0000000000..024794f578
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-combinations.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Popover combined with dialog/fullscreen behavior</title>
+<link rel=author href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<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>
+<script src="/common/top-layer.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<button id=visible>Visible button</button>
+<div id=examples>
+ <dialog popover>Popover Dialog</dialog>
+ <dialog popover open style="top:50px;">Open Non-modal Popover Dialog</dialog>
+ <div popover class=fullscreen>Fullscreen Popover</div>
+ <dialog popover class=fullscreen>Fullscreen Popover Dialog</dialog>
+ <dialog popover open class=fullscreen style="top:200px;">Fullscreen Open Non-modal Popover Dialog</dialog>
+</div>
+
+<style>
+ [popover] {
+ inset:auto;
+ top:0;
+ left:0;
+ }
+ [popover].fullscreen.visible {
+ display:block;
+ }
+</style>
+
+<script>
+const isDialog = (ex) => ex instanceof HTMLDialogElement;
+const isFullscreen = (ex) => ex.classList.contains('fullscreen');
+function ensureIsOpenPopover(ex,message) {
+ // Because :popover-open will eventually support <dialog>, this does extra work to
+ // verify we're dealing with an :popover-open Popover. Note that this will also throw
+ // if this is an element with the `popover` attribute that has been made
+ // visible via an explicit `display:block` style rule.
+ message = message || 'Error';
+ assert_true(ex.matches(':popover-open'),`${message}: Popover doesn\'t match :popover-open`);
+ ex.hidePopover(); // Shouldn't throw if this is a showing popover
+ ex.showPopover(); // Show it again to avoid state change
+ assert_true(ex.matches(':popover-open'),`${message}: Sanity`);
+}
+window.onload = () => requestAnimationFrame(() => requestAnimationFrame(() => {
+ const examples = Array.from(document.querySelectorAll('#examples>*'));
+ examples.forEach(ex => {
+ promise_test(async (t) => {
+ t.add_cleanup(() => ex.remove());
+ // Test initial conditions
+ if (ex.hasAttribute('open')) {
+ assert_true(isDialog(ex));
+ assert_true(isElementVisible(ex),'Open dialog should be visible by default');
+ ex.showPopover(); // Should not throw
+ ex.removeAttribute('open');
+ ex.hidePopover();
+ assert_false(isElementVisible(ex),'Removing the open attribute should hide the dialog');
+ } else {
+ ex.showPopover(); // Should not throw
+ ensureIsOpenPopover(ex,'showPopover should work');
+ ex.hidePopover(); // Should not throw
+ assert_false(ex.matches(':popover-open'),'hidePopover should work');
+ }
+ assert_false(isElementVisible(ex));
+
+ // Start with popover, try the other API
+ ex.showPopover();
+ ensureIsOpenPopover(ex);
+ let tested_something=false;
+ if (isDialog(ex)) {
+ tested_something=true;
+ assert_throws_dom("InvalidStateError",() => ex.showModal(),'Calling showModal() on an already-showing Popover should throw InvalidStateError');
+ ex.show(); // Should not throw
+ ex.close();
+ ex.showPopover();
+ }
+ if (isFullscreen(ex)) {
+ tested_something=true;
+ let requestSucceeded = false;
+ await blessTopLayer(ex);
+ await ex.requestFullscreen()
+ .then(() => {requestSucceeded = true;}) // We should not hit this.
+ .catch((exception) => {
+ // This exception is expected.
+ assert_equals(exception.name,'TypeError',`Invalid exception from requestFullscreen() (${exception.message})`);
+ });
+ assert_false(requestSucceeded,'requestFullscreen() should not succeed when the element is an already-showing Popover');
+ }
+ assert_true(tested_something);
+ ensureIsOpenPopover(ex);
+ ex.hidePopover();
+
+ // Start with the other API, then try popover
+ if (isDialog(ex)) {
+ ex.show();
+ assert_true(ex.hasAttribute('open'));
+ ex.showPopover(); // Should not throw
+ ex.close();
+ assert_false(ex.hasAttribute('open'));
+ ex.hidePopover();
+ ex.showModal();
+ assert_true(ex.hasAttribute('open'));
+ assert_throws_dom("InvalidStateError",() => ex.showPopover(),'Calling showPopover() on an already-showing modal dialog should throw InvalidStateError');
+ ex.close();
+ assert_false(ex.hasAttribute('open'));
+ ex.hidePopover();
+ } else if (isFullscreen(ex)) {
+ let requestSucceeded = false;
+ await blessTopLayer(visible);
+ await ex.requestFullscreen()
+ .then(() => {
+ assert_throws_dom("InvalidStateError",() => ex.showPopover(),'Calling showPopover() on an already-fullscreen element should throw InvalidStateError');
+ });
+ await document.exitFullscreen()
+ .then(() => assert_true(true));
+ }
+
+ // Finally, try invoking these combined popovers via a declarative invoker
+ const button = document.createElement('button');
+ t.add_cleanup(() => button.remove());
+ document.body.appendChild(button);
+ button.popoverTargetElement = ex;
+ button.popoverTargetAction = "toggle";
+ assert_false(ex.matches(':popover-open'));
+ await clickOn(button);
+ ensureIsOpenPopover(ex,'Invoking element should be able to invoke all popovers');
+ ex.hidePopover();
+ if (isDialog(ex)) {
+ ex.showModal();
+ assert_true(ex.hasAttribute('open'));
+ } else if (isFullscreen(ex)) {
+ // Popover fullscreen isn't visible by default, so explicitly add
+ // display:block, so that calls to "clickOn" can succeed.
+ ex.classList.add('visible');
+ await blessTopLayer(ex);
+ await ex.requestFullscreen();
+ } else {
+ assert_unreached('Not a dialog or fullscreen');
+ }
+ ex.appendChild(button); // Add button to the element, so it's visible to click
+ await clickOn(button);
+ assert_false(ex.matches(':popover-open'),'The invoker click should have failed on the already-open dialog/fullscreen');
+ if (isDialog(ex)) {
+ ex.close();
+ } else {
+ await document.exitFullscreen()
+ }
+ }, `Popover combination: ${ex.textContent}`);
+ });
+}));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-interactions.html b/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-interactions.html
new file mode 100644
index 0000000000..6d050ed99b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-interactions.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Interactions between top layer element types</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<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="/common/top-layer.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<body>
+<script>
+const types = Object.freeze({
+ popover: Symbol("Popover"),
+ modalDialog: Symbol("Modal Dialog"),
+ fullscreen: Symbol("Fullscreen Element"),
+});
+const examples = [
+ {
+ type: types.popover,
+ closes: [types.popover],
+ createElement: () => Object.assign(document.createElement('div'), {popover: 'auto'}),
+ trigger: function() {this.element.showPopover()},
+ close: function() {this.element.hidePopover()},
+ isTopLayer: function() {return this.element.matches(':popover-open')},
+ },
+ {
+ type: types.modalDialog,
+ closes: [types.popover],
+ createElement: () => document.createElement('dialog'),
+ trigger: function() {this.element.showModal()},
+ close: function() {this.element.close()},
+ isTopLayer: function() {return this.element.matches(':modal')},
+ },
+ {
+ type: types.fullscreen,
+ closes: [types.popover],
+ createElement: () => document.createElement('div'),
+ trigger: async function(visibleElement) {assert_false(this.isTopLayer());await blessTopLayer(visibleElement);await this.element.requestFullscreen();},
+ close: async function() {await document.exitFullscreen();},
+ isTopLayer: function() {return this.element.matches(':fullscreen')},
+ },
+];
+
+function createElement(ex) {
+ assert_true(!ex.element);
+ const element = ex.element = ex.createElement();
+ assert_true(!!element);
+ element.appendChild(document.createTextNode(`This is a ${ex.type.description}`));
+ document.body.appendChild(element);
+ assert_false(ex.isTopLayer(),'Element should start out not in the top layer');
+ return element;
+}
+async function doneWithExample(ex) {
+ assert_true(!!ex.element);
+ if (ex.isTopLayer())
+ await ex.close();
+ ex.element.remove();
+ ex.element = null;
+}
+// Test interactions between top layer elements
+for(let i=0;i<examples.length;++i) {
+ for(let j=0;j<examples.length;++j) {
+ const example1 = Object.assign([],examples[i]);
+ const example2 = Object.assign([],examples[j]);
+ const shouldClose = example2.closes.includes(example1.type);
+ const desc = `A ${example2.type.description} should${shouldClose ? "" : " *not*"} close a ${example1.type.description}.`;
+ promise_test(async t => {
+ const element1 = createElement(example1);
+ const element2 = createElement(example2);
+ t.add_cleanup(() => {
+ return Promise.all([
+ doneWithExample(example1),
+ doneWithExample(example2),
+ ]);
+ });
+ await example1.trigger(document.body); // Open the 1st top layer element
+ assert_true(example1.isTopLayer()); // Make sure it is top layer
+ await example2.trigger(element1); // Open the 2nd top layer element
+ assert_true(example2.isTopLayer()); // Make sure it is top layer
+ assert_equals(shouldClose,!example1.isTopLayer(),desc);
+ },desc);
+ }
+}
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-nesting-anchor.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-nesting-anchor.tentative.html
new file mode 100644
index 0000000000..4520ab0577
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-nesting-anchor.tentative.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<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="/common/top-layer.js"></script>
+<script src="resources/popover-utils.js"></script>
+<script src="resources/popover-top-layer-nesting.js"></script>
+
+<div id=tests>
+ <div> Single popover=auto ancestor
+ <div popover=auto class=target data-stay-open=true></div>
+ </div>
+
+ <div> Single popover=manual ancestor
+ <div popover=auto class=target data-stay-open=true></div>
+ </div>
+
+ <div> Nested popover=auto ancestors
+ <div popover=auto data-stay-open=true>
+ <div popover=auto class=target data-stay-open=true></div>
+ </div>
+ </div>
+
+ <div> Nested popover=auto ancestors, target is outer
+ <div popover=auto class=target data-stay-open=true>
+ <div popover=auto data-stay-open=false></div>
+ </div>
+ </div>
+
+ <div> Top layer inside of nested element
+ <div popover=auto data-stay-open=true>
+ <button class=target></button>
+ </div>
+ </div>
+</div>
+
+<script>
+ const tests = Array.from(document.querySelectorAll('#tests>div'));
+ runTopLayerTests(tests,/*testAnchorAttribute*/true);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-nesting-hints.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-nesting-hints.tentative.html
new file mode 100644
index 0000000000..4ec1f49bda
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-nesting-hints.tentative.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://open-ui.org/components/popover-hint.research.explainer">
+<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="/common/top-layer.js"></script>
+<script src="resources/popover-utils.js"></script>
+<script src="resources/popover-top-layer-nesting.js"></script>
+
+<div id=tests>
+ <div> Single popover=hint ancestor
+ <div popover=hint class=target data-stay-open=true></div>
+ </div>
+
+ <div> Nested auto/hint ancestors
+ <div popover=auto data-stay-open=true>
+ <div popover=hint class=target data-stay-open=true></div>
+ </div>
+ </div>
+
+ <div> Nested auto/hint ancestors, target is auto
+ <div popover=auto class=target data-stay-open=true>
+ <div popover=hint data-stay-open=false></div>
+ </div>
+ </div>
+
+ <div> Unrelated hint, target=hint
+ <div popover=auto data-stay-open=true></div>
+ <div popover=hint class=target data-stay-open=true></div>
+ </div>
+
+ <div> Unrelated hint, target=auto
+ <div popover=auto class=target data-stay-open=true></div>
+ <div popover=hint data-stay-open=false></div>
+ </div>
+</div>
+
+<script>
+ const tests = Array.from(document.querySelectorAll('#tests>div'));
+ runTopLayerTests(tests);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-nesting.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-nesting.tentative.html
new file mode 100644
index 0000000000..a0b3b60b72
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-top-layer-nesting.tentative.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<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="/common/top-layer.js"></script>
+<script src="resources/popover-utils.js"></script>
+<script src="resources/popover-top-layer-nesting.js"></script>
+
+<div id=tests>
+ <div> Single popover=auto ancestor
+ <div popover=auto class=target data-stay-open=true></div>
+ </div>
+
+ <div> Single popover=manual ancestor
+ <div popover=auto class=target data-stay-open=true></div>
+ </div>
+
+ <div> Nested popover=auto ancestors
+ <div popover=auto data-stay-open=true>
+ <div popover=auto class=target data-stay-open=true></div>
+ </div>
+ </div>
+
+ <div> Nested popover=auto ancestors, target is outer
+ <div popover=auto class=target data-stay-open=true>
+ <div popover=auto data-stay-open=false></div>
+ </div>
+ </div>
+
+ <div> Top layer inside of nested element
+ <div popover=auto data-stay-open=true>
+ <button class=target></button>
+ </div>
+ </div>
+</div>
+
+<script>
+ const tests = Array.from(document.querySelectorAll('#tests>div'));
+ runTopLayerTests(tests);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-types-with-hints.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-types-with-hints.tentative.html
new file mode 100644
index 0000000000..07f0e26fce
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-types-with-hints.tentative.html
@@ -0,0 +1,179 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+ function getPopovers() {
+ return Array.from(document.currentScript.parentElement.querySelectorAll('[popover]'));
+ }
+ function assertState(expectedState,description) {
+ description = description || 'Error';
+ const popovers = getPopovers();
+ const n = popovers.length;
+ assert_equals(expectedState.length,n,'Invalid expected state length');
+ for(let i=0;i<n;++i) {
+ const html = '<' + popovers[i].outerHTML.split('<')[1] + '</div>';
+ assert_equals(popovers[i].matches(':popover-open'),expectedState[i],`${description}, index ${i} (${html})`);
+ }
+ }
+</script>
+
+<div>
+ <div popover>Popover</div>
+ <div popover=hint>Hint</div>
+ <div popover=manual>Async</div>
+ <div popover=manual>Async</div>
+ <script>
+ {
+ const auto = getPopovers()[0];
+ const hint = getPopovers()[1];
+ const manual = getPopovers()[2];
+ const manual2 = getPopovers()[3];
+ test(() => {
+ assertState([false,false,false,false]);
+ auto.showPopover();
+ assertState([true,false,false,false]);
+ hint.showPopover();
+ assertState([true,true,false,false]);
+ manual.showPopover();
+ assertState([true,true,true,false]);
+ manual2.showPopover();
+ assertState([true,true,true,true]);
+ hint.hidePopover();
+ assertState([true,false,true,true]);
+ auto.hidePopover();
+ assertState([false,false,true,true]);
+ auto.showPopover();
+ hint.showPopover();
+ assertState([true,true,true,true]);
+ auto.hidePopover();
+ assertState([false,false,true,true]);
+ hint.hidePopover();
+ manual.hidePopover();
+ assertState([false,false,false,true]);
+ manual2.hidePopover();
+ assertState([false,false,false,false]);
+ },'manuals do not close popovers');
+
+ test(() => {
+ assertState([false,false,false,false]);
+ hint.showPopover();
+ manual.showPopover();
+ manual2.showPopover();
+ assertState([false,true,true,true]);
+ auto.showPopover();
+ assertState([true,false,true,true]);
+ auto.hidePopover();
+ assertState([false,false,true,true]);
+ manual.hidePopover();
+ manual2.hidePopover();
+ assertState([false,false,false,false]);
+ },'autos close hints but not manuals');
+ }
+ </script>
+</div>
+
+<div>
+ <div popover>popover 1
+ <div popover>popover 2
+ <p id=anchorid>Anchor</p>
+ <div popover>popover 3</div>
+ </div>
+ </div>
+ <div popover=hint anchor=anchorid>Hint anchored to popover</div>
+ <script>
+ {
+ const popover1 = getPopovers()[0];
+ const popover2 = getPopovers()[1];
+ const popover3 = getPopovers()[2];
+ const hint = getPopovers()[3];
+ test(() => {
+ assertState([false,false,false,false]);
+ popover1.showPopover();
+ popover2.showPopover();
+ popover3.showPopover();
+ assertState([true,true,true,false]);
+ hint.showPopover(); // Because hint is nested in popover2, popover3 should be hidden
+ assertState([true,true,false,true]);
+ popover1.hidePopover(); // Should close the hint, which is anchored to popover2
+ assertState([false,false,false,false]);
+ },'hint is not closed by pre-existing auto');
+ }
+ </script>
+</div>
+
+<div>
+ <div popover=hint>Hint
+ <div popover=hint>Nested hint</div>
+ </div>
+ <script>
+ test(() => {
+ const hint1 = getPopovers()[0];
+ const hint2 = getPopovers()[1];
+ hint1.showPopover();
+ assertState([true,false]);
+ hint2.showPopover();
+ assertState([true,true]);
+ hint1.hidePopover();
+ assertState([false,false]);
+ },'You can nest hint popovers');
+ </script>
+</div>
+
+<div>
+ <div popover="hint">Hint
+ <div popover>Nested auto (note - never visible, since inside display:none subtree)</div>
+ </div>
+ <script>
+ test(() => {
+ const hint = getPopovers()[0];
+ const auto = getPopovers()[1];
+ hint.showPopover();
+ assertState([true,false]);
+ auto.showPopover();
+ assertState([false,true]);
+ auto.hidePopover();
+ assertState([false,false]);
+ },'If a popover=auto is shown, it should hide any open popover=hint, including if the popover=hint is an ancestral popover of the popover=auto. (You can\'t nest a popover=auto inside a popover=hint)');
+ </script>
+</div>
+
+<div>
+ <div popover>Auto
+ <div popover>Nested Auto</div>
+ <div popover=hint>Nested hint</div>
+ </div>
+ <script>
+ test(() => {
+ const auto = getPopovers()[0];
+ const auto2 = getPopovers()[1];
+ const hint = getPopovers()[2];
+ auto.showPopover();
+ auto2.showPopover();
+ assertState([true,true,false]);
+ hint.showPopover(); // This should hide auto2, since it is nested in auto1.
+ assertState([true,false,true]);
+ auto.hidePopover(); // Should hide both auto and hint.
+ assertState([false,false,false]);
+ },'If you: a) show a popover=auto (call it D), then b) show a descendent popover=hint of D (call it T), then c) hide D, then T should be hidden. (A popover=hint can be nested inside a popover=auto)');
+ </script>
+</div>
+
+<div>
+ <div popover>Auto</div>
+ <div popover=hint>Non-Nested hint</div>
+ <script>
+ test(() => {
+ const auto = getPopovers()[0]
+ const hint = getPopovers()[1];
+ auto.showPopover();
+ hint.showPopover();
+ assertState([true,true]);
+ auto.hidePopover();
+ assertState([false,false]);
+ },'If you: a) show a popover=auto (call it D), then b) show a non-descendent popover=hint of D (call it T), then c) hide D, then T should be hidden. (Non-nested popover=hint gets hidden when unrelated popover=autos are hidden)');
+ </script>
+</div>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-types.html b/testing/web-platform/tests/html/semantics/popovers/popover-types.html
new file mode 100644
index 0000000000..d4ad81e52b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-types.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="container">
+ <div popover>Popover</div>
+ <div popover=manual>Async</div>
+ <div popover=manual>Async</div>
+</div>
+<script>
+ const auto = container.querySelector('[popover=""]');
+ const manual = container.querySelectorAll('[popover=manual]')[0];
+ const manual2 = container.querySelectorAll('[popover=manual]')[1];
+ function assert_state_1(autoOpen,manualOpen,manual2Open) {
+ assert_equals(auto.matches(':popover-open'),autoOpen,'auto open state is incorrect');
+ assert_equals(manual.matches(':popover-open'),manualOpen,'manual open state is incorrect');
+ assert_equals(manual2.matches(':popover-open'),manual2Open,'manual2 open state is incorrect');
+ }
+ test(() => {
+ assert_state_1(false,false,false);
+ auto.showPopover();
+ assert_state_1(true,false,false);
+ manual.showPopover();
+ assert_state_1(true,true,false);
+ manual2.showPopover();
+ assert_state_1(true,true,true);
+ auto.hidePopover();
+ assert_state_1(false,true,true);
+ manual.hidePopover();
+ assert_state_1(false,false,true);
+ manual2.hidePopover();
+ assert_state_1(false,false,false);
+ }, 'manuals do not close popovers');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-undefined-remove-crash.html b/testing/web-platform/tests/html/semantics/popovers/popover-undefined-remove-crash.html
new file mode 100644
index 0000000000..3c273ea6f3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-undefined-remove-crash.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/9459#issuecomment-1630466911">
+
+<div id="po" popover>
+PO
+</div>
+
+<script>
+po.popover = undefined;
+po.remove();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/popovertarget-reflection.html b/testing/web-platform/tests/html/semantics/popovers/popovertarget-reflection.html
new file mode 100644
index 0000000000..d0750fdd4c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popovertarget-reflection.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1523410">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1879001">
+<link rel=help href="https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes:element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<button id=mybutton popovertarget="mypopover">toggle popover</button>
+<div id=mypopover popover=auto>popover</div>
+
+<script>
+test(() => {
+ assert_equals(mybutton.popoverTargetElement.id, "mypopover",
+ 'Setting element.popoverTargetElement to a valid element should work');
+
+ mybutton.popoverTargetElement = null;
+ assert_false(mybutton.hasAttribute('popovertarget'),
+ 'Setting element.popoverTargetElement to null should unset popovertarget attribute.');
+ assert_equals(mybutton.popoverTargetElement, null,
+ 'Setting element.popoverTargetElement to null should remove the existing element from element.popoverTargetElement.');
+
+ mybutton.popoverTargetElement = mypopover;
+ assert_true(mybutton.hasAttribute('popovertarget'),
+ 'Assigning to element.popoverTargetElement should set the popovertarget attribute.');
+
+ mybutton.removeAttribute('popovertarget');
+ assert_equals(mybutton.popoverTargetElement, null,
+ 'Removing the popovertarget attribute should remove the element from element.popoverTargetElement.');
+
+ mybutton.popoverTargetElement = mypopover;
+ assert_true(mybutton.hasAttribute('popovertarget'),
+ 'Assigning to element.popoverTargetElement should set the popovertarget attribute.');
+
+ mybutton.setAttribute("popovertarget", 'invalid');
+ assert_equals(mybutton.popoverTargetElement, null,
+ 'Setting the popovertarget attribute to a localName that is not attr should remove the existing element from element.popoverTargetElement.');
+
+ mybutton.popoverTargetElement = mypopover;
+ mybutton.setAttribute("popovertarget", "");
+ assert_equals(mybutton.popoverTargetElement.id, "mypopover",
+ 'Setting the popovertarget attribute to empty string right after explicitly setting attribute element should have no effect.');
+
+ mybutton.setAttribute("popovertarget", "mypopover");
+ assert_equals(mybutton.popoverTargetElement.id, "mypopover",
+ 'Setting the popovertarget attribute to a value should set the popover target element.');
+ mybutton.setAttribute("popovertarget", "");
+ assert_equals(mybutton.getAttribute('popovertarget'), "",
+ 'Assigning to element.popoverTargetElement to empty string should update the attribute value to empty string.');
+ assert_equals(mybutton.popoverTargetElement, null,
+ 'Setting the popovertarget attribute to empty string should remove the existing element from element.popoverTargetElement.');
+}, 'Element attribute reflection of popoverTargetElement/popovertarget should be kept in sync.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/resources/popover-hover-hide-common.js b/testing/web-platform/tests/html/semantics/popovers/resources/popover-hover-hide-common.js
new file mode 100644
index 0000000000..9f407ef157
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/resources/popover-hover-hide-common.js
@@ -0,0 +1,139 @@
+// NOTE about testing methodology:
+// This test checks whether popovers are hidden *after* the appropriate de-hover
+// delay. The delay used for testing is kept low, to avoid this test taking too
+// long, but that means that sometimes on a slow bot/client, the delay can
+// elapse before we are able to check the popover status. And that can make this
+// test flaky. To avoid that, the msSinceMouseOver() function is used to check
+// that not-too-much time has passed, and if it has, the test is simply skipped.
+
+const hoverDelays = 100; // This needs to match the style block below.
+const hoverWaitTime = 200; // How long to wait to cover the delay for sure.
+
+async function initialPopoverShow(invoker) {
+ const popover = invoker.popoverTargetElement;
+ assert_false(popover.matches(':popover-open'));
+ await mouseOver(invoker); // Always start with the mouse over the invoker
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+}
+
+function runHoverHideTest(popoverType, invokerType, invokerAction) {
+ const descr = `popover=${popoverType}, invoker=${invokerType}, popovertargetaction=${invokerAction}`;
+ promise_test(async (t) => {
+ const {popover,invoker} = makeTestParts(t, popoverType, invokerType, invokerAction);
+ await initialPopoverShow(invoker);
+ await mouseOver(unrelated);
+ let showing = popover.matches(':popover-open');
+ if (msSinceMouseOver() >= hoverDelays)
+ return; // The WPT runner was too slow.
+ assert_true(showing,'popover shouldn\'t immediately hide');
+ await mouseHover(unrelated,hoverWaitTime);
+ assert_false(popover.matches(':popover-open'),'popover should hide after delay');
+ },`The popover-hide-delay causes a popover to be hidden after a delay, ${descr}`);
+
+ promise_test(async (t) => {
+ const {popover,invoker} = makeTestParts(t, popoverType, invokerType, invokerAction);
+ await initialPopoverShow(invoker);
+ await mouseHover(popover,hoverWaitTime);
+ assert_true(popover.matches(':popover-open'),'hovering the popover should keep it showing');
+ await mouseOver(unrelated);
+ let showing = popover.matches(':popover-open');
+ if (msSinceMouseOver() >= hoverDelays)
+ return; // The WPT runner was too slow.
+ assert_true(showing,'subsequently hovering unrelated element shouldn\'t immediately hide the popover');
+ await mouseHover(unrelated,hoverWaitTime);
+ assert_false(popover.matches(':popover-open'),'hovering unrelated element should hide popover after delay');
+ },`hovering the popover keeps it from being hidden, ${descr}`);
+
+ promise_test(async (t) => {
+ const {popover,invoker,mouseOverInvoker} = makeTestParts(t, popoverType, invokerType, invokerAction);
+ await initialPopoverShow(invoker);
+ assert_true(popover.matches(':popover-open'));
+ await mouseHover(popover,hoverWaitTime);
+ await mouseHover(mouseOverInvoker,hoverWaitTime);
+ assert_true(popover.matches(':popover-open'),'Moving hover between invoker and popover should keep popover from being hidden');
+ await mouseHover(unrelated,hoverWaitTime);
+ assert_false(popover.matches(':popover-open'),'Moving hover to unrelated should finally hide the popover');
+ },`hovering an invoking element keeps the popover from being hidden, ${descr}`);
+}
+
+function runHoverHideTestsForInvokerAction(invokerAction) {
+ promise_test(async (t) => {
+ const {popover,invoker} = makeTestParts(t, 'auto', 'button', 'show');
+ assert_false(popover.matches(':popover-open'));
+ assert_true(invoker.matches('[popovertarget]'),'invoker needs to match [popovertarget]');
+ assert_equals(invoker.popoverTargetElement,popover,'invoker should point to popover');
+ await mouseHover(invoker,hoverWaitTime);
+ assert_true(msSinceMouseOver() >= hoverWaitTime,'waitForHoverTime should wait the specified time');
+ assert_true(hoverWaitTime > hoverDelays,'hoverDelays is the value from CSS, hoverWaitTime should be longer than that');
+ assert_equals(getComputedStyleTimeMs(invoker,'popoverShowDelay'),hoverDelays,'popover-show-delay is incorrect');
+ assert_equals(getComputedStyleTimeMs(popover,'popoverHideDelay'),hoverDelays,'popover-hide-delay is incorrect');
+ },'Test the harness');
+
+ // Run for all invoker and popover types.
+ ["button","input"].forEach(invokerType => {
+ ["auto","hint","manual"].forEach(popoverType => {
+ runHoverHideTest(popoverType, invokerType, invokerAction);
+ });
+ });
+}
+
+// Setup stuff
+const unrelated = document.createElement('div');
+unrelated.id = 'unrelated';
+unrelated.textContent = 'Unrelated element';
+const style = document.createElement('style');
+document.body.append(unrelated,style);
+style.textContent = `
+ div, button, input {
+ /* Fixed position everything to ensure nothing overlaps */
+ position: fixed;
+ max-height: 100px;
+ }
+ #unrelated {top: 100px;}
+ [popovertarget] {
+ top:200px;
+ popover-show-delay: 100ms;
+ }
+ [popover] {
+ width: 200px;
+ height: 100px;
+ top:300px;
+ popover-hide-delay: 100ms;
+ }
+`;
+
+function makeTestParts(t,popoverType,invokerType,invokerAction) {
+ const popover = document.createElement('div');
+ popover.id = `popover-${popoverType}-${invokerType}-${invokerAction}`;
+ document.body.appendChild(popover);
+ popover.popover = popoverType;
+ assert_equals(popover.popover, popoverType, `Type ${popoverType} not supported`);
+ const invoker = document.createElement(invokerType);
+ document.body.appendChild(invoker);
+ invoker.popoverTargetElement = popover;
+ invoker.popoverTargetAction = invokerAction;
+ assert_equals(invoker.popoverTargetAction, invokerAction, `Invoker action ${invokerAction} not supported`);
+ let mouseOverInvoker;
+ switch (invokerType) {
+ case 'button':
+ invoker.innerHTML = '<span><span data-note=nested_element>Click me</span></span>';
+ mouseOverInvoker = invoker.firstElementChild.firstElementChild;
+ assert_true(!!mouseOverInvoker);
+ break;
+ case 'input':
+ invoker.type = 'button';
+ mouseOverInvoker = invoker;
+ break;
+ default:
+ assert_unreached('Invalid invokerType ' + invokerType);
+ break;
+ }
+ t.add_cleanup(() => {popover.remove(); invoker.remove();});
+ return {popover, invoker, mouseOverInvoker};
+}
+
+function getComputedStyleTimeMs(element,property) {
+ // Times are in seconds, so just strip off the 's'.
+ return Number(getComputedStyle(element)[property].slice(0,-1))*1000;
+}
diff --git a/testing/web-platform/tests/html/semantics/popovers/resources/popover-invoking-attribute.js b/testing/web-platform/tests/html/semantics/popovers/resources/popover-invoking-attribute.js
new file mode 100644
index 0000000000..d2911647e1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/resources/popover-invoking-attribute.js
@@ -0,0 +1,122 @@
+const actionReflectionLogic = (action) => {
+ switch (action?.toLowerCase()) {
+ case "show": return "show";
+ case "hide": return "hide";
+ default: return "toggle";
+ }
+}
+const noActivationLogic = (action) => {
+ return "none";
+}
+function makeElementWithType(element,type) {
+ return (test) => {
+ const el = Object.assign(document.createElement(element),{type});
+ document.body.appendChild(el);
+ test.add_cleanup(() => el.remove());
+ return el;
+ };
+}
+const supportedButtonTypes = ['button','reset','submit',''].map(type => {
+ return {
+ name: `<button type="${type}">`,
+ makeElement: makeElementWithType('button',type),
+ invokeFn: el => {el.focus(); el.click()},
+ getExpectedLogic: actionReflectionLogic,
+ };
+});
+const supportedInputButtonTypes = ['button','reset','submit','image'].map(type => {
+ return {
+ name: `<input type="${type}">`,
+ makeElement: makeElementWithType('input',type),
+ invokeFn: el => {el.focus(); el.click()},
+ getExpectedLogic: actionReflectionLogic,
+ };
+});
+const unsupportedTypes = ['text','email','password','search','tel','url','checkbox','radio','range','file','color','date','datetime-local','month','time','week','number'].map(type => {
+ return {
+ name: `<input type="${type}">`,
+ makeElement: makeElementWithType('input',type),
+ invokeFn: (el) => {el.focus();},
+ getExpectedLogic: noActivationLogic, // None of these support popover invocation
+ };
+});
+const invokers = [
+ ...supportedButtonTypes,
+ ...supportedInputButtonTypes,
+ ...unsupportedTypes,
+];
+function runPopoverInvokerTests(popoverTypes) {
+ window.addEventListener('load', () => {
+ popoverTypes.forEach(type => {
+ invokers.forEach(testcase => {
+ ["toggle","hide","show","ShOw","garbage",null,undefined].forEach(action => {
+ [false,true].forEach(use_idl_for_target => {
+ [false,true].forEach(use_idl_for_action => {
+ promise_test(async test => {
+ const popover = Object.assign(document.createElement('div'),{popover: type, id: 'my-popover'});
+ assert_equals(popover.popover,type,'reflection');
+ const invoker = testcase.makeElement(test);
+ if (use_idl_for_target) {
+ invoker.popoverTargetElement = popover;
+ assert_equals(invoker.getAttribute('popovertarget'),'','attribute value');
+ } else {
+ invoker.setAttribute('popovertarget',popover.id);
+ }
+ if (use_idl_for_action) {
+ invoker.popoverTargetAction = action;
+ assert_equals(invoker.getAttribute('popovertargetaction'),String(action),'action reflection');
+ } else {
+ invoker.setAttribute('popovertargetaction',action);
+ }
+ assert_true(!document.getElementById(popover.id));
+ assert_equals(invoker.popoverTargetElement,null,'targetElement should be null before the popover is in the document');
+ assert_equals(invoker.popoverTargetAction,actionReflectionLogic(action),'action should be correct immediately');
+ document.body.appendChild(popover);
+ test.add_cleanup(() => {popover.remove();});
+ assert_equals(invoker.popoverTargetElement,popover,'target element should be returned once it\'s in the document');
+ assert_false(popover.matches(':popover-open'));
+ await testcase.invokeFn(invoker);
+ assert_equals(document.activeElement,invoker,'Focus should end up on the invoker');
+ expectedBehavior = testcase.getExpectedLogic(action);
+ switch (expectedBehavior) {
+ case "toggle":
+ case "show":
+ assert_true(popover.matches(':popover-open'),'Toggle or show should show the popover');
+ popover.hidePopover(); // Hide the popover
+ break;
+ case "hide":
+ case "none":
+ assert_false(popover.matches(':popover-open'),'Hide or none should leave the popover hidden');
+ break;
+ default:
+ assert_unreached();
+ }
+ if (expectedBehavior === "none") {
+ // If no behavior is expected, then there is nothing left to test. Even re-focusing
+ // a control that has no expected behavior may hide an open popover via light dismiss.
+ return;
+ }
+ assert_false(popover.matches(':popover-open'));
+ popover.showPopover(); // Show the popover directly
+ assert_equals(document.activeElement,invoker,'The popover should not shift focus');
+ assert_true(popover.matches(':popover-open'));
+ await testcase.invokeFn(invoker);
+ switch (expectedBehavior) {
+ case "toggle":
+ case "hide":
+ assert_false(popover.matches(':popover-open'),'Toggle or hide should hide the popover');
+ break;
+ case "show":
+ assert_true(popover.matches(':popover-open'),'Show should leave the popover showing');
+ break;
+ default:
+ assert_unreached();
+ }
+ },`Test ${testcase.name}, action=${action}, ${use_idl_for_target ? "popoverTarget IDL" : "popovertarget attr"}, ${use_idl_for_action ? "popoverTargetAction IDL" : "popovertargetaction attr"}, with popover=${type}`);
+ });
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/testing/web-platform/tests/html/semantics/popovers/resources/popover-styles.css b/testing/web-platform/tests/html/semantics/popovers/resources/popover-styles.css
new file mode 100644
index 0000000000..df683c3c64
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/resources/popover-styles.css
@@ -0,0 +1,17 @@
+.fake-popover {
+ position: fixed;
+ inset: 0;
+ width: fit-content;
+ height: fit-content;
+ margin: auto;
+ border: solid;
+ padding: 0.25em;
+ overflow: auto;
+ color: CanvasText;
+ background-color: Canvas;
+}
+.fake-popover-backdrop {
+ position: fixed;
+ inset:0;
+ pointer-events: none !important;
+}
diff --git a/testing/web-platform/tests/html/semantics/popovers/resources/popover-top-layer-nesting.js b/testing/web-platform/tests/html/semantics/popovers/resources/popover-top-layer-nesting.js
new file mode 100644
index 0000000000..ace10b3f7b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/resources/popover-top-layer-nesting.js
@@ -0,0 +1,108 @@
+function createTopLayerElement(t,topLayerType) {
+ let element, show, showing;
+ switch (topLayerType) {
+ case 'dialog':
+ element = document.createElement('dialog');
+ show = () => element.showModal();
+ showing = () => element.matches(':modal');
+ break;
+ case 'fullscreen':
+ element = document.createElement('div');
+ show = async (topmostElement) => {
+ // Be sure to add user activation to the topmost visible target:
+ await blessTopLayer(topmostElement);
+ await element.requestFullscreen();
+ };
+ showing = () => document.fullscreenElement === element;
+ break;
+ default:
+ assert_unreached('Invalid top layer type');
+ }
+ t.add_cleanup(() => element.remove());
+ return {element,show,showing};
+}
+function runTopLayerTests(testCases, testAnchorAttribute) {
+ testAnchorAttribute = testAnchorAttribute || false;
+ testCases.forEach(test => {
+ const description = test.firstChild.data.trim();
+ assert_equals(test.querySelectorAll('.target').length,1,'There should be exactly one target');
+ const target = test.querySelector('.target');
+ assert_true(!!target,'Invalid test case');
+ const popovers = Array.from(test.querySelectorAll('[popover]'));
+ assert_true(popovers.length > 0,'No popovers found');
+ ['dialog','fullscreen'].forEach(topLayerType => {
+ promise_test(async t => {
+ const {element,show,showing} = createTopLayerElement(t,topLayerType);
+ target.appendChild(element);
+
+ // Show the popovers.
+ t.add_cleanup(() => popovers.forEach(popover => popover.hidePopover()));
+ popovers.forEach(popover => popover.showPopover());
+ popovers.forEach(popover => assert_true(popover.matches(':popover-open'),'All popovers should be open'));
+
+ // Activate the top layer element.
+ await show(popovers[popovers.length-1]);
+ assert_true(showing());
+ popovers.forEach(popover => assert_equals(popover.matches(':popover-open'),popover.dataset.stayOpen==='true','Incorrect behavior'));
+
+ // Add another popover within the top layer element and make sure entire stack stays open.
+ const newPopover = document.createElement('div');
+ t.add_cleanup(() => newPopover.remove());
+ newPopover.popover = popoverHintSupported() ? 'hint' : 'auto';
+ element.appendChild(newPopover);
+ popovers.forEach(popover => assert_equals(popover.matches(':popover-open'),popover.dataset.stayOpen==='true','Adding another popover shouldn\'t change anything'));
+ assert_true(showing(),'top layer element should still be top layer');
+ newPopover.showPopover();
+ assert_true(newPopover.matches(':popover-open'));
+ popovers.forEach(popover => assert_equals(popover.matches(':popover-open'),popover.dataset.stayOpen==='true','Showing the popover shouldn\'t change anything'));
+ assert_true(showing(),'top layer element should still be top layer');
+ },`${description} with ${topLayerType}`);
+
+ promise_test(async t => {
+ const {element,show,showing} = createTopLayerElement(t,topLayerType);
+ element.popover = popoverHintSupported() ? 'hint' : 'auto';
+ target.appendChild(element);
+
+ // Show the popovers.
+ t.add_cleanup(() => popovers.forEach(popover => popover.hidePopover()));
+ popovers.forEach(popover => popover.showPopover());
+ popovers.forEach(popover => assert_true(popover.matches(':popover-open'),'All popovers should be open'));
+ const targetWasOpenPopover = target.matches(':popover-open');
+
+ // Show the top layer element as a popover first.
+ element.showPopover();
+ assert_true(element.matches(':popover-open'),'element should be open as a popover');
+ assert_equals(target.matches(':popover-open'),targetWasOpenPopover,'target shouldn\'t change popover state');
+
+ try {
+ await show(element);
+ assert_unreached('It is an error to activate a top layer element that is already a showing popover');
+ } catch (e) {
+ // We expect an InvalidStateError for dialogs, and a TypeError for fullscreens.
+ // Anything else should fall through to the test harness.
+ if (e.name !== 'InvalidStateError' && e.name !== 'TypeError') {
+ throw e;
+ }
+ }
+ },`${description} with ${topLayerType}, top layer element *is* a popover`);
+
+ if (testAnchorAttribute) {
+ promise_test(async t => {
+ const {element,show,showing} = createTopLayerElement(t,topLayerType);
+ element.anchorElement = target;
+ document.body.appendChild(element);
+
+ // Show the popovers.
+ t.add_cleanup(() => popovers.forEach(popover => popover.hidePopover()));
+ popovers.forEach(popover => popover.showPopover());
+ popovers.forEach(popover => assert_true(popover.matches(':popover-open'),'All popovers should be open'));
+
+ // Activate the top layer element.
+ await show(popovers[popovers.length-1]);
+ assert_true(showing());
+ popovers.forEach(popover => assert_equals(popover.matches(':popover-open'),popover.dataset.stayOpen==='true','Incorrect behavior'));
+ },`${description} with ${topLayerType}, anchor attribute`);
+ }
+ });
+ });
+}
diff --git a/testing/web-platform/tests/html/semantics/popovers/resources/popover-utils.js b/testing/web-platform/tests/html/semantics/popovers/resources/popover-utils.js
new file mode 100644
index 0000000000..bfc1f89ec1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/resources/popover-utils.js
@@ -0,0 +1,176 @@
+function waitForRender() {
+ return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+}
+
+function waitForTick() {
+ return new Promise(resolve => step_timeout(resolve, 0));
+}
+
+async function clickOn(element) {
+ const actions = new test_driver.Actions();
+ await waitForRender();
+ await actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ await waitForRender();
+}
+async function sendTab() {
+ await waitForRender();
+ const kTab = '\uE004';
+ await new test_driver.send_keys(document.activeElement || document.documentElement, kTab);
+ await waitForRender();
+}
+async function sendShiftTab() {
+ await waitForRender();
+ const kShift = '\uE008';
+ const kTab = '\uE004';
+ await new test_driver.Actions()
+ .keyDown(kShift)
+ .keyDown(kTab)
+ .keyUp(kTab)
+ .keyUp(kShift)
+ .send();
+ await waitForRender();
+}
+async function sendEscape() {
+ await waitForRender();
+ await new test_driver.send_keys(document.activeElement || document.documentElement,'\uE00C'); // Escape
+ await waitForRender();
+}
+async function sendEnter() {
+ await waitForRender();
+ await new test_driver.send_keys(document.activeElement || document.documentElement,'\uE007'); // Enter
+ await waitForRender();
+}
+function isElementVisible(el) {
+ return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
+}
+async function finishAnimations(popover) {
+ popover.getAnimations({subtree: true}).forEach(animation => animation.finish());
+ await waitForRender();
+}
+let mousemoveInfo;
+function mouseOver(element) {
+ mousemoveInfo?.controller?.abort();
+ const controller = new AbortController();
+ mousemoveInfo = {element, controller, moved: false, started: performance.now()};
+ return (new test_driver.Actions())
+ .pointerMove(0, 0, {origin: element})
+ .send()
+ .then(() => {
+ document.addEventListener("mousemove", (e) => {mousemoveInfo.moved = true;}, {signal: controller.signal});
+ })
+}
+function msSinceMouseOver() {
+ return performance.now() - mousemoveInfo.started;
+}
+function assertMouseStillOver(element) {
+ assert_equals(mousemoveInfo.element, element, 'Broken test harness');
+ assert_false(mousemoveInfo.moved,'Broken test harness');
+}
+async function waitForHoverTime(hoverWaitTimeMs) {
+ await new Promise(resolve => step_timeout(resolve,hoverWaitTimeMs));
+ await waitForRender();
+};
+async function mouseHover(element,hoverWaitTimeMs) {
+ await mouseOver(element);
+ await waitForHoverTime(hoverWaitTimeMs);
+ assertMouseStillOver(element);
+}
+
+// This is a "polyfill" of sorts for the `defaultopen` attribute.
+// It can be called before window.load is complete, and it will
+// show defaultopen popovers according to the rules previously part
+// of the popover API: any popover=manual popover can be shown this
+// way, and only the first popover=auto popover.
+function showDefaultopenPopoversOnLoad() {
+ function show() {
+ const popovers = Array.from(document.querySelectorAll('[popover][defaultopen]'));
+ popovers.forEach((p) => {
+ // The showPopover calls below aren't guarded by a check on the popover
+ // open/closed status. If they throw exceptions, this function was
+ // probably called at a bad time. However, a check is made for open
+ // <dialog open> elements.
+ if (p instanceof HTMLDialogElement && p.hasAttribute('open'))
+ return;
+ switch (p.popover) {
+ case 'auto':
+ if (!document.querySelector('[popover]:popover-open'))
+ p.showPopover();
+ return;
+ case 'manual':
+ p.showPopover();
+ return;
+ default:
+ assert_unreached(`Unknown popover type ${p.popover}`);
+ }
+ });
+ }
+ if (document.readyState === 'complete') {
+ show();
+ } else {
+ window.addEventListener('load',show,{once:true});
+ }
+}
+function popoverHintSupported() {
+ // TODO(crbug.com/1416284): This function should be removed, and
+ // any calls replaced with `true`, once popover=hint ships.
+ const testElement = document.createElement('div');
+ testElement.popover = 'hint';
+ return testElement.popover === 'hint';
+}
+
+function assertPopoverVisibility(popover, isPopover, expectedVisibility, message) {
+ const isVisible = isElementVisible(popover);
+ assert_equals(isVisible, expectedVisibility,`${message}: Expected this element to be ${expectedVisibility ? "visible" : "not visible"}`);
+ // Check other things related to being visible or not:
+ if (isVisible) {
+ assert_not_equals(window.getComputedStyle(popover).display,'none');
+ assert_equals(popover.matches(':popover-open'),isPopover,`${message}: Visible popovers should match :popover-open`);
+ } else {
+ assert_equals(window.getComputedStyle(popover).display,'none',`${message}: Non-showing popovers should have display:none`);
+ assert_false(popover.matches(':popover-open'),`${message}: Non-showing popovers should *not* match :popover-open`);
+ }
+}
+
+function assertIsFunctionalPopover(popover, checkVisibility) {
+ assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'A popover should start out hidden');
+ popover.showPopover();
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After showPopover(), a popover should be visible');
+ popover.showPopover(); // Calling showPopover on a showing popover should not throw.
+ popover.hidePopover();
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'After hidePopover(), a popover should be hidden');
+ popover.hidePopover(); // Calling hidePopover on a hidden popover should not throw.
+ popover.togglePopover();
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After togglePopover() on hidden popover, it should be visible');
+ popover.togglePopover();
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'After togglePopover() on visible popover, it should be hidden');
+ popover.togglePopover(/*force=*/true);
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After togglePopover(true) on hidden popover, it should be visible');
+ popover.togglePopover(/*force=*/true);
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After togglePopover(true) on visible popover, it should be visible');
+ popover.togglePopover(/*force=*/false);
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'After togglePopover(false) on visible popover, it should be hidden');
+ popover.togglePopover(/*force=*/false);
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'After togglePopover(false) on hidden popover, it should be hidden');
+ const parent = popover.parentElement;
+ popover.remove();
+ assert_throws_dom("InvalidStateError",() => popover.showPopover(),'Calling showPopover on a disconnected popover should throw InvalidStateError');
+ popover.hidePopover(); // Calling hidePopover on a disconnected popover should not throw.
+ assert_throws_dom("InvalidStateError",() => popover.togglePopover(),'Calling hidePopover on a disconnected popover should throw InvalidStateError');
+ parent.appendChild(popover);
+}
+
+function assertNotAPopover(nonPopover) {
+ // If the non-popover element nonetheless has a 'popover' attribute, it should
+ // be invisible. Otherwise, it should be visible.
+ const expectVisible = !nonPopover.hasAttribute('popover');
+ assertPopoverVisibility(nonPopover, /*isPopover*/false, expectVisible, 'A non-popover should start out visible');
+ assert_throws_dom("NotSupportedError",() => nonPopover.showPopover(),'Calling showPopover on a non-popover should throw NotSupported');
+ assertPopoverVisibility(nonPopover, /*isPopover*/false, expectVisible, 'Calling showPopover on a non-popover should leave it visible');
+ assert_throws_dom("NotSupportedError",() => nonPopover.hidePopover(),'Calling hidePopover on a non-popover should throw NotSupported');
+ assertPopoverVisibility(nonPopover, /*isPopover*/false, expectVisible, 'Calling hidePopover on a non-popover should leave it visible');
+ assert_throws_dom("NotSupportedError",() => nonPopover.togglePopover(),'Calling togglePopover on a non-popover should throw NotSupported');
+ assertPopoverVisibility(nonPopover, /*isPopover*/false, expectVisible, 'Calling togglePopover on a non-popover should leave it visible');
+}
diff --git a/testing/web-platform/tests/html/semantics/popovers/togglePopover.html b/testing/web-platform/tests/html/semantics/popovers/togglePopover.html
new file mode 100644
index 0000000000..388617fccc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/togglePopover.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/9043">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id=popover popover=auto>popover</div>
+<div id=popover2 popover=auto>popover</div>
+
+<script>
+test(() => {
+ assert_false(popover.matches(':popover-open'),
+ 'Popover should be closed when the test starts.');
+
+ assert_true(popover.togglePopover(),
+ 'togglePopover() should return true.');
+ assert_true(popover.matches(':popover-open'),
+ 'togglePopover() should open the popover.');
+
+ assert_true(popover.togglePopover(/*force=*/true),
+ 'togglePopover(true) should return true.');
+ assert_true(popover.matches(':popover-open'),
+ 'togglePopover(true) should open the popover.');
+
+ assert_false(popover.togglePopover(),
+ 'togglePopover() should return false.');
+ assert_false(popover.matches(':popover-open'),
+ 'togglePopover() should close the popover.');
+
+ assert_false(popover.togglePopover(/*force=*/false),
+ 'togglePopover(false) should return false.');
+ assert_false(popover.matches(':popover-open'),
+ 'togglePopover(false) should close the popover.');
+}, 'togglePopover should toggle the popover and return true or false as specified.');
+
+test(() => {
+ const popover = document.getElementById('popover2');
+ popover.addEventListener('beforetoggle', event => event.preventDefault(), {once: true});
+ assert_false(popover.togglePopover(/*force=*/true),
+ 'togglePopover(true) should return false when the popover does not get opened.');
+ assert_false(popover.matches(':popover-open'));
+
+ // We could also add a test for the return value of togglePopover(false),
+ // but every way to prevent that from hiding the popover also throws an
+ // exception, so the return value is not testable.
+}, `togglePopover's return value should reflect what the end state is, not just the force parameter.`);
+
+test(() => {
+ const popover = document.createElement('div');
+ document.body.appendChild(popover);
+
+ assert_throws_dom('NotSupportedError', () => popover.togglePopover(),
+ 'togglePopover() should throw an exception when the element has no popover attribute.');
+ assert_throws_dom('NotSupportedError', () => popover.togglePopover(true),
+ 'togglePopover(true) should throw an exception when the element has no popover attribute.');
+ assert_throws_dom('NotSupportedError', () => popover.togglePopover(false),
+ 'togglePopover(false) should throw an exception when the element has no popover attribute.');
+
+ popover.setAttribute('popover', 'auto');
+ popover.remove();
+
+ assert_throws_dom('InvalidStateError', () => popover.togglePopover(),
+ 'togglePopover() should throw an exception when the element is disconnected.');
+ assert_throws_dom('InvalidStateError', () => popover.togglePopover(true),
+ 'togglePopover(true) should throw an exception when the element is disconnected.');
+ assert_throws_dom('InvalidStateError', () => popover.togglePopover(false),
+ 'togglePopover(false) should throw an exception when the element is disconnected.');
+
+ document.body.appendChild(popover);
+ // togglePopover(false) should not throw just because the popover is already hidden.
+ popover.togglePopover(false);
+ popover.showPopover();
+ // togglePopover(true) should not throw just because the popover is already showing.
+ popover.togglePopover(true);
+
+ // cleanup
+ popover.hidePopover();
+}, 'togglePopover should throw an exception when there is no popover attribute.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/popovers/toggleevent-interface.html b/testing/web-platform/tests/html/semantics/popovers/toggleevent-interface.html
new file mode 100644
index 0000000000..09ce3f3b56
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/toggleevent-interface.html
@@ -0,0 +1,208 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+test(function() {
+ var event = new ToggleEvent("");
+ assert_true(event instanceof window.ToggleEvent);
+}, "the event is an instance of ToggleEvent");
+
+test(function() {
+ var event = new ToggleEvent("");
+ assert_true(event instanceof window.Event);
+}, "the event inherts from Event");
+
+test(function() {
+ assert_throws_js(TypeError, function() {
+ new ToggleEvent();
+ }, 'First argument (type) is required, so was expecting a TypeError.');
+}, 'Missing type argument');
+
+test(function() {
+ var event = new ToggleEvent("test");
+ assert_equals(event.type, "test");
+}, "type argument is string");
+
+test(function() {
+ var event = new ToggleEvent(null);
+ assert_equals(event.type, "null");
+}, "type argument is null");
+
+test(function() {
+ var event = new ToggleEvent(undefined);
+ assert_equals(event.type, "undefined");
+}, "event type set to undefined");
+
+test(function() {
+ var event = new ToggleEvent("test");
+ assert_equals(event.oldState, "");
+}, "oldState has default value of empty string");
+
+test(function() {
+ var event = new ToggleEvent("test");
+ assert_readonly(event, "oldState", "readonly attribute value");
+}, "oldState is readonly");
+
+test(function() {
+ var event = new ToggleEvent("test");
+ assert_equals(event.newState, "");
+}, "newState has default value of empty string");
+
+test(function() {
+ var event = new ToggleEvent("test");
+ assert_readonly(event, "newState", "readonly attribute value");
+}, "newState is readonly");
+
+test(function() {
+ var event = new ToggleEvent("test", null);
+ assert_equals(event.oldState, "");
+ assert_equals(event.newState, "");
+}, "ToggleEventInit argument is null");
+
+test(function() {
+ var event = new ToggleEvent("test", undefined);
+ assert_equals(event.oldState, "");
+ assert_equals(event.newState, "");
+}, "ToggleEventInit argument is undefined");
+
+test(function() {
+ var event = new ToggleEvent("test", {});
+ assert_equals(event.oldState, "");
+ assert_equals(event.newState, "");
+}, "ToggleEventInit argument is empty dictionary");
+
+test(function() {
+ var event = new ToggleEvent("test", {oldState: "sample"});
+ assert_equals(event.oldState, "sample");
+}, "oldState set to 'sample'");
+
+test(function() {
+ var event = new ToggleEvent("test", {oldState: undefined});
+ assert_equals(event.oldState, "");
+}, "oldState set to undefined");
+
+test(function() {
+ var event = new ToggleEvent("test", {oldState: null});
+ assert_equals(event.oldState, "null");
+}, "oldState set to null");
+
+test(function() {
+ var event = new ToggleEvent("test", {oldState: false});
+ assert_equals(event.oldState, "false");
+}, "oldState set to false");
+
+test(function() {
+ var event = new ToggleEvent("test", {oldState: true});
+ assert_equals(event.oldState, "true");
+}, "oldState set to true");
+
+test(function() {
+ var event = new ToggleEvent("test", {oldState: 0.5});
+ assert_equals(event.oldState, "0.5");
+}, "oldState set to a number");
+
+test(function() {
+ var event = new ToggleEvent("test", {oldState: []});
+ assert_equals(event.oldState, "");
+}, "oldState set to []");
+
+test(function() {
+ var event = new ToggleEvent("test", {oldState: [1, 2, 3]});
+ assert_equals(event.oldState, "1,2,3");
+}, "oldState set to [1, 2, 3]");
+
+test(function() {
+ var event = new ToggleEvent("test", {oldState: {sample: 0.5}});
+ assert_equals(event.oldState, "[object Object]");
+}, "oldState set to an object");
+
+test(function() {
+ var event = new ToggleEvent("test",
+ {oldState: {valueOf: function () { return 'sample'; }}});
+ assert_equals(event.oldState, "[object Object]");
+}, "oldState set to an object with a valueOf function");
+
+test(function() {
+ var eventInit = {oldState: "sample",newState: "sample2"};
+ var event = new ToggleEvent("test", eventInit);
+ assert_equals(event.oldState, "sample");
+ assert_equals(event.newState, "sample2");
+}, "ToggleEventInit properties set value");
+
+test(function() {
+ var eventInit = {oldState: "open",newState: "closed"};
+ var event = new ToggleEvent("beforetoggle", eventInit);
+ assert_equals(event.oldState, "open");
+ assert_equals(event.newState, "closed");
+}, "ToggleEventInit properties set value 2");
+
+test(function() {
+ var eventInit = {oldState: "closed",newState: "open"};
+ var event = new ToggleEvent("toggle", eventInit);
+ assert_equals(event.oldState, "closed");
+ assert_equals(event.newState, "open");
+}, "ToggleEventInit properties set value 3");
+
+test(function() {
+ var eventInit = {oldState: "open",newState: "open"};
+ var event = new ToggleEvent("beforetoggle", eventInit);
+ assert_equals(event.oldState, "open");
+ assert_equals(event.newState, "open");
+}, "ToggleEventInit properties set value 4");
+
+test(function() {
+ var event = new ToggleEvent("test", {newState: "sample"});
+ assert_equals(event.newState, "sample");
+}, "newState set to 'sample'");
+
+test(function() {
+ var event = new ToggleEvent("test", {newState: undefined});
+ assert_equals(event.newState, "");
+}, "newState set to undefined");
+
+test(function() {
+ var event = new ToggleEvent("test", {newState: null});
+ assert_equals(event.newState, "null");
+}, "newState set to null");
+
+test(function() {
+ var event = new ToggleEvent("test", {newState: false});
+ assert_equals(event.newState, "false");
+}, "newState set to false");
+
+test(function() {
+ var event = new ToggleEvent("test", {newState: true});
+ assert_equals(event.newState, "true");
+}, "newState set to true");
+
+test(function() {
+ var event = new ToggleEvent("test", {newState: 0.5});
+ assert_equals(event.newState, "0.5");
+}, "newState set to a number");
+
+test(function() {
+ var event = new ToggleEvent("test", {newState: []});
+ assert_equals(event.newState, "");
+}, "newState set to []");
+
+test(function() {
+ var event = new ToggleEvent("test", {newState: [1, 2, 3]});
+ assert_equals(event.newState, "1,2,3");
+}, "newState set to [1, 2, 3]");
+
+test(function() {
+ var event = new ToggleEvent("test", {newState: {sample: 0.5}});
+ assert_equals(event.newState, "[object Object]");
+}, "newState set to an object");
+
+test(function() {
+ var event = new ToggleEvent("test",
+ {newState: {valueOf: function () { return 'sample'; }}});
+ assert_equals(event.newState, "[object Object]");
+}, "newState set to an object with a valueOf function");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/rellist-feature-detection.html b/testing/web-platform/tests/html/semantics/rellist-feature-detection.html
new file mode 100644
index 0000000000..6866de1906
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/rellist-feature-detection.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<title>Test relList attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+let link_support_table = {};
+// https://html.spec.whatwg.org/multipage/links.html#linkTypes
+link_support_table['link'] = {
+ supported : ['modulepreload', 'preload', 'preconnect', 'dns-prefetch',
+ 'stylesheet', 'icon', 'alternate', 'prefetch',
+ 'prerender', 'next', 'manifest', 'apple-touch-icon',
+ 'apple-touch-icon-precomposed', 'canonical'],
+ unsupported : ['author', 'bookmark', 'external', 'help', 'import',
+ 'license', 'nofollow', 'pingback', 'prev', 'search',
+ 'tag', 'noreferrer', 'noopener']
+};
+link_support_table['a'] = {
+ supported : ['noreferrer', 'noopener', 'opener'],
+ unsupported : ['author', 'bookmark', 'external', 'help', 'license',
+ 'nofollow', 'pingback', 'prev', 'search', 'tag',
+ 'modulepreload', 'preload', 'preconnect', 'dns-prefetch',
+ 'stylesheet', 'import', 'icon', 'alternate', 'prefetch',
+ 'prerender', 'next', 'manifest', 'apple-touch-icon',
+ 'apple-touch-icon-precomposed', 'canonical']
+};
+link_support_table['area'] = link_support_table['a'];
+link_support_table['form'] = link_support_table['a'];
+
+function test_rellist(tag_name) {
+ const rel_table = link_support_table[tag_name];
+ const element = document.createElement(tag_name);
+ let tag = element.tagName;
+ // Test that setting rel is also setting relList, for both
+ // valid and invalid values.
+ element.rel = 'whatever';
+ assert_true(element.relList.contains('whatever'), 'tag = ' + tag + ', setting rel must work');
+ element.rel = 'prefetch';
+ assert_true(element.relList.contains('prefetch'), 'tag = ' + tag + ', setting rel must work');
+ // Test that add() works.
+ element.relList.add('preloadwhatever');
+ assert_equals(element.rel, 'prefetch preloadwhatever', 'tag = ' + tag + ', add must work');
+ assert_true(element.relList.contains('preloadwhatever'), 'tag = ' + tag + ', add must work');
+ // Test that remove() works.
+ element.relList.remove('preloadwhatever');
+ assert_equals(element.rel, 'prefetch', 'tag = ' + tag + ', remove must work');
+ assert_false(element.relList.contains('preloadwhatever'), 'tag = ' + tag + ', remove must work');
+ // Test that toggle() works.
+ element.relList.toggle('prefetch', false);
+ assert_equals(element.rel, '', 'tag = ' + tag + ', toggle must work');
+ element.relList.toggle('prefetch', true);
+ assert_equals(element.rel, 'prefetch', 'tag = ' + tag + ', toggle must work');
+ // Test that replace() works.
+ element.relList.replace('prefetch', 'first');
+ assert_equals(element.rel, 'first', 'tag = ' + tag + ', replace must work');
+ // Test that indexed item getter works.
+ element.relList.add('second');
+ assert_equals(element.relList.length, 2, 'tag = ' + tag + ', relList length must be correct');
+ assert_equals(element.relList[0], 'first', 'tag = ' + tag + ', relList indexed item must work');
+ assert_equals(element.relList[1], 'second', 'tag = ' + tag + ', relList indexed item must work');
+ // Test that relList is [SameObject].
+ let savedRelList = element.relList;
+ element.rel = 'something';
+ assert_equals(element.relList, savedRelList, 'tag = ' + tag + ', SameObject must work');
+
+ // Test that supports() is returning true for valid values
+ // and false for invalid ones.
+ let supported = rel_table['supported'];
+ for (let link_type in supported) {
+ assert_true(element.relList.supports(supported[link_type]), 'tag = ' + tag + ', link type = ' + supported[link_type] + ' must be supported');
+ assert_true(element.relList.supports(supported[link_type].toUpperCase()), 'tag = ' + tag + ', link type = ' + supported[link_type].toUpperCase() + ' must be supported');
+ }
+ let unsupported = rel_table['unsupported'];
+ for (let link_type in unsupported) {
+ assert_false(element.relList.supports(unsupported[link_type]), 'tag = ' + tag + ', link type = ' + unsupported[link_type] + ' must be unsupported');
+ }
+}
+
+['link', 'a', 'area', 'form'].forEach(tag_name => {
+ test(
+ () => test_rellist(tag_name),
+ `Make sure that relList based feature detection is working for <${tag_name}>`
+ );
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/META.yml b/testing/web-platform/tests/html/semantics/scripting-1/META.yml
new file mode 100644
index 0000000000..0f3cf59653
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/META.yml
@@ -0,0 +1,2 @@
+suggested_reviewers:
+ - domenic
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-noscript-element/non-html-noscript.html b/testing/web-platform/tests/html/semantics/scripting-1/the-noscript-element/non-html-noscript.html
new file mode 100644
index 0000000000..2f85d1d47d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-noscript-element/non-html-noscript.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>noscript rules don't apply to non-html elements</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-noscript-element">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1470150">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+test(function() {
+ let non_html_noscript = document.createElementNS("http://www.w3.org/2000/svg", "noscript");
+ document.body.appendChild(non_html_noscript);
+ assert_not_equals(getComputedStyle(non_html_noscript).display, "none");
+}, "Non-HTML <noscript> element shouldn't be undisplayed by a UA rule");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/README.md b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/README.md
new file mode 100644
index 0000000000..ab4a4820af
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/README.md
@@ -0,0 +1,16 @@
+# Script tests
+
+## Import attributes & JSON/CSS modules
+
+The import attributes proposal changed the keyword from `assert` to `with`, after that it was already been implemented and shipped in some browsers. Thus, there are some implementations that only support the `assert` syntax and others that only support the `with` syntax.
+
+For this reason, the import attributes, JSON modules and CSS modules are duplicated to use both keywords:
+| `with` keyword | `assert` keyword |
+|:----------------------|:---------------------------|
+| `./import-attributes` | `./import-assertions` |
+| `./json-module` | `./json-module-assertions` |
+| `./css-module` | `./css-module-assertions` |
+
+All changes in one folder should be reflected in the corresponding folder, because the two syntaxes have the same semantics.
+
+The web compatibility of removing the `assert` keyword is being investigated. If it will be deemed feasible, it will be removed from the proposal and the `assert` tests can be deleted.
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_001.htm b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_001.htm
new file mode 100644
index 0000000000..370152683b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_001.htm
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Async property on a dynamically-created script is true by default</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <meta description="This test checks the Async property on a dynamically-created script element. By default it should be true." />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-script-async"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script type="text/javascript">
+ test(function() {assert_true(document.createElement("script").async)}, "Async property on a dynamically-created script is true by default");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_002.htm b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_002.htm
new file mode 100644
index 0000000000..e1850ff6e1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_002.htm
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Changes to the 'async' attribute are reflected in the async property</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <meta description="This test ensures changes to the 'async' attribute are reflected in the async property." />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-script-async"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script type="text/javascript">
+ test(function() {
+ var s = document.createElement("script");
+ s.async = false;
+ s.setAttribute('async', ''); /*Should change s.async to true*/
+ assert_true(s.async)
+ }, "Test 'async' attribute are reflected in the async property with setAttribute");
+
+ test(function() {
+ var s = document.createElement("script");
+ s.async = false;
+ s.setAttribute('async', ''); /*Should change s.async to true*/
+ s.removeAttribute('async'); /*Should change s.async to false*/
+ assert_false(s.async)
+ }, "Test 'async' attribute are reflected in the async property with removeAttribute");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_003.htm b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_003.htm
new file mode 100644
index 0000000000..c6d84f9a87
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_003.htm
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>An async script does not block the parser while downloading</title>
+ <meta name="timeout" content="long">
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <meta description="This test ensures an async script does not block the parser while downloading." />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-script-async"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script type="text/javascript">
+ var t = async_test("An async script does not block the parser while downloading");
+
+ function timeout()
+ {
+ t.step(function(){ assert_equals(document.getElementById("testresult").innerHTML, "21")});
+ t.done();
+ }
+
+ var timer = setTimeout(timeout, 4000);
+
+ function log(text)
+ {
+ var textNode = document.createTextNode(text);
+ document.getElementById("testresult").appendChild(textNode);
+ }
+ </script>
+
+ <span id="testresult"></span>
+
+ <script src="log.py?sec=3&id=1" async></script>
+ <script>
+ log('2');
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_004.htm b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_004.htm
new file mode 100644
index 0000000000..5d9df099b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_004.htm
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>An async script executes as soon as possible after a download is complete</title>
+ <meta name="timeout" content="long">
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <meta description="This test ensures an async script executes as soon as possible after a download is complete." />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-script-async"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script type="text/javascript">
+ var t = async_test("async script executes as soon as possible after a download is complete");
+
+ function timeout()
+ {
+ t.step(function(){ assert_equals(document.getElementById("testresult").innerHTML, "21")});
+ t.done();
+ }
+
+ var timer = setTimeout(timeout, 4000);
+
+ function log(text)
+ {
+ var textNode = document.createTextNode(text);
+ document.getElementById("testresult").appendChild(textNode);
+ }
+ </script>
+
+ <span id="testresult"></span>
+
+ <script src="log.py?sec=3&id=1" async></script>
+ <script src="log.py?sec=1&id=2" async></script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_005.htm b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_005.htm
new file mode 100644
index 0000000000..03f9adeb67
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_005.htm
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>A script element with both async and defer set should execute asynchronously</title>
+ <meta name="timeout" content="long">
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <meta description="This test ensures a script element with both async and defer set should execute asynchronously." />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-script-async"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script type="text/javascript">
+ var t = async_test("A script element with both async and defer set should execute asynchronously");
+
+ function timeout()
+ {
+ t.step(function(){
+ var actual = document.getElementById("testresult").innerHTML;
+ assert_in_array(actual, ["2134", "2341"]);
+ });
+ t.done();
+ }
+
+ var timer = setTimeout(timeout, 5000);
+
+ function log(text)
+ {
+ var textNode = document.createTextNode(text);
+ document.getElementById("testresult").appendChild(textNode);
+ }
+ </script>
+
+ <span id="testresult"></span>
+
+ <script type="text/javascript" src="log.py?sec=1&id=1" defer async></script>
+ <script type="text/javascript">log('2');</script>
+ <script type="text/javascript" src="log.py?sec=3&id=3"></script>
+ <script type="text/javascript">log('4');</script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_006.htm b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_006.htm
new file mode 100644
index 0000000000..ed3d8b22c6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_006.htm
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>A dynamically created external script executes asynchronously</title>
+ <meta name="timeout" content="long">
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <meta description="This test ensures a dynamically created external script executes asynchronously." />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#force-async"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script type="text/javascript">
+ var t = async_test("dynamically created external script executes asynchronously");
+
+ function timeout()
+ {
+ t.step(function(){ assert_equals(document.getElementById("testresult").innerHTML, "321")});
+ t.done();
+ }
+
+ var timer = setTimeout(timeout, 4000);
+
+ function log(text)
+ {
+ var textNode = document.createTextNode(text);
+ document.getElementById("testresult").appendChild(textNode);
+ }
+ </script>
+
+ <span id="testresult"></span>
+ <script type="text/javascript">
+ var one = document.createElement("script");
+ one.src="log.py?sec=3&id=1";
+ document.head.appendChild(one);
+
+ var two = document.createElement("script");
+ two.src="log.py?sec=1&id=2";
+ document.head.appendChild(two);
+
+ log('3');
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_007.htm b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_007.htm
new file mode 100644
index 0000000000..6c4ae29e06
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_007.htm
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Ordered async script execution when script.async == false</title>
+ <meta name="timeout" content="long">
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <meta description="This test ensures Ordered async script execution when script.async == false" />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#script-processing-src-sync"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script type="text/javascript">
+ var t = async_test("Ordered async script execution when script.async == false");
+
+ function timeout()
+ {
+ t.step(function(){ assert_equals(document.getElementById("testresult").innerHTML, "312")});
+ t.done();
+ }
+
+ var timer = setTimeout(timeout, 8000);
+
+ function log(text)
+ {
+ var textNode = document.createTextNode(text);
+ document.getElementById("testresult").appendChild(textNode);
+ }
+ </script>
+
+ <span id="testresult"></span>
+ <script type="text/javascript">
+ var one = document.createElement("script");
+ one.src="log.py?sec=3&id=1";
+ one.async = false;
+ document.head.appendChild(one);
+
+ var two = document.createElement("script");
+ two.src="log.py?sec=1&id=2";
+ two.async = false;
+ document.head.appendChild(two);
+ </script>
+ <script type="text/javascript">
+ log('3');
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_008.htm b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_008.htm
new file mode 100644
index 0000000000..73529cc318
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_008.htm
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Async script element execution delays the window's load event</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <meta description="This test ensures an async script element's execution delays the window's load event." />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#delay-the-load-event"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script type="text/javascript">
+ var t = async_test("Async script element execution delays the window's load event");
+
+ function timeout()
+ {
+ t.step(function(){ assert_equals(document.getElementById("testresult").innerHTML, "213")});
+ t.done();
+ }
+
+ var timer = setTimeout(timeout, 8000);
+
+ function log(text)
+ {
+ var textNode = document.createTextNode(text);
+ document.getElementById("testresult").appendChild(textNode);
+ }
+ </script>
+
+ <span id="testresult"></span>
+ <script type="text/javascript">
+ window.addEventListener("load", function() {
+ log("3");
+ timeout();
+ }, false);
+
+ var s1 = document.createElement("script");
+ s1.src = "log.py?sec=2&id=1";
+ document.head.appendChild(s1);
+ </script>
+ <script type="text/javascript">
+ log('2');
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_009.htm b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_009.htm
new file mode 100644
index 0000000000..307aa46412
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_009.htm
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Document.write() silently fails from an Async script</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <meta description="This test ensures Document.write() silently fails from an Async script." />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script type="text/javascript">
+ var t = async_test("Document.write() silently fails from an Async script");
+
+ var log = t.step_func(function() {
+ document.write("<span id='writtenText'></span>");
+ assert_equals(null, document.getElementById('writtenText'));
+ t.done();
+ });
+ </script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script type="text/javascript" src="log.py?sec=1&id=1" async></script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_010.htm b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_010.htm
new file mode 100644
index 0000000000..c54defc00d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_010.htm
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Removing an async script before execution</title>
+ <meta name="timeout" content="long">
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <meta description="This test ensures that an async script still executes if it is removed from a markup before the download is complete. The other two scripts that come after it in insertion order should execute as well." />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script type="text/javascript">
+ var t = async_test("Removing an async script before execution");
+
+ function timeout()
+ {
+ t.step(function(){ assert_equals(document.getElementById("testresult").innerHTML, "4123")});
+ t.done();
+ }
+
+ var timer = setTimeout(timeout, 8000);
+
+ function log(text)
+ {
+ var textNode = document.createTextNode(text);
+ document.getElementById("testresult").appendChild(textNode);
+ }
+ </script>
+ </head>
+ <body>
+ <div id=log></div>
+ <span id="testresult"></span>
+ <script type="text/javascript">
+ var s1 = document.createElement("script");
+ s1.src="log.py?sec=2&id=1";
+ s1.async = false;
+ document.body.appendChild(s1);
+
+ var s2 = document.createElement("script");
+ s2.src="log.py?sec=1&id=2";
+ s2.async = false;
+ document.body.appendChild(s2);
+
+ var s3 = document.createElement("script");
+ s3.id = "s3";
+ s3.src="log.py?sec=0&id=3";
+ s3.async = false;
+ document.body.appendChild(s3);
+
+ //Remove s1 (Should still execute)
+ document.body.removeChild(s1);
+ </script>
+ <script type="text/javascript">log('4');</script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_011.htm b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_011.htm
new file mode 100644
index 0000000000..d80e463cee
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/async_011.htm
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>An empty parser-inserted script element should return async=true</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <meta description="An empty parser-inserted script element should return async=true." />
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script></script>
+ <script type="text/javascript">
+ test(function() { assert_true(document.getElementsByTagName("script")[2].async)}, "An empty parser-inserted script element should return async=true");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py
new file mode 100644
index 0000000000..b315afed56
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py
@@ -0,0 +1,4 @@
+def main(request, response):
+ headers = [(b"Content-Type", b"text/javascript"), (b"Cache-control", b"public, max-age=100")]
+ body = u"throw('fox');"
+ return 200, headers, body
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/charset-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/charset-2.html
new file mode 100644
index 0000000000..aaa2960aaf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/charset-2.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="windows-1250">
+<title>CSS modules: UTF-8 decoding</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script type="module">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module-assertions/resources/utf-8.css" assert { type: "css"};
+ test(() => {
+ assert_equals(styleSheet.rules[0].style.content, "\"�湿�\"");
+ }, "CSS module should be loaded as utf-8 even though document's encoding is windows-1250");
+</script>
+<script type="module">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module-assertions/resources/windows-1250.css&ct=text/css%3Bcharset=windows-1250" assert { type: "css"};
+ test(() => {
+ assert_not_equals(styleSheet.rules[0].style.content, "\"�湿�\"",
+ 'Should be decoded as UTF-8');
+ }, "CSS module should be loaded as utf-8 even if it is encoded in windows-1250 and served with a windows-1250 charset response header, and this document's encoding is windows-1250");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/charset-bom.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/charset-bom.html
new file mode 100644
index 0000000000..e26ee08d31
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/charset-bom.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>CSS Module scripts should ignore BOMs and always use UTF-8</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="module">
+ import utf8BOMSheet from './resources/bom-utf-8.css' assert { type: 'css' };
+ test(function() {
+ assert_equals(utf8BOMSheet.rules[0].selectorText, 'div', 'No UTF-8 BOM expected in selector');
+ }, 'UTF-8 BOM should be stripped when decoding JSON module script');
+
+ import utf16BEBOMSheet from './resources/bom-utf-16be.css' assert { type: 'css' };
+ test(function() {
+ assert_equals(utf16BEBOMSheet.rules[0].selectorText, '\ufffd\ufffd\ufffdd\ufffdi\ufffdv\ufffd \ufffd', 'Expected UTF-8 decoded selectorText with 0xfffd replacement characters');
+ }, 'UTF-16BE BOM should be ignored, so CSS module should be UTF-8 decoded');
+
+ import utf16LEBOMSheet from './resources/bom-utf-16le.css' assert { type: 'css' };
+ test(function() {
+ assert_equals(utf16LEBOMSheet.rules[0].selectorText, '\ufffd\ufffdd\ufffdi\ufffdv\ufffd \ufffd', 'Expected UTF-8 decoded selectorText with 0xfffd replacement characters');
+ }, 'UTF-16LE BOM should be ignored, so CSS module should be UTF-8 decoded');
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/charset.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/charset.html
new file mode 100644
index 0000000000..2127ec7280
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/charset.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CSS modules: UTF-8 decoding</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script type="module" onerror="unreachable()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module-assertions/resources/utf-8.css&ct=text/css%3Bcharset=utf-8" assert { type: "css"};
+ test(() => {
+ assert_equals(styleSheet.rules[0].style.content, "\"śćążź\"");
+ }, "CSS module should be loaded as utf-8 when charset=utf8 is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module-assertions/resources/utf-8.css&ct=text/css%3Bcharset=shift-jis" assert { type: "css"};
+ test(() => {
+ assert_equals(styleSheet.rules[0].style.content, "\"śćążź\"");
+ }, "CSS module should be loaded as utf-8 when charset=shift-jis is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module-assertions/resources/utf-8.css&ct=text/css%3Bcharset=windows-1252" assert { type: "css"};
+ test(() => {
+ assert_equals(styleSheet.rules[0].style.content, "\"śćążź\"");
+ }, "CSS module should be loaded as utf-8 when charset=windows-1252 is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module-assertions/resources/utf-8.css&ct=text/css%3Bcharset=utf-7" assert { type: "css"};;
+ test(() => {
+ assert_equals(styleSheet.rules[0].style.content, "\"śćążź\"");
+ }, "CSS module should be loaded as utf-8 when charset=utf-7 is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module-assertions/resources/windows-1250.css&ct=text/css%3Bcharset=windows-1250" assert { type: "css"};
+ test(() => {
+ assert_not_equals(styleSheet.rules[0].style.content, "\"śćążź\"",
+ 'Should be decoded as UTF-8');
+ }, "CSS module should be loaded as utf-8 even if it is encoded in windows-1250 and served with a windows-1250 charset response header");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/content-type-checking.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/content-type-checking.html
new file mode 100644
index 0000000000..3ade863305
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/content-type-checking.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CSS modules: Content-Type</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+function check(t, styleSheet) {
+ t.step(() => {
+ assert_equals(styleSheet.rules[0].cssText, "#test { background-color: rgb(255, 0, 0); }");
+ t.done();
+ });
+}
+const t1 = async_test("text/css");
+const t2 = async_test("application/css");
+const t3 = async_test("text/html+css");
+const t4 = async_test("text/css;boundary=something");
+const t5 = async_test("text/css;foo=bar");
+</script>
+<script type="module" onerror="t1.unreached_func()()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module-assertions/resources/basic.css&ct=text/css" assert { type: "css"};
+ check(t1, styleSheet);
+</script>
+<script type="module" onerror="t2.step_func_done()()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module-assertions/resources/basic.css&ct=application/css" assert { type: "css"};
+ t2.unreached_func("Should not have loaded with MIME type application/css")();
+</script>
+<script type="module" onerror="t3.step_func_done()()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module-assertions/resources/basic.css&ct=text/html+css" assert { type: "css"};
+ t3.unreached_func("Should not have loaded with MIME type text/html+css")();
+</script>
+<script type="module" onerror="t4.unreached_func()()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module-assertions/resources/basic.css&ct=text/css;boundary=something" assert { type: "css"};
+ check(t4, styleSheet);
+</script>
+<script type="module" onerror="t5.unreached_func()()">
+import styleSheet from "../serve-with-content-type.py?fn=css-module-assertions/resources/basic.css&ct=text/css;foo=bar" assert { type: "css"};
+check(t5, styleSheet);
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/cors-crossorigin-requests.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/cors-crossorigin-requests.html
new file mode 100644
index 0000000000..b9de562b72
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/cors-crossorigin-requests.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+<head>
+ <title>css-module-assertions-crossorigin</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <h1>css-module-assertions-crossorigin</h1>
+ <iframe id="import-WithCORS" src="resources/crossorigin-import-with-cors.sub.html"></iframe>
+ <iframe id="import-NoCORS" src="resources/crossorigin-import-without-cors.sub.html"></iframe>
+ <iframe id="import-parseerror-WithCors" src="resources/crossorigin-import-parse-error-with-cors.sub.html"></iframe>
+ <script>
+
+ var tests = [
+ { "obj": async_test("Imported CSS module, cross-origin with CORS"), "id": "import-WithCORS", "expected": "imported CSS: #test { background-color: rgb(255, 0, 0); }" },
+ { "obj": async_test("Imported CSS module, cross-origin, missing CORS ACAO header"), "id": "import-NoCORS", "expected": "error" },
+ { "obj": async_test("Imported CSS module with parse error, cross-origin, with CORS"), "id": "import-parseerror-WithCors", "expected": "imported CSS rules count: 0" },
+ ];
+
+ window.addEventListener("load", function () {
+ tests.forEach(function (test) {
+ var target = document.getElementById(test.id);
+ test.obj.step(function () {
+ assert_equals(target.contentDocument._log, test.expected, "Unexpected _log value");
+ });
+ test.obj.done();
+ });
+ });
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/credentials.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/credentials.sub.html
new file mode 100644
index 0000000000..8c2f0f8073
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/credentials.sub.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+document.cookie = 'milk=1';
+
+const setCookiePromise = fetch(
+ 'http://{{domains[www2]}}:{{ports[http][0]}}/cookies/resources/set-cookie.py?name=milk&path=/html/semantics/scripting-1/the-script-element/css-module-assertions/',
+ {
+ mode: 'no-cors',
+ credentials: 'include',
+ });
+
+const windowLoadPromise = new Promise(resolve => {
+ window.addEventListener('load', () => {
+ resolve();
+ });
+});
+
+promise_test(t => {
+ const iframe = document.createElement('iframe');
+
+ return Promise.all([setCookiePromise, windowLoadPromise]).then(() => {
+ const messagePromise = new Promise(resolve => {
+ window.addEventListener('message', event => {
+ resolve();
+ });
+ });
+
+ iframe.src = 'resources/credentials-iframe.sub.html';
+ document.body.appendChild(iframe);
+
+ return messagePromise;
+ }).then(() => {
+ const w = iframe.contentWindow;
+
+ assert_equals(w.sameOriginNoneDescendant, true,
+ 'Descendant CSS modules should be loaded with the credentials when the crossOrigin attribute is not specified and the target is same-origin');
+ assert_equals(w.sameOriginAnonymousDescendant, true,
+ 'Descendant CSS modules should be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is same-origin');
+ assert_equals(w.sameOriginUseCredentialsDescendant, true,
+ 'Descendant CSS modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is same-origin');
+ assert_equals(w.crossOriginNoneDescendant, false,
+ 'Descendant CSS modules should not be loaded with the credentials when the crossOrigin attribute is not specified and the target is cross-origin');
+ assert_equals(w.crossOriginAnonymousDescendant, false,
+ 'Descendant CSS modules should not be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is cross-origin');
+ assert_equals(w.crossOriginUseCredentialsDescendant, true,
+ 'Descendant CSS modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is cross-origin');
+});
+}, 'CSS Modules should be loaded with or without the credentials based on the same-origin-ness and the crossOrigin attribute');
+</script>
+<body>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/css-module-worker-test.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/css-module-worker-test.html
new file mode 100644
index 0000000000..7ff672da6a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/css-module-worker-test.html
@@ -0,0 +1,54 @@
+<!doctype html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/utils.js"></script>
+</head>
+
+<body>
+ <script>
+ setup({allow_uncaught_exception: true});
+ promise_test(function (test) {
+ const uuid = token();
+ const worker = new Worker(`./resources/worker.sub.js?key=${uuid}`, {
+ type: "module"
+ });
+ return new Promise((resolve, reject) => {
+ worker.addEventListener("error", resolve);
+ worker.addEventListener("message", reject);
+ }).then(async () => {
+ const fetchResponse = await fetch(`./resources/record-fetch.py?key=${uuid}&action=getCount`);
+ const fetchData = await fetchResponse.json();
+ assert_equals(fetchData.count, 0, "Shouldn't have tried fetching CSS module in worker");
+ });
+ }, "A static import CSS Module within a web worker should not load and should not attempt to fetch the module.");
+
+ promise_test(function (test) {
+ const uuid = token();
+ const worker = new Worker(`./resources/worker-dynamic-import.sub.js?key=${uuid}`, {
+ type: "module"
+ });
+
+ return new Promise(resolve => {
+ worker.addEventListener("message", resolve);
+ }).then(async (event) => {
+ assert_equals(event.data, "NOT LOADED");
+ const fetchResponse = await fetch(`./resources/record-fetch.py?key=${uuid}&action=getCount`);
+ const fetchData = await fetchResponse.json();
+ assert_equals(fetchData.count, 0, "Shouldn't have tried fetching CSS module in worker");
+ });
+ }, "A dynamic import CSS Module within a web worker should not load and should not attempt to fetch the module.");
+
+ promise_test(function (test) {
+ const worker = new Worker("./resources/basic.css", {
+ type: "module"
+ });
+ return new Promise(resolve => {
+ worker.onerror = resolve;
+ });
+ }, "An attempt to load a CSS module as a worker should fail.");
+
+ </script>
+
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/import-css-module-basic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/import-css-module-basic.html
new file mode 100644
index 0000000000..4ea1790aab
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/import-css-module-basic.html
@@ -0,0 +1,83 @@
+<!doctype html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id="test">I am a test div.</div>
+ <div id="test2">I am a test div.</div>
+ <div id="test3">I am a test div.</div>
+ <div id="test3b">I am a test div.</div>
+ <div id="test4">I am a test div.</div>
+ <div id="test4b">I am a test div.</div>
+ <script>
+ window.errorCount = 0;
+ window.onerror = (errorMsg, url, lineNumber, column, errorObj) => {
+ window.errorCount++;
+ };
+ </script>
+ <script type="module" onerror="unreachable()">
+ import sheet from "./resources/basic.css" assert { type: "css" };
+ test(() => {
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
+ assert_equals(getComputedStyle(document.querySelector('#test'))
+ .backgroundColor, "rgb(255, 0, 0)", "CSS module import should succeed");
+ }, "A CSS Module should load");
+ </script>
+ <script type="module" onerror="unreachable()">
+ import sheet from "./resources/basic-large.css" assert { type: "css" };
+ test(() => {
+ // This tests potential streaming compilation of modules in
+ // Chromium that is triggered only for large (32>KiB) files in older
+ // versions.
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
+ assert_equals(getComputedStyle(document.querySelector('#test2'))
+ .backgroundColor, "rgb(255, 0, 0)",
+ "CSS module import should succeed");
+ }, "A large CSS Module should load");
+ </script>
+ <script type="module" onerror="unreachable()">
+ import sheet from "./resources/bad-import.css" assert { type: "css" };
+ test(() => {
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
+ assert_equals(window.errorCount, 0);
+ assert_equals(sheet.cssRules.length, 1, "Parser should skip @import rule");
+ assert_equals(getComputedStyle(document.querySelector('#test3b'))
+ .backgroundColor, "rgba(0, 0, 0, 0)",
+ "CSS module @import should not succeed");
+ assert_equals(getComputedStyle(document.querySelector('#test3'))
+ .backgroundColor, "rgb(0, 255, 0)",
+ "Rule after @import should still be applied");
+ }, "An @import CSS Module should not load, but should not throw an exception");
+ </script>
+ <script type="module" onerror="unreachable()">
+ import sheet from "./resources/malformed.css" assert { type: "css" };
+ test(() => {
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
+ assert_equals(window.errorCount, 0);
+ assert_equals(sheet.cssRules.length, 1, "Import of malformed CSS should succeed and rules after the parse error should still be parsed");
+ assert_equals(getComputedStyle(document.querySelector('#test4'))
+ .backgroundColor, "rgba(0, 0, 0, 0)",
+ "Malformed CSS rule should not be applied");
+ assert_equals(getComputedStyle(document.querySelector('#test4b'))
+ .backgroundColor, "rgb(0, 255, 0)",
+ "Parsing should recover and rules after malformed rules should be applied");
+ }, "A parse error should not prevent subsequent rules from being included in a CSS module");
+ </script>
+ <script type="module">
+ promise_test(function (test) {
+ const iframe = document.createElement("iframe");
+ iframe.src = "resources/css-module-without-assertion-iframe.html";
+ return new Promise(resolve => {
+ iframe.onload = resolve;
+ document.body.appendChild(iframe);
+ }).then(event => {
+ assert_equals(iframe.contentDocument.window_onerror, undefined);
+ assert_equals(iframe.contentDocument.script_onerror.type, "error");
+ assert_equals(getComputedStyle(iframe.contentDocument.querySelector('#test'))
+ .backgroundColor, "rgba(0, 0, 0, 0)",
+ "CSS module without type assertion should result in a fetch error");
+ });
+ }, "CSS module without type assertion should result in a fetch error");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/import-css-module-dynamic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/import-css-module-dynamic.html
new file mode 100644
index 0000000000..13967858cb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/import-css-module-dynamic.html
@@ -0,0 +1,23 @@
+<!doctype html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <script>
+ promise_test(async function (test) {
+ const css_module = await import("./resources/basic.css", { assert: { type: "css" }});
+ assert_true(css_module.default instanceof CSSStyleSheet);
+ assert_equals(css_module.default.cssRules[0].cssText,
+ "#test { background-color: rgb(255, 0, 0); }");
+ }, "Load a CSS module with dynamic import()");
+
+ promise_test(function (test) {
+ return promise_rejects_js(test, TypeError,
+ import("./resources/basic.css"),
+ "Attempting to import() a CSS module without a type assertion should fail");
+ }, "Ensure that loading a CSS module with dymnamic import() fails without a type assertion");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/integrity.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/integrity.html
new file mode 100644
index 0000000000..1dd0dad470
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/integrity.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>&lt;script> integrity="" with CSS modules</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+window.matchesLog = [];
+window.matchesEvents = [];
+
+window.mismatchesLog = [];
+window.mismatchesEvents = [];
+</script>
+<script type="module" src="resources/integrity-matches.js" integrity="sha384-xvbfmg9iJFHqmCoOS4VNMCwnFPPxEoIlW1Ojzl+fgEd+Wf8Pyez+SMWue+KNovjA" onload="window.matchesEvents.push('load');" onerror="window.matchesEvents.push('error')"></script>
+<script type="module" src="resources/integrity-mismatches.js" integrity="sha384-doesnotmatch" onload="window.mismatchesEvents.push('load');" onerror="window.mismatchesEvents.push('error')"></script>
+
+<script type="module">
+test(() => {
+ assert_array_equals(window.matchesLog, ["integrity-matches,css:#test { background-color: rgb(255, 0, 0); }"], "The module and its dependency must have executed");
+ assert_array_equals(window.matchesEvents, ["load"], "The load event must have fired");
+}, "The integrity attribute must be verified on the top-level of a module loading a CSS module and allow it to execute when it matches");
+
+test(() => {
+ assert_array_equals(window.mismatchesLog, [], "The module and its dependency must not have executed");
+ assert_array_equals(window.mismatchesEvents, ["error"], "The error event must have fired");
+}, "The integrity attribute must be verified on the top-level of a module loading a CSS module and not allow it to execute when there's a mismatch");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/load-error-events.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/load-error-events.html
new file mode 100644
index 0000000000..3457452c93
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/load-error-events.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<head>
+<title>load/error events for CSS modules</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/load-error-events-helpers.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+</head>
+<script>
+ "use strict";
+
+ var test1_load = event_test('inline, 200, parser-inserted', false, false);
+ var test1_error = event_test('inline, 404, parser-inserted', false, true);
+
+ var test2_load = event_test('src, 200, parser-inserted', true, false);
+ var test2_error = event_test('src, 404, parser-inserted', false, true);
+
+ var test3_dynamic_load = event_test('src, 200, not parser-inserted', true, false);
+ var test3_dynamic_error = event_test('src, 404, not parser-inserted', false, true);
+
+ var test4_dynamic_load = event_test('inline, 200, not parser-inserted', false, false);
+ var test4_dynamic_error = event_test('inline, 404, not parser-inserted', false, true);
+
+ var script3_dynamic_load = document.createElement('script');
+ script3_dynamic_load.setAttribute('type', 'module');
+ script3_dynamic_load.onload = () => onLoad(test3_dynamic_load);
+ script3_dynamic_load.onerror = () => onError(test3_dynamic_load);
+ script3_dynamic_load.src = "./resources/load-error-events.py?test=test3_dynamic_load";
+ document.head.appendChild(script3_dynamic_load);
+
+ var script3_dynamic_error = document.createElement('script');
+ script3_dynamic_error.setAttribute('type', 'module');
+ script3_dynamic_error.onload = () => onLoad(test3_dynamic_error);
+ script3_dynamic_error.onerror = () => onError(test3_dynamic_error);
+ script3_dynamic_error.src = "./resources/load-error-events.py?test=test3_dynamic_error";
+ document.head.appendChild(script3_dynamic_error);
+
+ var script4_dynamic_load = document.createElement('script');
+ script4_dynamic_load.setAttribute('type', 'module');
+ script4_dynamic_load.onload = () => onLoad(test4_dynamic_load);
+ script4_dynamic_load.onerror = () => onError(test4_dynamic_load);
+ script4_dynamic_load.async = true;
+ script4_dynamic_load.appendChild(document.createTextNode(`
+ import "./resources/basic.css" assert { type: "css" };
+ onExecute(test4_dynamic_load);`
+ ));
+ document.head.appendChild(script4_dynamic_load);
+
+ var script4_dynamic_error = document.createElement('script');
+ script4_dynamic_error.setAttribute('type', 'module');
+ script4_dynamic_error.onload = () => onLoad(test4_dynamic_error);
+ script4_dynamic_error.onerror = () => onError(test4_dynamic_error);
+ script4_dynamic_error.async = true;
+ script4_dynamic_error.appendChild(document.createTextNode(`import "./not_found.css" assert { type: "css" };`));
+ document.head.appendChild(script4_dynamic_error);
+</script>
+<script onload="onLoad(test1_load);" onerror="onError(test1_load);" type="module">
+ import "./resources/basic.css" assert { type: "css"};
+ onExecute(test1_load);
+</script>
+<script onload="onLoad(test1_error);" onerror="onError(test1_error);" type="module">
+ import "./not_found.css" assert { type: "css"};
+ onExecute(test1_error);
+</script>
+<script src="./resources/load-error-events.py?test=test2_load" onload="onLoad(test2_load);" onerror="onError(test2_load);" type="module"></script>
+<script src="./resources/load-error-events.py?test=test2_error" onload="onLoad(test2_error);" onerror="onError(test2_error);" type="module"></script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/referrer-policies.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/referrer-policies.sub.html
new file mode 100644
index 0000000000..f078437e09
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/referrer-policies.sub.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Referrers with CSS module requests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+ // "name" parameter is necessary for bypassing the module map.
+ import referrerSame from "./resources/referrer-checker.py?name=sameNoReferrerPolicy" assert { type: "css"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/referrer-checker.py?name=remoteNoReferrerPolicy" assert { type: "css"};
+
+ const origin = (new URL(location.href)).origin + "/";
+ const originUrl = location.href;
+
+ test(t => {
+ assert_equals(
+ referrerSame.rules[0].style.content, '"' + originUrl + '"',
+ "Referrer URL should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the default referrer policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.rules[0].style.content, '"' + origin + '"',
+ "Referrer origin should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the default referrer policy.");
+</script>
+<script type="module" referrerpolicy="origin">
+ import referrerSame from "./resources/referrer-checker.py?name=sameReferrerPolicyOrigin" assert { type: "css"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/referrer-checker.py?name=remoteReferrerPolicyOrigin" assert { type: "css"};
+
+ const origin = (new URL(location.href)).origin + "/";
+
+ test(t => {
+ assert_equals(
+ referrerSame.rules[0].style.content, '"' + origin + '"',
+ "Referrer origin should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the origin policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.rules[0].style.content, '"' + origin + '"',
+ "Referrer origin should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the origin policy.");
+
+</script>
+<script type="module" referrerpolicy="no-referrer">
+ import referrerSame from "./resources/referrer-checker.py?name=sameReferrerPolicyNoReferrer" assert { type: "css"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/referrer-checker.py?name=remoteReferrerPolicyNoReferrer" assert { type: "css"};
+
+ test(t => {
+ assert_equals(
+ referrerSame.rules[0].style.content, '""',
+ "No referrer should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the no-referrer policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.rules[0].style.content, '""',
+ "No referrer should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the no-referrer policy.");
+
+</script>
+<script type="module" referrerpolicy="unsafe-url">
+ import referrerSame from "./resources/referrer-checker.py?name=sameNoReferrerPolicyUnsafeUrl" assert { type: "css"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/referrer-checker.py?name=remoteNoReferrerPolicyUnsafeUrl" assert { type: "css"};
+
+ const originUrl = location.href;
+
+ test(t => {
+ assert_equals(
+ referrerSame.rules[0].style.content, '"' + originUrl + '"',
+ "Referrer URL should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the unsafe-url referrer policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.rules[0].style.content, '"' + originUrl + '"',
+ "Referrer URL should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the unsafe-url referrer policy.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/relative-urls.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/relative-urls.html
new file mode 100644
index 0000000000..22971d28d9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/relative-urls.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<head>
+ <title>Test resolution of relative URL in CSS module</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id="target"></div>
+ <script type="module">
+ import styleSheet from "./resources/load-relative-url.css" assert { type: "css"};
+ test(() => {
+ const target = document.querySelector("#target");
+ document.adoptedStyleSheets = [ styleSheet ];
+ let backgroundStyle = window.getComputedStyle(target).background;
+ assert_not_equals(backgroundStyle.indexOf("css-module-assertions/resources/image.png"), -1);
+ }, "A relative URL in a CSS module should be resolved relative to the CSS file's URL, not the importing document's URL");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/atImported.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/atImported.css
new file mode 100644
index 0000000000..8629a846d1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/atImported.css
@@ -0,0 +1,3 @@
+#test3b {
+ background-color: #FF0000;
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bad-import.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bad-import.css
new file mode 100644
index 0000000000..a6e1a0f395
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bad-import.css
@@ -0,0 +1,4 @@
+@import "atImported.css";
+#test3 {
+ background-color:#00FF00;
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/basic-large.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/basic-large.css
new file mode 100644
index 0000000000..555ab70d2e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/basic-large.css
@@ -0,0 +1,7 @@
+#test2 {
+ background-color:red;
+}
+
+#test:before {
+ content: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/basic.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/basic.css
new file mode 100644
index 0000000000..e034ed9ac7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/basic.css
@@ -0,0 +1,3 @@
+#test {
+ background-color: #FF0000;
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bom-utf-16be.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bom-utf-16be.css
new file mode 100644
index 0000000000..9e17902a1d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bom-utf-16be.css
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bom-utf-16le.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bom-utf-16le.css
new file mode 100644
index 0000000000..ef90843d8e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bom-utf-16le.css
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bom-utf-8.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bom-utf-8.css
new file mode 100644
index 0000000000..5cf81232b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/bom-utf-8.css
@@ -0,0 +1 @@
+div { background-color: blue; } \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/credentials-iframe.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/credentials-iframe.sub.html
new file mode 100644
index 0000000000..cf1d621ce3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/credentials-iframe.sub.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<script type="module">
+ import styleSheet from "./cross-origin.py?id=sameOriginNoneDescendant&origin=http://{{host}}:{{ports[http][0]}}" assert { type: "css" };
+ window.sameOriginNoneDescendant = (styleSheet.cssRules[0].cssText.indexOf(".requestHadCookies") !== -1);
+</script>
+<script type="module" crossOrigin="anonymous">
+ import styleSheet from "./cross-origin.py?id=sameOriginAnonymousDescendant&origin=http://{{host}}:{{ports[http][0]}}" assert { type: "css" };
+ window.sameOriginAnonymousDescendant = (styleSheet.cssRules[0].cssText.indexOf(".requestHadCookies") !== -1);
+</script>
+<script type="module" crossOrigin="use-credentials">
+ import styleSheet from "./cross-origin.py?id=sameOriginUseCredentialsDescendant&origin=http://{{host}}:{{ports[http][0]}}" assert { type: "css" };
+ window.sameOriginUseCredentialsDescendant = (styleSheet.cssRules[0].cssText.indexOf(".requestHadCookies") !== -1);
+</script>
+<script type="module">
+ import styleSheet from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/cross-origin.py?id=crossOriginNoneDescendant&origin=http://{{host}}:{{ports[http][0]}}" assert { type: "css" };
+ window.crossOriginNoneDescendant = (styleSheet.cssRules[0].cssText.indexOf(".requestHadCookies") !== -1);
+</script>
+<script type="module" crossOrigin="anonymous">
+ import styleSheet from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/cross-origin.py?id=crossOriginAnonymousDescendant&origin=http://{{host}}:{{ports[http][0]}}" assert { type: "css" };
+ window.crossOriginAnonymousDescendant = (styleSheet.cssRules[0].cssText.indexOf(".requestHadCookies") !== -1);
+</script>
+<script type="module" crossOrigin="use-credentials">
+ import styleSheet from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/cross-origin.py?id=crossOriginUseCredentialsDescendant&origin=http://{{host}}:{{ports[http][0]}}" assert { type: "css" };
+ window.crossOriginUseCredentialsDescendant = (styleSheet.cssRules[0].cssText.indexOf(".requestHadCookies") !== -1);
+</script>
+
+<script type="text/javascript">
+window.addEventListener('load', event => {
+ window.parent.postMessage({}, '*');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/cross-origin.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/cross-origin.py
new file mode 100644
index 0000000000..d744fc9514
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/cross-origin.py
@@ -0,0 +1,17 @@
+def main(request, response):
+
+ headers = [
+ (b"Content-Type", b"text/css"),
+ (b"Access-Control-Allow-Origin", request.GET.first(b"origin")),
+ (b"Access-Control-Allow-Credentials", b"true")
+ ]
+
+ milk = request.cookies.first(b"milk", None)
+
+ # Send back
+ if milk is None:
+ return headers, u'.requestDidNotHaveCookies { }'
+ elif milk.value == b"1":
+ return headers, u'.requestHadCookies { }'
+
+ return headers, u'.requestDidNotHaveCookies { }'
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/crossorigin-import-parse-error-with-cors.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/crossorigin-import-parse-error-with-cors.sub.html
new file mode 100644
index 0000000000..3afb8edeb1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/crossorigin-import-parse-error-with-cors.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>css-module-assertions-import-cross-domain-parse-error-WithCORS</title>
+ <script src="../../module/crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>css-module-assertions-import-cross-domain-parse-error-WithCORS</h1>
+ <script type="module" crossorigin>
+ import styleSheet from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/parse-error.css?pipe=header(Access-Control-Allow-Origin,*)" assert { type: "css" };
+ // Push an event to the log indicating that the script was executed.
+ document._log.push(`imported CSS rules count: ${styleSheet.rules.length}`);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/crossorigin-import-with-cors.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/crossorigin-import-with-cors.sub.html
new file mode 100644
index 0000000000..f8754c2a56
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/crossorigin-import-with-cors.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>css-module-assertions-import-cross-domain-WithCORS</title>
+ <script src="../../module/crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>css-module-assertions-import-cross-domain-WithCORS</h1>
+ <script type="module" crossorigin>
+ import styleSheet from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/basic.css?pipe=header(Access-Control-Allow-Origin,*)" assert { type: "css" };
+ // Push an event to the log indicating that the script was executed.
+ document._log.push(`imported CSS: ${styleSheet.rules[0].cssText}`);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/crossorigin-import-without-cors.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/crossorigin-import-without-cors.sub.html
new file mode 100644
index 0000000000..ab70b73573
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/crossorigin-import-without-cors.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>css-module-assertions-import-cross-domain-NoCORS</title>
+ <script src="../../module/crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>css-module-assertions-import-cross-domain-NoCORS</h1>
+ <script type="module" onerror="document._log.push('error');">
+ import styleSheet from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/basic.css" assert { type: "css" };
+ // Push an event to the log indicating that the script was executed.
+ document._log.push(`imported CSS: ${styleSheet.rules[0].cssText}`);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/css-module-without-assertion-iframe.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/css-module-without-assertion-iframe.html
new file mode 100644
index 0000000000..3d1be841ce
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/css-module-without-assertion-iframe.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<body>
+ <script>
+ window.onerror = function (errorMsg, url, lineNumber, column, errorObj)
+ {
+ document.window_onerror = errorObj.name;
+ return true;
+ };
+
+ function scriptErrorHandler(e) {
+ document.script_onerror = e;
+ }
+ </script>
+ <script type="module" onerror="scriptErrorHandler(event)">
+ import v from "./basic.css";
+ document.adoptedStyleSheets = [v];
+ </script>
+
+ <div id="test">
+ I am a test div.
+ </div>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/integrity-matches.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/integrity-matches.js
new file mode 100644
index 0000000000..95be445311
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/integrity-matches.js
@@ -0,0 +1,2 @@
+import styleSheet from "./basic.css" assert { type: "css" };
+window.matchesLog.push(`integrity-matches,css:${styleSheet.cssRules[0].cssText}`);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/integrity-mismatches.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/integrity-mismatches.js
new file mode 100644
index 0000000000..af6fc24de4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/integrity-mismatches.js
@@ -0,0 +1,2 @@
+import styleSheet from "./basic.css" assert { type: "css" };
+window.matchesLog.push(`integrity-mismatches,css:${styleSheet.cssRules[0].cssText}`);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/load-error-events.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/load-error-events.py
new file mode 100644
index 0000000000..b61b1ca834
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/load-error-events.py
@@ -0,0 +1,14 @@
+import re
+
+def main(request, response):
+ headers = [(b"Content-Type", b"text/javascript")]
+ test = request.GET.first(b'test')
+ assert(re.match(b'^[a-zA-Z0-9_]+$', test))
+
+ status = 200
+ if test.find(b'_load') >= 0:
+ content = b'import "./basic.css" assert { type: "css"}; %s.executed = true;' % test
+ else:
+ content = b'import "./not_found.css" assert { type: "css"}; %s.test.step(function() { assert_unreached("404 script should not be executed"); });' % test
+
+ return status, headers, content
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/load-relative-url.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/load-relative-url.css
new file mode 100644
index 0000000000..27f2987610
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/load-relative-url.css
@@ -0,0 +1,5 @@
+
+#target {
+ /* image.png doesn't exist, but that's irrelevant to the test. */
+ background: url('./image.png');
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/malformed.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/malformed.css
new file mode 100644
index 0000000000..28819bfdf5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/malformed.css
@@ -0,0 +1,7 @@
+#test4 } {
+ background-color: #FF0000;
+}
+
+#test4b {
+ background-color: #00FF00;
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/parse-error.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/parse-error.css
new file mode 100644
index 0000000000..2bee3ff996
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/parse-error.css
@@ -0,0 +1,2 @@
+div /* Opening bracket skipped intentionally. */ }
+
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/record-fetch.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/record-fetch.py
new file mode 100644
index 0000000000..4928cb4acb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/record-fetch.py
@@ -0,0 +1,20 @@
+def main(request, response):
+ try:
+ stash_key = request.GET.first(b"key")
+ action = request.GET.first(b"action")
+
+ run_count = request.server.stash.take(stash_key)
+ if not run_count:
+ run_count = 0
+
+ if action == b"incCount":
+ request.server.stash.put(stash_key, run_count + 1)
+ response.headers.set(b"Content-Type", b"text/css")
+ response.content = b'#test { background-color: #FF0000; }'
+ elif action == b"getCount":
+ response.headers.set(b"Content-Type", b"text/json")
+ response.content = b'{"count": %d }' % run_count
+ else:
+ response.set_error(400, u"Invalid action")
+ except:
+ response.set_error(400, u"Not enough parameters")
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/referrer-checker.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/referrer-checker.py
new file mode 100644
index 0000000000..c1eaed8e46
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/referrer-checker.py
@@ -0,0 +1,7 @@
+def main(request, response):
+ referrer = request.headers.get(b"referer", b"")
+ response_headers = [(b"Content-Type", b"text/css"),
+ (b"Access-Control-Allow-Origin", b"*")]
+ # Put the referrer in a CSS rule that can be read by the importer through CSSOM
+ return (200, response_headers,
+ b'.referrer { content: "' + referrer + b'" }')
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/utf-8.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/utf-8.css
new file mode 100644
index 0000000000..0a8b46656f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/utf-8.css
@@ -0,0 +1,3 @@
+#test {
+ content: "śćążź";
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/windows-1250.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/windows-1250.css
new file mode 100644
index 0000000000..9beac4d618
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/windows-1250.css
@@ -0,0 +1,3 @@
+#test {
+ content: "�湿�";
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/worker-dynamic-import.sub.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/worker-dynamic-import.sub.js
new file mode 100644
index 0000000000..791bd7d3f9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/worker-dynamic-import.sub.js
@@ -0,0 +1,3 @@
+import("./record-fetch.py?key={{GET[key]}}&action=incCount", { assert: { type: "css" } })
+ .then(() => postMessage("LOADED"))
+ .catch(e => postMessage("NOT LOADED")); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/worker.sub.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/worker.sub.js
new file mode 100644
index 0000000000..ffee312d21
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/resources/worker.sub.js
@@ -0,0 +1,2 @@
+import "./record-fetch.py?key={{GET[key]}}&action=incCount" assert { type: "css" };
+postMessage("Unexpectedly loaded"); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/script-element-css-src.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/script-element-css-src.html
new file mode 100644
index 0000000000..231d02db47
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module-assertions/script-element-css-src.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>&lt;script&gt; with CSS src</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ const test_load = async_test(
+ "Test that <script> doesn't load when the src is CSS.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(log, ["error"]);
+ }));
+</script>
+<script type="module" src="./resources/basic.css" onload="t.unreached_func('CSS src should fail to load')" onerror="log.push('error')"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/charset-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/charset-2.html
new file mode 100644
index 0000000000..fb25b1ffb3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/charset-2.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="windows-1250">
+<title>CSS modules: UTF-8 decoding</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script type="module">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module/resources/utf-8.css" with { type: "css"};
+ test(() => {
+ assert_equals(styleSheet.rules[0].style.content, "\"�湿�\"");
+ }, "CSS module should be loaded as utf-8 even though document's encoding is windows-1250");
+</script>
+<script type="module">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module/resources/windows-1250.css&ct=text/css%3Bcharset=windows-1250" with { type: "css"};
+ test(() => {
+ assert_not_equals(styleSheet.rules[0].style.content, "\"�湿�\"",
+ 'Should be decoded as UTF-8');
+ }, "CSS module should be loaded as utf-8 even if it is encoded in windows-1250 and served with a windows-1250 charset response header, and this document's encoding is windows-1250");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/charset-bom.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/charset-bom.html
new file mode 100644
index 0000000000..113ae63ea4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/charset-bom.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>CSS Module scripts should ignore BOMs and always use UTF-8</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="module">
+ import utf8BOMSheet from './resources/bom-utf-8.css' with { type: 'css' };
+ test(function() {
+ assert_equals(utf8BOMSheet.rules[0].selectorText, 'div', 'No UTF-8 BOM expected in selector');
+ }, 'UTF-8 BOM should be stripped when decoding JSON module script');
+
+ import utf16BEBOMSheet from './resources/bom-utf-16be.css' with { type: 'css' };
+ test(function() {
+ assert_equals(utf16BEBOMSheet.rules[0].selectorText, '\ufffd\ufffd\ufffdd\ufffdi\ufffdv\ufffd \ufffd', 'Expected UTF-8 decoded selectorText with 0xfffd replacement characters');
+ }, 'UTF-16BE BOM should be ignored, so CSS module should be UTF-8 decoded');
+
+ import utf16LEBOMSheet from './resources/bom-utf-16le.css' with { type: 'css' };
+ test(function() {
+ assert_equals(utf16LEBOMSheet.rules[0].selectorText, '\ufffd\ufffdd\ufffdi\ufffdv\ufffd \ufffd', 'Expected UTF-8 decoded selectorText with 0xfffd replacement characters');
+ }, 'UTF-16LE BOM should be ignored, so CSS module should be UTF-8 decoded');
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/charset.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/charset.html
new file mode 100644
index 0000000000..e7010c2f57
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/charset.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CSS modules: UTF-8 decoding</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script type="module" onerror="unreachable()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module/resources/utf-8.css&ct=text/css%3Bcharset=utf-8" with { type: "css"};
+ test(() => {
+ assert_equals(styleSheet.rules[0].style.content, "\"śćążź\"");
+ }, "CSS module should be loaded as utf-8 when charset=utf8 is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module/resources/utf-8.css&ct=text/css%3Bcharset=shift-jis" with { type: "css"};
+ test(() => {
+ assert_equals(styleSheet.rules[0].style.content, "\"śćążź\"");
+ }, "CSS module should be loaded as utf-8 when charset=shift-jis is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module/resources/utf-8.css&ct=text/css%3Bcharset=windows-1252" with { type: "css"};
+ test(() => {
+ assert_equals(styleSheet.rules[0].style.content, "\"śćążź\"");
+ }, "CSS module should be loaded as utf-8 when charset=windows-1252 is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module/resources/utf-8.css&ct=text/css%3Bcharset=utf-7" with { type: "css"};;
+ test(() => {
+ assert_equals(styleSheet.rules[0].style.content, "\"śćążź\"");
+ }, "CSS module should be loaded as utf-8 when charset=utf-7 is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module/resources/windows-1250.css&ct=text/css%3Bcharset=windows-1250" with { type: "css"};
+ test(() => {
+ assert_not_equals(styleSheet.rules[0].style.content, "\"śćążź\"",
+ 'Should be decoded as UTF-8');
+ }, "CSS module should be loaded as utf-8 even if it is encoded in windows-1250 and served with a windows-1250 charset response header");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/content-type-checking.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/content-type-checking.html
new file mode 100644
index 0000000000..5be0d50c38
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/content-type-checking.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CSS modules: Content-Type</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+function check(t, styleSheet) {
+ t.step(() => {
+ assert_equals(styleSheet.rules[0].cssText, "#test { background-color: rgb(255, 0, 0); }");
+ t.done();
+ });
+}
+const t1 = async_test("text/css");
+const t2 = async_test("application/css");
+const t3 = async_test("text/html+css");
+const t4 = async_test("text/css;boundary=something");
+const t5 = async_test("text/css;foo=bar");
+</script>
+<script type="module" onerror="t1.unreached_func()()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module/resources/basic.css&ct=text/css" with { type: "css"};
+ check(t1, styleSheet);
+</script>
+<script type="module" onerror="t2.step_func_done()()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module/resources/basic.css&ct=application/css" with { type: "css"};
+ t2.unreached_func("Should not have loaded with MIME type application/css")();
+</script>
+<script type="module" onerror="t3.step_func_done()()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module/resources/basic.css&ct=text/html+css" with { type: "css"};
+ t3.unreached_func("Should not have loaded with MIME type text/html+css")();
+</script>
+<script type="module" onerror="t4.unreached_func()()">
+ import styleSheet from "../serve-with-content-type.py?fn=css-module/resources/basic.css&ct=text/css;boundary=something" with { type: "css"};
+ check(t4, styleSheet);
+</script>
+<script type="module" onerror="t5.unreached_func()()">
+import styleSheet from "../serve-with-content-type.py?fn=css-module/resources/basic.css&ct=text/css;foo=bar" with { type: "css"};
+check(t5, styleSheet);
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/cors-crossorigin-requests.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/cors-crossorigin-requests.html
new file mode 100644
index 0000000000..e699ef927e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/cors-crossorigin-requests.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+<head>
+ <title>css-module-crossorigin</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <h1>css-module-crossorigin</h1>
+ <iframe id="import-WithCORS" src="resources/crossorigin-import-with-cors.sub.html"></iframe>
+ <iframe id="import-NoCORS" src="resources/crossorigin-import-without-cors.sub.html"></iframe>
+ <iframe id="import-parseerror-WithCors" src="resources/crossorigin-import-parse-error-with-cors.sub.html"></iframe>
+ <script>
+
+ var tests = [
+ { "obj": async_test("Imported CSS module, cross-origin with CORS"), "id": "import-WithCORS", "expected": "imported CSS: #test { background-color: rgb(255, 0, 0); }" },
+ { "obj": async_test("Imported CSS module, cross-origin, missing CORS ACAO header"), "id": "import-NoCORS", "expected": "error" },
+ { "obj": async_test("Imported CSS module with parse error, cross-origin, with CORS"), "id": "import-parseerror-WithCors", "expected": "imported CSS rules count: 0" },
+ ];
+
+ window.addEventListener("load", function () {
+ tests.forEach(function (test) {
+ var target = document.getElementById(test.id);
+ test.obj.step(function () {
+ assert_equals(target.contentDocument._log, test.expected, "Unexpected _log value");
+ });
+ test.obj.done();
+ });
+ });
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/credentials.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/credentials.sub.html
new file mode 100644
index 0000000000..0da573dad2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/credentials.sub.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+document.cookie = 'milk=1';
+
+const setCookiePromise = fetch(
+ 'http://{{domains[www2]}}:{{ports[http][0]}}/cookies/resources/set-cookie.py?name=milk&path=/html/semantics/scripting-1/the-script-element/css-module/',
+ {
+ mode: 'no-cors',
+ credentials: 'include',
+ });
+
+const windowLoadPromise = new Promise(resolve => {
+ window.addEventListener('load', () => {
+ resolve();
+ });
+});
+
+promise_test(t => {
+ const iframe = document.createElement('iframe');
+
+ return Promise.all([setCookiePromise, windowLoadPromise]).then(() => {
+ const messagePromise = new Promise(resolve => {
+ window.addEventListener('message', event => {
+ resolve();
+ });
+ });
+
+ iframe.src = 'resources/credentials-iframe.sub.html';
+ document.body.appendChild(iframe);
+
+ return messagePromise;
+ }).then(() => {
+ const w = iframe.contentWindow;
+
+ assert_equals(w.sameOriginNoneDescendant, true,
+ 'Descendant CSS modules should be loaded with the credentials when the crossOrigin attribute is not specified and the target is same-origin');
+ assert_equals(w.sameOriginAnonymousDescendant, true,
+ 'Descendant CSS modules should be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is same-origin');
+ assert_equals(w.sameOriginUseCredentialsDescendant, true,
+ 'Descendant CSS modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is same-origin');
+ assert_equals(w.crossOriginNoneDescendant, false,
+ 'Descendant CSS modules should not be loaded with the credentials when the crossOrigin attribute is not specified and the target is cross-origin');
+ assert_equals(w.crossOriginAnonymousDescendant, false,
+ 'Descendant CSS modules should not be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is cross-origin');
+ assert_equals(w.crossOriginUseCredentialsDescendant, true,
+ 'Descendant CSS modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is cross-origin');
+});
+}, 'CSS Modules should be loaded with or without the credentials based on the same-origin-ness and the crossOrigin attribute');
+</script>
+<body>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/css-module-worker-test.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/css-module-worker-test.html
new file mode 100644
index 0000000000..7ff672da6a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/css-module-worker-test.html
@@ -0,0 +1,54 @@
+<!doctype html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/utils.js"></script>
+</head>
+
+<body>
+ <script>
+ setup({allow_uncaught_exception: true});
+ promise_test(function (test) {
+ const uuid = token();
+ const worker = new Worker(`./resources/worker.sub.js?key=${uuid}`, {
+ type: "module"
+ });
+ return new Promise((resolve, reject) => {
+ worker.addEventListener("error", resolve);
+ worker.addEventListener("message", reject);
+ }).then(async () => {
+ const fetchResponse = await fetch(`./resources/record-fetch.py?key=${uuid}&action=getCount`);
+ const fetchData = await fetchResponse.json();
+ assert_equals(fetchData.count, 0, "Shouldn't have tried fetching CSS module in worker");
+ });
+ }, "A static import CSS Module within a web worker should not load and should not attempt to fetch the module.");
+
+ promise_test(function (test) {
+ const uuid = token();
+ const worker = new Worker(`./resources/worker-dynamic-import.sub.js?key=${uuid}`, {
+ type: "module"
+ });
+
+ return new Promise(resolve => {
+ worker.addEventListener("message", resolve);
+ }).then(async (event) => {
+ assert_equals(event.data, "NOT LOADED");
+ const fetchResponse = await fetch(`./resources/record-fetch.py?key=${uuid}&action=getCount`);
+ const fetchData = await fetchResponse.json();
+ assert_equals(fetchData.count, 0, "Shouldn't have tried fetching CSS module in worker");
+ });
+ }, "A dynamic import CSS Module within a web worker should not load and should not attempt to fetch the module.");
+
+ promise_test(function (test) {
+ const worker = new Worker("./resources/basic.css", {
+ type: "module"
+ });
+ return new Promise(resolve => {
+ worker.onerror = resolve;
+ });
+ }, "An attempt to load a CSS module as a worker should fail.");
+
+ </script>
+
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-basic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-basic.html
new file mode 100644
index 0000000000..8e9b84691a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-basic.html
@@ -0,0 +1,83 @@
+<!doctype html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id="test">I am a test div.</div>
+ <div id="test2">I am a test div.</div>
+ <div id="test3">I am a test div.</div>
+ <div id="test3b">I am a test div.</div>
+ <div id="test4">I am a test div.</div>
+ <div id="test4b">I am a test div.</div>
+ <script>
+ window.errorCount = 0;
+ window.onerror = (errorMsg, url, lineNumber, column, errorObj) => {
+ window.errorCount++;
+ };
+ </script>
+ <script type="module" onerror="unreachable()">
+ import sheet from "./resources/basic.css" with { type: "css" };
+ test(() => {
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
+ assert_equals(getComputedStyle(document.querySelector('#test'))
+ .backgroundColor, "rgb(255, 0, 0)", "CSS module import should succeed");
+ }, "A CSS Module should load");
+ </script>
+ <script type="module" onerror="unreachable()">
+ import sheet from "./resources/basic-large.css" with { type: "css" };
+ test(() => {
+ // This tests potential streaming compilation of modules in
+ // Chromium that is triggered only for large (32>KiB) files in older
+ // versions.
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
+ assert_equals(getComputedStyle(document.querySelector('#test2'))
+ .backgroundColor, "rgb(255, 0, 0)",
+ "CSS module import should succeed");
+ }, "A large CSS Module should load");
+ </script>
+ <script type="module" onerror="unreachable()">
+ import sheet from "./resources/bad-import.css" with { type: "css" };
+ test(() => {
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
+ assert_equals(window.errorCount, 0);
+ assert_equals(sheet.cssRules.length, 1, "Parser should skip @import rule");
+ assert_equals(getComputedStyle(document.querySelector('#test3b'))
+ .backgroundColor, "rgba(0, 0, 0, 0)",
+ "CSS module @import should not succeed");
+ assert_equals(getComputedStyle(document.querySelector('#test3'))
+ .backgroundColor, "rgb(0, 255, 0)",
+ "Rule after @import should still be applied");
+ }, "An @import CSS Module should not load, but should not throw an exception");
+ </script>
+ <script type="module" onerror="unreachable()">
+ import sheet from "./resources/malformed.css" with { type: "css" };
+ test(() => {
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
+ assert_equals(window.errorCount, 0);
+ assert_equals(sheet.cssRules.length, 1, "Import of malformed CSS should succeed and rules after the parse error should still be parsed");
+ assert_equals(getComputedStyle(document.querySelector('#test4'))
+ .backgroundColor, "rgba(0, 0, 0, 0)",
+ "Malformed CSS rule should not be applied");
+ assert_equals(getComputedStyle(document.querySelector('#test4b'))
+ .backgroundColor, "rgb(0, 255, 0)",
+ "Parsing should recover and rules after malformed rules should be applied");
+ }, "A parse error should not prevent subsequent rules from being included in a CSS module");
+ </script>
+ <script type="module">
+ promise_test(function (test) {
+ const iframe = document.createElement("iframe");
+ iframe.src = "resources/css-module-without-attribute-iframe.html";
+ return new Promise(resolve => {
+ iframe.onload = resolve;
+ document.body.appendChild(iframe);
+ }).then(event => {
+ assert_equals(iframe.contentDocument.window_onerror, undefined);
+ assert_equals(iframe.contentDocument.script_onerror.type, "error");
+ assert_equals(getComputedStyle(iframe.contentDocument.querySelector('#test'))
+ .backgroundColor, "rgba(0, 0, 0, 0)",
+ "CSS module without type attribute should result in a fetch error");
+ });
+ }, "CSS module without type attribute should result in a fetch error");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-dynamic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-dynamic.html
new file mode 100644
index 0000000000..5774a31cb2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-dynamic.html
@@ -0,0 +1,23 @@
+<!doctype html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <script>
+ promise_test(async function (test) {
+ const css_module = await import("./resources/basic.css", { with: { type: "css" }});
+ assert_true(css_module.default instanceof CSSStyleSheet);
+ assert_equals(css_module.default.cssRules[0].cssText,
+ "#test { background-color: rgb(255, 0, 0); }");
+ }, "Load a CSS module with dynamic import()");
+
+ promise_test(function (test) {
+ return promise_rejects_js(test, TypeError,
+ import("./resources/basic.css"),
+ "Attempting to import() a CSS module without a type attribute should fail");
+ }, "Ensure that loading a CSS module with dymnamic import() fails without a type attribute");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/integrity.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/integrity.html
new file mode 100644
index 0000000000..1dd0dad470
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/integrity.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>&lt;script> integrity="" with CSS modules</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+window.matchesLog = [];
+window.matchesEvents = [];
+
+window.mismatchesLog = [];
+window.mismatchesEvents = [];
+</script>
+<script type="module" src="resources/integrity-matches.js" integrity="sha384-xvbfmg9iJFHqmCoOS4VNMCwnFPPxEoIlW1Ojzl+fgEd+Wf8Pyez+SMWue+KNovjA" onload="window.matchesEvents.push('load');" onerror="window.matchesEvents.push('error')"></script>
+<script type="module" src="resources/integrity-mismatches.js" integrity="sha384-doesnotmatch" onload="window.mismatchesEvents.push('load');" onerror="window.mismatchesEvents.push('error')"></script>
+
+<script type="module">
+test(() => {
+ assert_array_equals(window.matchesLog, ["integrity-matches,css:#test { background-color: rgb(255, 0, 0); }"], "The module and its dependency must have executed");
+ assert_array_equals(window.matchesEvents, ["load"], "The load event must have fired");
+}, "The integrity attribute must be verified on the top-level of a module loading a CSS module and allow it to execute when it matches");
+
+test(() => {
+ assert_array_equals(window.mismatchesLog, [], "The module and its dependency must not have executed");
+ assert_array_equals(window.mismatchesEvents, ["error"], "The error event must have fired");
+}, "The integrity attribute must be verified on the top-level of a module loading a CSS module and not allow it to execute when there's a mismatch");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/load-error-events.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/load-error-events.html
new file mode 100644
index 0000000000..703a0734ff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/load-error-events.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<head>
+<title>load/error events for CSS modules</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/load-error-events-helpers.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+</head>
+<script>
+ "use strict";
+
+ var test1_load = event_test('inline, 200, parser-inserted', false, false);
+ var test1_error = event_test('inline, 404, parser-inserted', false, true);
+
+ var test2_load = event_test('src, 200, parser-inserted', true, false);
+ var test2_error = event_test('src, 404, parser-inserted', false, true);
+
+ var test3_dynamic_load = event_test('src, 200, not parser-inserted', true, false);
+ var test3_dynamic_error = event_test('src, 404, not parser-inserted', false, true);
+
+ var test4_dynamic_load = event_test('inline, 200, not parser-inserted', false, false);
+ var test4_dynamic_error = event_test('inline, 404, not parser-inserted', false, true);
+
+ var script3_dynamic_load = document.createElement('script');
+ script3_dynamic_load.setAttribute('type', 'module');
+ script3_dynamic_load.onload = () => onLoad(test3_dynamic_load);
+ script3_dynamic_load.onerror = () => onError(test3_dynamic_load);
+ script3_dynamic_load.src = "./resources/load-error-events.py?test=test3_dynamic_load";
+ document.head.appendChild(script3_dynamic_load);
+
+ var script3_dynamic_error = document.createElement('script');
+ script3_dynamic_error.setAttribute('type', 'module');
+ script3_dynamic_error.onload = () => onLoad(test3_dynamic_error);
+ script3_dynamic_error.onerror = () => onError(test3_dynamic_error);
+ script3_dynamic_error.src = "./resources/load-error-events.py?test=test3_dynamic_error";
+ document.head.appendChild(script3_dynamic_error);
+
+ var script4_dynamic_load = document.createElement('script');
+ script4_dynamic_load.setAttribute('type', 'module');
+ script4_dynamic_load.onload = () => onLoad(test4_dynamic_load);
+ script4_dynamic_load.onerror = () => onError(test4_dynamic_load);
+ script4_dynamic_load.async = true;
+ script4_dynamic_load.appendChild(document.createTextNode(`
+ import "./resources/basic.css" with { type: "css" };
+ onExecute(test4_dynamic_load);`
+ ));
+ document.head.appendChild(script4_dynamic_load);
+
+ var script4_dynamic_error = document.createElement('script');
+ script4_dynamic_error.setAttribute('type', 'module');
+ script4_dynamic_error.onload = () => onLoad(test4_dynamic_error);
+ script4_dynamic_error.onerror = () => onError(test4_dynamic_error);
+ script4_dynamic_error.async = true;
+ script4_dynamic_error.appendChild(document.createTextNode(`import "./not_found.css" with { type: "css" };`));
+ document.head.appendChild(script4_dynamic_error);
+</script>
+<script onload="onLoad(test1_load);" onerror="onError(test1_load);" type="module">
+ import "./resources/basic.css" with { type: "css"};
+ onExecute(test1_load);
+</script>
+<script onload="onLoad(test1_error);" onerror="onError(test1_error);" type="module">
+ import "./not_found.css" with { type: "css"};
+ onExecute(test1_error);
+</script>
+<script src="./resources/load-error-events.py?test=test2_load" onload="onLoad(test2_load);" onerror="onError(test2_load);" type="module"></script>
+<script src="./resources/load-error-events.py?test=test2_error" onload="onLoad(test2_error);" onerror="onError(test2_error);" type="module"></script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/referrer-policies.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/referrer-policies.sub.html
new file mode 100644
index 0000000000..a507ee52fc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/referrer-policies.sub.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Referrers with CSS module requests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+ // "name" parameter is necessary for bypassing the module map.
+ import referrerSame from "./resources/referrer-checker.py?name=sameNoReferrerPolicy" with { type: "css"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module/resources/referrer-checker.py?name=remoteNoReferrerPolicy" with { type: "css"};
+
+ const origin = (new URL(location.href)).origin + "/";
+ const originUrl = location.href;
+
+ test(t => {
+ assert_equals(
+ referrerSame.rules[0].style.content, '"' + originUrl + '"',
+ "Referrer URL should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the default referrer policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.rules[0].style.content, '"' + origin + '"',
+ "Referrer origin should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the default referrer policy.");
+</script>
+<script type="module" referrerpolicy="origin">
+ import referrerSame from "./resources/referrer-checker.py?name=sameReferrerPolicyOrigin" with { type: "css"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module/resources/referrer-checker.py?name=remoteReferrerPolicyOrigin" with { type: "css"};
+
+ const origin = (new URL(location.href)).origin + "/";
+
+ test(t => {
+ assert_equals(
+ referrerSame.rules[0].style.content, '"' + origin + '"',
+ "Referrer origin should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the origin policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.rules[0].style.content, '"' + origin + '"',
+ "Referrer origin should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the origin policy.");
+
+</script>
+<script type="module" referrerpolicy="no-referrer">
+ import referrerSame from "./resources/referrer-checker.py?name=sameReferrerPolicyNoReferrer" with { type: "css"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module/resources/referrer-checker.py?name=remoteReferrerPolicyNoReferrer" with { type: "css"};
+
+ test(t => {
+ assert_equals(
+ referrerSame.rules[0].style.content, '""',
+ "No referrer should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the no-referrer policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.rules[0].style.content, '""',
+ "No referrer should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the no-referrer policy.");
+
+</script>
+<script type="module" referrerpolicy="unsafe-url">
+ import referrerSame from "./resources/referrer-checker.py?name=sameNoReferrerPolicyUnsafeUrl" with { type: "css"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module/resources/referrer-checker.py?name=remoteNoReferrerPolicyUnsafeUrl" with { type: "css"};
+
+ const originUrl = location.href;
+
+ test(t => {
+ assert_equals(
+ referrerSame.rules[0].style.content, '"' + originUrl + '"',
+ "Referrer URL should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the unsafe-url referrer policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.rules[0].style.content, '"' + originUrl + '"',
+ "Referrer URL should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the unsafe-url referrer policy.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/relative-urls.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/relative-urls.html
new file mode 100644
index 0000000000..0aafb9aae5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/relative-urls.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<head>
+ <title>Test resolution of relative URL in CSS module</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id="target"></div>
+ <script type="module">
+ import styleSheet from "./resources/load-relative-url.css" with { type: "css"};
+ test(() => {
+ const target = document.querySelector("#target");
+ document.adoptedStyleSheets = [ styleSheet ];
+ let backgroundStyle = window.getComputedStyle(target).background;
+ assert_not_equals(backgroundStyle.indexOf("css-module/resources/image.png"), -1);
+ }, "A relative URL in a CSS module should be resolved relative to the CSS file's URL, not the importing document's URL");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/atImported.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/atImported.css
new file mode 100644
index 0000000000..8629a846d1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/atImported.css
@@ -0,0 +1,3 @@
+#test3b {
+ background-color: #FF0000;
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bad-import.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bad-import.css
new file mode 100644
index 0000000000..a6e1a0f395
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bad-import.css
@@ -0,0 +1,4 @@
+@import "atImported.css";
+#test3 {
+ background-color:#00FF00;
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/basic-large.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/basic-large.css
new file mode 100644
index 0000000000..555ab70d2e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/basic-large.css
@@ -0,0 +1,7 @@
+#test2 {
+ background-color:red;
+}
+
+#test:before {
+ content: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/basic.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/basic.css
new file mode 100644
index 0000000000..e034ed9ac7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/basic.css
@@ -0,0 +1,3 @@
+#test {
+ background-color: #FF0000;
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bom-utf-16be.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bom-utf-16be.css
new file mode 100644
index 0000000000..9e17902a1d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bom-utf-16be.css
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bom-utf-16le.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bom-utf-16le.css
new file mode 100644
index 0000000000..ef90843d8e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bom-utf-16le.css
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bom-utf-8.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bom-utf-8.css
new file mode 100644
index 0000000000..5cf81232b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/bom-utf-8.css
@@ -0,0 +1 @@
+div { background-color: blue; } \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/credentials-iframe.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/credentials-iframe.sub.html
new file mode 100644
index 0000000000..c71b0e4bb7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/credentials-iframe.sub.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<script type="module">
+ import styleSheet from "./cross-origin.py?id=sameOriginNoneDescendant&origin=http://{{host}}:{{ports[http][0]}}" with { type: "css" };
+ window.sameOriginNoneDescendant = (styleSheet.cssRules[0].cssText.indexOf(".requestHadCookies") !== -1);
+</script>
+<script type="module" crossOrigin="anonymous">
+ import styleSheet from "./cross-origin.py?id=sameOriginAnonymousDescendant&origin=http://{{host}}:{{ports[http][0]}}" with { type: "css" };
+ window.sameOriginAnonymousDescendant = (styleSheet.cssRules[0].cssText.indexOf(".requestHadCookies") !== -1);
+</script>
+<script type="module" crossOrigin="use-credentials">
+ import styleSheet from "./cross-origin.py?id=sameOriginUseCredentialsDescendant&origin=http://{{host}}:{{ports[http][0]}}" with { type: "css" };
+ window.sameOriginUseCredentialsDescendant = (styleSheet.cssRules[0].cssText.indexOf(".requestHadCookies") !== -1);
+</script>
+<script type="module">
+ import styleSheet from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module/resources/cross-origin.py?id=crossOriginNoneDescendant&origin=http://{{host}}:{{ports[http][0]}}" with { type: "css" };
+ window.crossOriginNoneDescendant = (styleSheet.cssRules[0].cssText.indexOf(".requestHadCookies") !== -1);
+</script>
+<script type="module" crossOrigin="anonymous">
+ import styleSheet from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module/resources/cross-origin.py?id=crossOriginAnonymousDescendant&origin=http://{{host}}:{{ports[http][0]}}" with { type: "css" };
+ window.crossOriginAnonymousDescendant = (styleSheet.cssRules[0].cssText.indexOf(".requestHadCookies") !== -1);
+</script>
+<script type="module" crossOrigin="use-credentials">
+ import styleSheet from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module/resources/cross-origin.py?id=crossOriginUseCredentialsDescendant&origin=http://{{host}}:{{ports[http][0]}}" with { type: "css" };
+ window.crossOriginUseCredentialsDescendant = (styleSheet.cssRules[0].cssText.indexOf(".requestHadCookies") !== -1);
+</script>
+
+<script type="text/javascript">
+window.addEventListener('load', event => {
+ window.parent.postMessage({}, '*');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/cross-origin.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/cross-origin.py
new file mode 100644
index 0000000000..d744fc9514
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/cross-origin.py
@@ -0,0 +1,17 @@
+def main(request, response):
+
+ headers = [
+ (b"Content-Type", b"text/css"),
+ (b"Access-Control-Allow-Origin", request.GET.first(b"origin")),
+ (b"Access-Control-Allow-Credentials", b"true")
+ ]
+
+ milk = request.cookies.first(b"milk", None)
+
+ # Send back
+ if milk is None:
+ return headers, u'.requestDidNotHaveCookies { }'
+ elif milk.value == b"1":
+ return headers, u'.requestHadCookies { }'
+
+ return headers, u'.requestDidNotHaveCookies { }'
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/crossorigin-import-parse-error-with-cors.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/crossorigin-import-parse-error-with-cors.sub.html
new file mode 100644
index 0000000000..59b4afa721
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/crossorigin-import-parse-error-with-cors.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>css-module-import-cross-domain-parse-error-WithCORS</title>
+ <script src="../../module/crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>css-module-import-cross-domain-parse-error-WithCORS</h1>
+ <script type="module" crossorigin>
+ import styleSheet from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module/resources/parse-error.css?pipe=header(Access-Control-Allow-Origin,*)" with { type: "css" };
+ // Push an event to the log indicating that the script was executed.
+ document._log.push(`imported CSS rules count: ${styleSheet.rules.length}`);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/crossorigin-import-with-cors.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/crossorigin-import-with-cors.sub.html
new file mode 100644
index 0000000000..2a383d8be9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/crossorigin-import-with-cors.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>css-module-import-cross-domain-WithCORS</title>
+ <script src="../../module/crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>css-module-import-cross-domain-WithCORS</h1>
+ <script type="module" crossorigin>
+ import styleSheet from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module/resources/basic.css?pipe=header(Access-Control-Allow-Origin,*)" with { type: "css" };
+ // Push an event to the log indicating that the script was executed.
+ document._log.push(`imported CSS: ${styleSheet.rules[0].cssText}`);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/crossorigin-import-without-cors.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/crossorigin-import-without-cors.sub.html
new file mode 100644
index 0000000000..256a76a1d9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/crossorigin-import-without-cors.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>css-module-import-cross-domain-NoCORS</title>
+ <script src="../../module/crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>css-module-import-cross-domain-NoCORS</h1>
+ <script type="module" onerror="document._log.push('error');">
+ import styleSheet from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/css-module/resources/basic.css" with { type: "css" };
+ // Push an event to the log indicating that the script was executed.
+ document._log.push(`imported CSS: ${styleSheet.rules[0].cssText}`);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-without-attribute-iframe.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-without-attribute-iframe.html
new file mode 100644
index 0000000000..3d1be841ce
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-without-attribute-iframe.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<body>
+ <script>
+ window.onerror = function (errorMsg, url, lineNumber, column, errorObj)
+ {
+ document.window_onerror = errorObj.name;
+ return true;
+ };
+
+ function scriptErrorHandler(e) {
+ document.script_onerror = e;
+ }
+ </script>
+ <script type="module" onerror="scriptErrorHandler(event)">
+ import v from "./basic.css";
+ document.adoptedStyleSheets = [v];
+ </script>
+
+ <div id="test">
+ I am a test div.
+ </div>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/integrity-matches.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/integrity-matches.js
new file mode 100644
index 0000000000..4bd004ce96
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/integrity-matches.js
@@ -0,0 +1,2 @@
+import styleSheet from "./basic.css" with { type: "css" };
+window.matchesLog.push(`integrity-matches,css:${styleSheet.cssRules[0].cssText}`);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/integrity-mismatches.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/integrity-mismatches.js
new file mode 100644
index 0000000000..7f3bf54762
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/integrity-mismatches.js
@@ -0,0 +1,2 @@
+import styleSheet from "./basic.css" with { type: "css" };
+window.matchesLog.push(`integrity-mismatches,css:${styleSheet.cssRules[0].cssText}`);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/load-error-events.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/load-error-events.py
new file mode 100644
index 0000000000..490bdc42aa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/load-error-events.py
@@ -0,0 +1,14 @@
+import re
+
+def main(request, response):
+ headers = [(b"Content-Type", b"text/javascript")]
+ test = request.GET.first(b'test')
+ assert(re.match(b'^[a-zA-Z0-9_]+$', test))
+
+ status = 200
+ if test.find(b'_load') >= 0:
+ content = b'import "./basic.css" with { type: "css"}; %s.executed = true;' % test
+ else:
+ content = b'import "./not_found.css" with { type: "css"}; %s.test.step(function() { assert_unreached("404 script should not be executed"); });' % test
+
+ return status, headers, content
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/load-relative-url.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/load-relative-url.css
new file mode 100644
index 0000000000..27f2987610
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/load-relative-url.css
@@ -0,0 +1,5 @@
+
+#target {
+ /* image.png doesn't exist, but that's irrelevant to the test. */
+ background: url('./image.png');
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/malformed.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/malformed.css
new file mode 100644
index 0000000000..28819bfdf5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/malformed.css
@@ -0,0 +1,7 @@
+#test4 } {
+ background-color: #FF0000;
+}
+
+#test4b {
+ background-color: #00FF00;
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/parse-error.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/parse-error.css
new file mode 100644
index 0000000000..2bee3ff996
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/parse-error.css
@@ -0,0 +1,2 @@
+div /* Opening bracket skipped intentionally. */ }
+
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/record-fetch.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/record-fetch.py
new file mode 100644
index 0000000000..4928cb4acb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/record-fetch.py
@@ -0,0 +1,20 @@
+def main(request, response):
+ try:
+ stash_key = request.GET.first(b"key")
+ action = request.GET.first(b"action")
+
+ run_count = request.server.stash.take(stash_key)
+ if not run_count:
+ run_count = 0
+
+ if action == b"incCount":
+ request.server.stash.put(stash_key, run_count + 1)
+ response.headers.set(b"Content-Type", b"text/css")
+ response.content = b'#test { background-color: #FF0000; }'
+ elif action == b"getCount":
+ response.headers.set(b"Content-Type", b"text/json")
+ response.content = b'{"count": %d }' % run_count
+ else:
+ response.set_error(400, u"Invalid action")
+ except:
+ response.set_error(400, u"Not enough parameters")
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/referrer-checker.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/referrer-checker.py
new file mode 100644
index 0000000000..c1eaed8e46
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/referrer-checker.py
@@ -0,0 +1,7 @@
+def main(request, response):
+ referrer = request.headers.get(b"referer", b"")
+ response_headers = [(b"Content-Type", b"text/css"),
+ (b"Access-Control-Allow-Origin", b"*")]
+ # Put the referrer in a CSS rule that can be read by the importer through CSSOM
+ return (200, response_headers,
+ b'.referrer { content: "' + referrer + b'" }')
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/utf-8.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/utf-8.css
new file mode 100644
index 0000000000..0a8b46656f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/utf-8.css
@@ -0,0 +1,3 @@
+#test {
+ content: "śćążź";
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/windows-1250.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/windows-1250.css
new file mode 100644
index 0000000000..9beac4d618
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/windows-1250.css
@@ -0,0 +1,3 @@
+#test {
+ content: "�湿�";
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker-dynamic-import.sub.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker-dynamic-import.sub.js
new file mode 100644
index 0000000000..30dc8cde85
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker-dynamic-import.sub.js
@@ -0,0 +1,3 @@
+import("./record-fetch.py?key={{GET[key]}}&action=incCount", { with: { type: "css" } })
+ .then(() => postMessage("LOADED"))
+ .catch(e => postMessage("NOT LOADED")); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker.sub.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker.sub.js
new file mode 100644
index 0000000000..dc85c13ed9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker.sub.js
@@ -0,0 +1,2 @@
+import "./record-fetch.py?key={{GET[key]}}&action=incCount" with { type: "css" };
+postMessage("Unexpectedly loaded"); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/script-element-css-src.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/script-element-css-src.html
new file mode 100644
index 0000000000..231d02db47
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/css-module/script-element-css-src.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>&lt;script&gt; with CSS src</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ const test_load = async_test(
+ "Test that <script> doesn't load when the src is CSS.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(log, ["error"]);
+ }));
+</script>
+<script type="module" src="./resources/basic.css" onload="t.unreached_func('CSS src should fail to load')" onerror="log.push('error')"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/data-url.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/data-url.html
new file mode 100644
index 0000000000..6fad505271
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/data-url.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test data URL and scripts errors</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+ setup({allow_uncaught_exception:true});
+ async_test(function(t) {
+ var counter = 1
+ window.onerror = t.step_func((message, url, lineno, colno, e) => {
+ // Test that error is not muted as data URLs have a response type of "default"
+ // and errors should only be muted if the response type is "opaque" or "opaqueredirect"
+ assert_not_equals(message, "Script error.")
+ assert_not_equals(url, null);
+ assert_not_equals(url, "");
+ assert_equals(typeof lineno, "number");
+ assert_not_equals(lineno, 0);
+ assert_equals(typeof colno, "number");
+ assert_not_equals(colno, 0);
+ assert_equals(typeof e, "number")
+ assert_equals(e, counter)
+ if (counter == 3) {
+ t.done()
+ }
+ counter++
+ });
+ });
+</script>
+<script src="data:,throw 1"></script>
+<script src="data:,throw 2" crossorigin></script>
+<script src="data:,throw 3" crossorigin=use-credentials></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/README.md b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/README.md
new file mode 100644
index 0000000000..ac5c91c9a2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/README.md
@@ -0,0 +1,7 @@
+The tests in this directory is intended for Chromium's DeferAllScript
+experiment https://crbug.com/1339112, containing scenarios that would be
+affected by DeferAllScript, to monitor the behavior on Chromium and other
+browsers.
+
+The same set of expectations (when async/defer scripts are evaluated) should
+already be covered somewhere in WPT.
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/async-script-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/async-script-2.html
new file mode 100644
index 0000000000..f7377d847a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/async-script-2.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<title>Async Script Execution Order</title>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/helper.js"></script>
+</head>
+<body>
+ <script>
+ setup({single_test: true});
+ function finish() {
+ assert_array_equals(
+ window.result,
+ ["Inline1", "Sync1", "Async1", "Sync2", "EndOfBody",
+ "DOMContentLoaded"],
+ "Execution order");
+ // Chromium's force-defer order would be:
+ // ["EndOfBody", "Inline1", "Sync1", "Sync2",
+ // "DOMContentLoaded", "Async1"]
+ //
+ // If we delay async script e.g. after DOMContentLoaded,
+ // the order would be:
+ // ["Inline1", "Sync1", "Sync2", "EndOfBody",
+ // "DOMContentLoaded", "Async1"]
+ done();
+ }
+ logScript("Inline1");
+ window.addEventListener("load", finish);
+ </script>
+ <script src="resources/sync-script-1.js"></script>
+ <!-- To test the async script loaded before force-deferred scripts
+ should be evaluated after the force-deferred scripts
+ in Chromium's force-defer order. -->
+ <script src="resources/async-script-1.js?pipe=trickle(d1)" async></script>
+ <script src="resources/sync-script-2.js?pipe=trickle(d2)"></script>
+ <pre id="bodyend">End</pre>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/async-script.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/async-script.html
new file mode 100644
index 0000000000..90f657679f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/async-script.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>Async Script Execution Order</title>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name=variant content="?default">
+ <meta name=variant content="?reload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/helper.js"></script>
+</head>
+<body>
+ <script>
+ let child_path = 'support/async-script.html';
+ if (location.search == '?reload') {
+ child_path += '?reload';
+ }
+ const child_window = window.open(child_path);
+ fetch_tests_from_window(child_window);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/defer-script-xml.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/defer-script-xml.xhtml
new file mode 100644
index 0000000000..9d02ff39f5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/defer-script-xml.xhtml
@@ -0,0 +1,40 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Defer Script Execution Order</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/helper.js"></script>
+</head>
+<body>
+ <div id="scriptlog"/>
+ <input id="testElement"/>
+ <script>
+ setup({single_test: true});
+ function finish() {
+ assert_array_equals(
+ window.result,
+ ["Inline1", "Sync1", "Inline2", "Sync2", "EndOfBody",
+ "Defer1", "Defer2", "DOMContentLoaded"],
+ "Execution order");
+ // Chromium order is (due to https://crbug.com/874749):
+ // ["Inline1", "Sync1", "Defer1", "Inline2", "Defer2", "Sync2",
+ // "EndOfBody", "DOMContentLoaded"]
+ done();
+ }
+ logScript("Inline1");
+ window.addEventListener("load", finish);
+ </script>
+
+ <script src="resources/sync-script-1.js"></script>
+ <script src="resources/defer-script-1.js" defer="defer"></script>
+ <script>
+ logScript("Inline2");
+ </script>
+ <script src="resources/defer-script-2.js" defer="defer"></script>
+ <script src="resources/sync-script-2.js"></script>
+ <pre id="bodyend">End</pre>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/defer-script.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/defer-script.html
new file mode 100644
index 0000000000..62c3a74014
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/defer-script.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<title>Defer Script Execution Order</title>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/helper.js"></script>
+</head>
+<body>
+ <script>
+ setup({single_test: true});
+ function finish() {
+ assert_array_equals(
+ window.result,
+ ["Inline1", "Sync1", "Inline2", "Sync2", "EndOfBody",
+ "Defer1", "Defer2", "DOMContentLoaded"],
+ "Execution order");
+ // Chromium's force defer order would be:
+ // ["EndOfBody", "Inline1", "Sync1", "Inline2", "Sync2",
+ // "Defer1", "Defer2", "DOMContentLoaded"]
+ done();
+ }
+ logScript("Inline1");
+ window.addEventListener("load", finish);
+ </script>
+
+ <script src="resources/sync-script-1.js"></script>
+ <script src="resources/defer-script-1.js" defer></script>
+ <script>
+ logScript("Inline2");
+ </script>
+ <script src="resources/defer-script-2.js" defer></script>
+ <script src="resources/sync-script-2.js"></script>
+ <pre id="bodyend">End</pre>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/document-write.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/document-write.html
new file mode 100644
index 0000000000..63e251bae5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/document-write.html
@@ -0,0 +1,67 @@
+<!doctype html>
+<title>DeferAllScript: document.write()</title>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ const t1 = async_test("document.write()");
+ const t2 = async_test("document.write(),close()");
+ const t3 = async_test("document.open(),write()");
+ const t4 = async_test("document.open(),write(),close()");
+ function finish() {
+ const expected = ["Inline1", "Sync2", "Async1", "Sync1",
+ "EndOfBody", "DOMContentLoaded", "WindowLoad"];
+ t1.step_func_done(() => {
+ assert_array_equals(
+ document.getElementById("document-write").contentWindow.result,
+ expected,
+ "Execution order");
+ })();
+
+ t2.step_func_done(() => {
+ assert_array_equals(
+ document.getElementById("document-write-close").contentWindow.result,
+ expected,
+ "Execution order");
+ })();
+
+ t3.step_func_done(() => {
+ assert_array_equals(
+ document.getElementById("document-open-write").contentWindow.result,
+ expected,
+ "Execution order");
+ })();
+
+ t4.step_func_done(() => {
+ assert_array_equals(
+ document.getElementById(
+ "document-open-write-close").contentWindow.result,
+ expected,
+ "Execution order");
+ })();
+ // For cases where documents are kept open, call `document.close()` here
+ // to finish the test harness.
+ for (const iframe of document.querySelectorAll("iframe")) {
+ iframe.contentDocument.close();
+ }
+ }
+
+ // For cases where documents are kept open (that should never occur in
+ // non-intervention cases), schedule `finish()` because Window load events
+ // might be not fired.
+ setTimeout(finish, 5000);
+ </script>
+</head>
+<body onload="finish()">
+<iframe id="document-write"
+ src="resources/document-write-iframe.sub.html?script=document-write.js"></iframe>
+<iframe id="document-write-close"
+ src="resources/document-write-iframe.sub.html?script=document-write-close.js"></iframe>
+<iframe id="document-open-write"
+ src="resources/document-write-iframe.sub.html?script=document-open-write.js"></iframe>
+<iframe id="document-open-write-close"
+ src="resources/document-write-iframe.sub.html?script=document-open-write-close.js"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/async-script-1.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/async-script-1.js
new file mode 100644
index 0000000000..267f324aa6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/async-script-1.js
@@ -0,0 +1 @@
+logScript("Async1");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/defer-script-1.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/defer-script-1.js
new file mode 100644
index 0000000000..1a0524f4fb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/defer-script-1.js
@@ -0,0 +1 @@
+logScript("Defer1");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/defer-script-2.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/defer-script-2.js
new file mode 100644
index 0000000000..d644e37f18
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/defer-script-2.js
@@ -0,0 +1 @@
+logScript("Defer2");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write-close.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write-close.js
new file mode 100644
index 0000000000..80703d5c0e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write-close.js
@@ -0,0 +1,3 @@
+document.open();
+document.write(`<script src="sync-script-2.js"></script>`);
+document.close();
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write.js
new file mode 100644
index 0000000000..178c374df6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-open-write.js
@@ -0,0 +1,2 @@
+document.open();
+document.write(`<script src="sync-script-2.js"></script>`);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-close.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-close.js
new file mode 100644
index 0000000000..7cdde0d78f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-close.js
@@ -0,0 +1,2 @@
+document.write(`<script src="sync-script-2.js"></script>`);
+document.close();
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-iframe.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-iframe.sub.html
new file mode 100644
index 0000000000..e3022e3bf1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write-iframe.sub.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<body>
+<script src="helper.js"></script>
+<script>
+logScript("Inline1");
+window.addEventListener("load", () => logScript("WindowLoad"));
+</script>
+<script src="{{GET[script]}}?pipe=trickle(d1)"></script>
+<script src="async-script-1.js" async></script>
+<script src="sync-script-1.js?pipe=trickle(d2)"></script>
+<pre id="bodyend">End</pre>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write.js
new file mode 100644
index 0000000000..413a9bc621
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/document-write.js
@@ -0,0 +1 @@
+document.write(`<script src="sync-script-2.js"></script>`);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/helper.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/helper.js
new file mode 100644
index 0000000000..89c6d1e828
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/helper.js
@@ -0,0 +1,17 @@
+window.result = [];
+function log(msg) {
+ window.result.push(msg);
+}
+function checkIfReachedBodyEnd() {
+ const endelement = document.getElementById("bodyend");
+ // `<pre id="bodyend">End</pre>` is needed at the end of HTML.
+ if (endelement && endelement.textContent === "End") {
+ log("EndOfBody");
+ endelement.textContent = "Detected";
+ }
+}
+function logScript(msg) {
+ checkIfReachedBodyEnd();
+ log(msg);
+}
+document.addEventListener("DOMContentLoaded", function() { logScript("DOMContentLoaded"); });
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/sync-script-1.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/sync-script-1.js
new file mode 100644
index 0000000000..726b56346e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/sync-script-1.js
@@ -0,0 +1 @@
+logScript("Sync1");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/sync-script-2.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/sync-script-2.js
new file mode 100644
index 0000000000..ba2edfbf27
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/resources/sync-script-2.js
@@ -0,0 +1 @@
+logScript("Sync2");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/support/async-script.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/support/async-script.html
new file mode 100644
index 0000000000..d513bafe4f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer-script/support/async-script.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<title>Child Async Script Execution Order</title>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <script src="/resources/testharness.js"></script>
+ <script src="../resources/helper.js"></script>
+</head>
+
+<body>
+ <script>
+ const reload_key = 'run-after-reload';
+ if (location.search == '?reload' && !window.localStorage.getItem(reload_key)) {
+ window.localStorage.setItem(reload_key, true);
+ location.reload();
+ } else {
+ window.localStorage.clear();
+ test();
+ }
+
+ function test() {
+ setup({ single_test: true });
+ function finish() {
+ assert_array_equals(
+ window.result,
+ ["Inline1", "Sync1", "EndOfBody", "DOMContentLoaded", "Async1"],
+ "Execution order");
+ // Chromium's force-defer order would be:
+ // ["EndOfBody", "Inline1", "Sync1", "DOMContentLoaded", "Async1"]
+ done();
+ }
+ logScript("Inline1");
+ window.addEventListener("load", finish);
+ }
+ </script>
+ <script src="../resources/sync-script-1.js"></script>
+ <!-- Delays are added to make DOMContentLoaded be fired before
+ async script load completion -->
+ <script src="../resources/async-script-1.js?pipe=trickle(d1)" async></script>
+ <pre id="bodyend">End</pre>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer.js
new file mode 100644
index 0000000000..c4449ca7c8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/defer.js
@@ -0,0 +1,4 @@
+t.step(() => {
+ assert_equals(script_run_status, "deferred", "the script run status");
+});
+t.done();
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/emptyish-script-elements.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/emptyish-script-elements.html
new file mode 100644
index 0000000000..37f4a87c95
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/emptyish-script-elements.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Treatment of various empty-ish script elements</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#already-started">
+<link rel="help" href="https://github.com/whatwg/html/issues/3419">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script id="no-children"></script>
+<script id="whitespace-child"> </script>
+
+<script id="gets-a-no-text-child"></script>
+<script id="gets-an-empty-text-child"></script>
+<script id="gets-a-text-child"></script>
+<script id="gets-a-comment-child"></script>
+<script id="gets-a-text-descendant"></script>
+
+<script>
+"use strict";
+
+test(() => {
+ const el = document.querySelector("#no-children");
+ el.appendChild(document.createTextNode("window.noChildrenExecuted = true;"));
+ assert_true(window.noChildrenExecuted);
+}, "A script with no children bails early, before setting already-started, so can be executed when adding text");
+
+test(() => {
+ const el = document.querySelector("#whitespace-child");
+ el.appendChild(document.createTextNode("window.whitespaceChildExecuted = true;"));
+ assert_equals(window.whitespaceChildExecuted, undefined);
+}, "A script with a whitespace child executes, setting already-started, so adding text is a no-op");
+
+test(() => {
+ const el = document.querySelector("#gets-a-no-text-child");
+ el.appendChild(document.createElement("span"));
+ el.appendChild(document.createTextNode("window.getsANoTextChildExecuted = true;"));
+ assert_true(window.getsANoTextChildExecuted);
+}, "A script with an empty element inserted bails early, before setting already-started, so can be executed when adding text");
+
+test(() => {
+ const el = document.querySelector("#gets-an-empty-text-child");
+ el.appendChild(document.createTextNode(""));
+ el.appendChild(document.createTextNode("window.getsAnEmptyTextChildExecuted = true;"));
+ assert_true(window.getsAnEmptyTextChildExecuted);
+}, "A script with an empty text node inserted bails early, before setting already-started, so can be executed when adding text");
+
+test(() => {
+ const el = document.querySelector("#gets-a-text-child");
+ el.appendChild(document.createTextNode("window.getsATextChildExecuted1 = true;"));
+ el.appendChild(document.createTextNode("window.getsATextChildExecuted2 = true;"));
+ assert_true(window.getsATextChildExecuted1);
+ assert_equals(window.getsATextChildExecuted2, undefined);
+}, "A script with a text child inserted executes, setting already-started, so adding text is a no-op");
+
+test(() => {
+ const el = document.querySelector("#gets-a-comment-child");
+ el.appendChild(document.createComment("window.getsACommentChild1 = true;"));
+ el.appendChild(document.createTextNode("window.getsACommentChild2 = true;"));
+ assert_equals(window.getsACommentChild1, undefined);
+ assert_true(window.getsACommentChild2);
+}, "A script with a comment child inserted bails early, before setting already-started, so can be executed when adding text");
+
+test(() => {
+ const el = document.querySelector("#gets-a-text-descendant");
+ const child = document.createElement("span");
+ child.appendChild(document.createTextNode("window.getsATextDescendantExecuted1 = true;"));
+ el.appendChild(child);
+ el.appendChild(document.createTextNode("window.getsATextDescendantExecuted2 = true;"));
+ assert_equals(window.getsATextDescendantExecuted1, undefined);
+ assert_true(window.getsATextDescendantExecuted2);
+}, "A script with an element containing text inserted bails early, before setting already-started, so can be executed when adding text");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/001.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/001.html
new file mode 100644
index 0000000000..3f54f764f9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/001.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: inline in markup </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');</script>
+ <script>log('inline script #2');</script>
+
+ <script type="text/javascript">
+
+ var t = async_test()
+
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'inline script #2' ]);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/002.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/002.html
new file mode 100644
index 0000000000..df7ca95799
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/002.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: external in markup </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script src="scripts/include-1.js"></script>
+ <script src="scripts/include-2.js"></script>
+
+ <script type="text/javascript">
+
+ var t = async_test()
+
+
+ function test() {
+ assert_array_equals(eventOrder, ['external script #1', 'external script #2' ]);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/003.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/003.html
new file mode 100644
index 0000000000..9c23b7e715
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/003.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: inline+external in markup </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1')</script>
+ <script src="scripts/include-2.js"></script>
+
+ <script type="text/javascript">
+
+ var t = async_test()
+
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'external script #2' ]);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/004.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/004.html
new file mode 100644
index 0000000000..a21dd388eb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/004.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: external+inline in markup </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script src="scripts/include-1.js"></script>
+ <script>log('inline script #2')</script>
+
+ <script type="text/javascript">
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['external script #1', 'inline script #2' ]);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/005.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/005.html
new file mode 100644
index 0000000000..ff4a66d25e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/005.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write inline in markup </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.write( '<script>log(\'doc write script\')<\/script>' );
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'doc write script', 'end script #1' ]);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/006.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/006.html
new file mode 100644
index 0000000000..b8785a60c6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/006.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write inline - multiple</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.write( '<script>log(\'doc write script 1\')<\/script>' );
+ document.write( '<script>log(\'doc write script 2\')<\/script>' );
+ eval('log(\'eval 1\')');
+ document.write( '<script>log(\'doc write script 3\')<\/script>' );
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'doc write script 1','doc write script 2', 'eval 1','doc write script 3', 'end script #1' ]);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/007.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/007.html
new file mode 100644
index 0000000000..edd9920757
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/007.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write external</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.write( '<script src="scripts/include-1.js">log(\'ignore this\')<\/script>' );
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'external script #1' ]);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/008.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/008.html
new file mode 100644
index 0000000000..dce763987f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/008.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write external - multiple</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.write( '<script src="scripts/include-1.js">log(\'ignore this\')<\/script>' );
+ document.write( '<script src="scripts/include-2.js">log(\'ignore this\')<\/script>' );
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'external script #1', 'external script #2' ]);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/009.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/009.html
new file mode 100644
index 0000000000..9d5b2de081
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/009.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write external - multiple with doc.write</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.write( '<script src="scripts/include-1.js">log(\'ignore this\')<\/script>' );
+ document.write( '<script src="scripts/include-3.js">log(\'ignore this\')<\/script>' );
+ document.write( '<script src="scripts/include-2.js">log(\'ignore this\')<\/script>' );
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'external script #1', 'external script before doc write', 'document.write external script', 'external script after doc write', 'external script #2' ]);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/010.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/010.html
new file mode 100644
index 0000000000..69a462c301
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/010.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write external + inline - multiple with doc.write</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.write( '<script src="scripts/include-1.js">log(\'ignore this\')<\/script>' );
+ document.write( '<script>log(\'inline with doc.write #1\')<\/script>' );
+ document.write( '<script src="scripts/include-2.js">log(\'ignore this\')<\/script>' );
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'external script #1', 'inline with doc.write #1', 'external script #2']);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/011.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/011.html
new file mode 100644
index 0000000000..33024ba59e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/011.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write external + inline - multiple with doc.write + subsequent markup</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.write( '<script src="scripts/include-1.js">log(\'ignore this\')<\/script>' );
+ document.write( '<script>log(\'inline with doc.write #1\')<\/script>' );
+ document.write( '<script src="scripts/include-2.js">log(\'ignore this\')<\/script>' );
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'external script #1', 'inline with doc.write #1', 'external script #2', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/012.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/012.html
new file mode 100644
index 0000000000..01c9293b20
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/012.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write external and onload events </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.write( '<script src="scripts/include-1.js" onload = "log(\'include-1 load\')">log(\'ignore this\')<\/script>' )
+ document.write( '<script src="scripts/include-3.js" onload = "log(\'include-3 load\')"><\/script>' )
+ document.write( '<script src="scripts/include-2.js" onload = "log(\'include-2 load\')">log(\'ignore this\')<\/script>' )
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'external script #1', 'include-1 load', 'external script before doc write', 'document.write external script', 'external script after doc write', 'include-3 load', 'external script #2', 'include-2 load', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/013.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/013.html
new file mode 100644
index 0000000000..09616a67d4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/013.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM added inline script earlier in document</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ testlib.addScript('log(\'head script #1\')', {}, document.getElementsByTagName('head')[0], false);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'head script #1','end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(function(){setTimeout(t.step_func(test), 100); })
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/014.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/014.html
new file mode 100644
index 0000000000..41c90a3421
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/014.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: SCRIPT elements that move themselves in DOM </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.getElementsByTagName('head')[0].appendChild(document.getElementsByTagName('script')[2]);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log('script #2');
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'script #2']);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/015.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/015.html
new file mode 100644
index 0000000000..1fa67e22ed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/015.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM added inline+external+inline script earlier in document</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ testlib.addScript('log(\'head script #1\')', {}, document.getElementsByTagName('head')[0], false);
+ var s = testlib.addScript('', { 'src':'scripts/include-1.js' }, document.getElementsByTagName('head')[0], false);
+ testlib.addScript('log(\'head script #2\')', {}, document.getElementsByTagName('head')[0], false);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ ///XXX I think the spec allows this case to race
+ onload = function(){
+ setTimeout(t.step_func(
+ function() {
+ assert_any(assert_array_equals, eventOrder, [['inline script #1', 'head script #1', 'head script #2', 'end script #1', 'external script #1', 'inline script #2'],
+ ['inline script #1', 'head script #1', 'head script #2', 'end script #1', 'inline script #2', 'external script #1']]);
+ t.done();
+ }),
+ 100);}
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/015a.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/015a.html
new file mode 100644
index 0000000000..94763c3542
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/015a.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM added inline+external+inline script earlier in document</title>
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ testlib.addScript('log(\'head script #1\')', {}, document.getElementsByTagName('head')[0], false);
+ var s = testlib.addScript('', { 'src':'scripts/include-1.js?pipe=trickle(d1)' }, document.getElementsByTagName('head')[0], false);
+ testlib.addScript('log(\'head script #2\')', {}, document.getElementsByTagName('head')[0], false);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'head script #1', 'head script #2', 'end script #1', 'inline script #2', 'external script #1']);
+ t.done();
+ }
+ onload = function(){setTimeout(t.step_func(function() {test.apply(t)}), 2000); }
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/016.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/016.html
new file mode 100644
index 0000000000..1149dcc752
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/016.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM added inline script later in document</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ testlib.addScript('log(\'body script #1\')', {}, document.body, false);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'body script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(function(){setTimeout(t.step_func(test), 100); })
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/017.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/017.html
new file mode 100644
index 0000000000..66675bcf11
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/017.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: multiple DOM added scripts later in document</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ testlib.addScript('log(\'body script #1\')', {}, document.getElementsByTagName('body')[0], false);
+ testlib.addScript('', { 'src':'scripts/include-1.js' }, document.getElementsByTagName('body')[0], false);
+ testlib.addScript('log(\'body script #2\')', {}, document.getElementsByTagName('body')[0], false);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ //The order of the external script vs the second inline script is undefined because the added script is async by default
+ //But we expect most UAs to have the second order
+ onload = function() {setTimeout(t.step_func(function() {
+ assert_any(assert_array_equals, eventOrder, [
+ ['inline script #1', 'body script #1', 'body script #2', 'end script #1', 'external script #1', 'inline script #2'],
+ ['inline script #1', 'body script #1', 'body script #2', 'end script #1', 'inline script #2', 'external script #1']]);
+ t.done();
+ }), 100);}
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/018.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/018.html
new file mode 100644
index 0000000000..5a349bf556
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/018.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM added scripts and doc.write</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ testlib.addScript('', { 'src':'scripts/include-3.js' }, document.getElementsByTagName('head')[0], false);
+ testlib.addScript('log(\'body script #2\')', {}, document.getElementsByTagName('body')[0], true);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ //XXX Need to test this delaying the document after we insert the external script and delaying the external script itself; afaict the spec allows us to race here on whether the document.write
+ //ever actually happens or not according to whether the insertion point is defined at the point at which the script is executed.
+ function test() {
+ assert_any(assert_array_equals, eventOrder, [
+ ['inline script #1', 'body script #2', 'end script #1', 'external script before doc write', 'document.write external script', 'external script after doc write', 'inline script #2'],
+ ['inline script #1', 'body script #2', 'end script #1', 'inline script #2', 'external script before doc write', 'document.write external script', 'external script after doc write'],
+ ['inline script #1', 'body script #2', 'end script #1', 'inline script #2', 'external script before doc write', 'external script after doc write']
+ ]);
+ t.done();
+}
+ onload = t.step_func(function(){setTimeout(test.apply(t), 100); })
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/019.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/019.html
new file mode 100644
index 0000000000..64ee4f1c52
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/019.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM added scripts and event handling </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script onload="log('inline #1 load')">
+ log('inline script #1');
+ testlib.addScript('', {'src':'scripts/include-1.js', 'onload':function(){log("external #1 load")}}, document.getElementsByTagName('head')[0], false);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_any(assert_array_equals, eventOrder, [['inline script #1', 'end script #1', 'external script #1', 'external #1 load', 'inline script #2'],
+ ['inline script #1', 'end script #1', 'inline script #2', 'external script #1', 'external #1 load']
+ ]);
+ t.done();
+ }
+ onload = function(){setTimeout(t.step_func(test), 100); }
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/020.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/020.html
new file mode 100644
index 0000000000..7d8f953e40
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/020.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM added script with data: URL </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <script>log('inline script #1');
+ testlib.addScript('', { 'src':'data:text/javascript,log("data URL script")' }, document.getElementsByTagName('body')[0], true);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_any(assert_array_equals, eventOrder, [['inline script #1', 'end script #1', 'data URL script', 'inline script #2'],
+ ['inline script #1', 'end script #1', 'inline script #2', 'data URL script']]);
+ t.done();
+ }
+ onload = function() {setTimeout( t.step_func(test), 100); }
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/021.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/021.html
new file mode 100644
index 0000000000..34fdc95cb1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/021.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM added script with javascript: URL </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <script>log('inline script #1');
+ testlib.addScript('', { 'src':'javascript:log("JS URL script")' }, document.getElementsByTagName('body')[0], true);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', /*'JS URL script',*/ 'inline script #2']);
+ t.done();
+ /* pass condition changed 2010-12-01 due to CT-198 */
+ }
+ onload = t.step_func(function(){setTimeout( test, 100); })
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/022.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/022.html
new file mode 100644
index 0000000000..ccbcb347dc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/022.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM added script, late .src </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <script>log('inline script #1');
+ var script = testlib.addScript('', { }, document.getElementsByTagName('body')[0], false);
+ script.src='scripts/include-1.js';
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ onload = function() {setTimeout(t.step_func(
+ function() {
+ assert_any(assert_array_equals, eventOrder, [['inline script #1', 'end script #1', 'external script #1', 'inline script #2'],
+ ['inline script #1', 'end script #1', 'inline script #2', 'external script #1']]);
+ t.done()
+ }),
+ 100)}
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/023.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/023.html
new file mode 100644
index 0000000000..dc687ffe4d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/023.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM added script, even later .src </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <script>log('inline script #1');
+ var script = testlib.addScript('', { }, document.getElementsByTagName('body')[0], false);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'inline script #2', 'external script #1']);
+ t.done();
+}
+ onload = t.step_func(function(){
+ script.src='scripts/include-1.js';
+ script.onload = t.step_func(test);
+ })
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/024.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/024.html
new file mode 100644
index 0000000000..ee807b56f9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/024.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM added script, .src set twice</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <script>log('inline script #1');
+ var script = testlib.addScript('', { }, document.getElementsByTagName('body')[0], false);
+ script.src='scripts/include-1.js';
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_any(assert_array_equals, eventOrder, [['inline script #1', 'end script #1', 'external script #1', 'inline script #2'],
+ ['inline script #1', 'end script #1', 'inline script #2', 'external script #1']]);
+ t.done();
+ }
+ onload = t.step_func(function(){
+ script.src='scripts/include-2.js'; // needs to be ignored, script already "is executed"
+ setTimeout(t.step_func(test), 100);
+ })
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/025.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/025.html
new file mode 100644
index 0000000000..05c6a97284
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/025.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM added script, .src set on script with content</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <script>log('inline script #1');
+ var script = testlib.addScript('log("inline DOM script #1")', { }, document.getElementsByTagName('body')[0], false);
+ script.src='scripts/include-1.js';
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'inline DOM script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(function(){
+ setTimeout(t.step_func(test), 100);
+ })
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/026.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/026.html
new file mode 100644
index 0000000000..34110ff5ba
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/026.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: doc write added script, .src set later</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <script>log('inline script #1');
+ var t = async_test();
+ document.write('<script><\/script>');
+ var scripts = document.getElementsByTagName('script');
+ scripts[scripts.length - 1].src = 'scripts/include-1.js';
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+
+ onload = function() {
+ setTimeout(
+ t.step_func(function() {
+ assert_any(assert_array_equals, eventOrder, [['inline script #1', 'end script #1', 'external script #1', 'inline script #2'],
+ ['inline script #1', 'end script #1', 'inline script #2', 'external script #1']]);
+ t.done();
+ }),
+ 100);
+ }
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/027.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/027.html
new file mode 100644
index 0000000000..e9fbe7f15c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/027.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: doc write added script with content, .src set later</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <script>log('inline script #1');
+ document.write('<script>log(\'doc.write script\')<\/script>');
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ document.getElementsByTagName('script')[4].src='scripts/include-1.js';
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'doc.write script', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(function(){
+ setTimeout(t.step_func(test), 100);
+ })
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/028.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/028.html
new file mode 100644
index 0000000000..e383d4f1a6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/028.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: javascript: URL</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <script>log('inline script #1');
+ window.location.replace('javascript:log(\'JS URL\')');
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_any(assert_array_equals, eventOrder, [
+ ['inline script #1', 'end script #1', 'JS URL', 'inline script #2'],
+ ['inline script #1', 'end script #1', 'inline script #2', 'JS URL']]);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/030.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/030.html
new file mode 100644
index 0000000000..f01c257e0d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/030.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: javascript: URL in HREF, onclick handler</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <p><a href="javascript:log('JS URL')" onclick="log('click event');return true;"></a></p>
+ <script>log('inline script #1');
+ if(document.links[0].click){
+ document.links[0].click();
+ }else{
+ var evt = document.createEvent("MouseEvents");
+ evt.initMouseEvent("click", true, true, window,
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ document.links[0].dispatchEvent(evt);
+ }
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ var w = window;
+ onload = function() {setTimeout(
+ t.step_func(function() {
+ w.assert_any(w.assert_array_equals, w.eventOrder,
+ [['inline script #1', 'click event', 'end script #1', 'JS URL', 'inline script #2'],
+ ['inline script #1', 'click event', 'end script #1', 'inline script #2', 'JS URL']]);
+ t.done();
+ }), 200);
+ }
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/031.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/031.html
new file mode 100644
index 0000000000..3ddb36ab8d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/031.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: focus and blur events</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <form><input type="button" onclick="log('click event')" onfocus="log('focus event')" onblur="log('blur event')"><input type="button" onfocus="log('focus el 2 event')" onblur="log('blur event')"></form>
+ <script>log('inline script #1');
+ document.forms[0][0].focus();
+ document.forms[0][1].click();
+ document.forms[0][1].focus();
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log('inline script #2');
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'focus event', 'blur event', 'focus el 2 event', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(function(){setTimeout(t.step_func(test), 200);})
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/032.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/032.html
new file mode 100644
index 0000000000..da3969740c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/032.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: innerHTML and scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div></div>
+ <script>log('inline script #1');
+ // script added with innerHTML should not run..
+ document.getElementsByTagName('div')[1].innerHTML = '<script>log("innerHTML script runs")<\/script><script src="scripts/include-1.js"><\/script>';
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(function(){setTimeout(t.step_func(test), 200);})
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/033.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/033.html
new file mode 100644
index 0000000000..5c41effcde
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/033.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: innerHTML and scripts moved in DOM</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div></div>
+ <script>log('inline script #1');
+ // script added with innerHTML should not run..
+ document.getElementsByTagName('div')[0].innerHTML = '<script>log("innerHTML script runs")<\/script><script src="scripts/include-1.js"><\/script>';
+ try{
+ document.body.appendChild( document.getElementsByTagName('div')[0].firstChild );
+ document.body.appendChild( document.getElementsByTagName('div')[0].firstChild );
+ }catch(e){
+ log('ERROR while testing');
+ }
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(function(){setTimeout(t.step_func(test), 200);})
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/034.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/034.html
new file mode 100644
index 0000000000..13664253a2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/034.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: innerHTML adding frames with JS in</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div></div>
+ <script>log('inline script #1');
+ document.getElementsByTagName('div')[1].innerHTML = '<iframe src="pages/helloworld.html"></iframe>';
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'inline script #2', 'frame/popup script']);
+ t.done();
+ }
+ onload = t.step_func(function(){setTimeout(t.step_func(test), 200);})
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/035.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/035.html
new file mode 100644
index 0000000000..406c3c548f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/035.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: innerHTML adding frames with JS in and moving scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div></div>
+ <script>log('inline script #1');
+ document.getElementsByTagName('div')[1].innerHTML = '<iframe src="pages/helloworld.html"></iframe>';
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'inline script #2', 'frame/popup script']);
+ t.done();
+ /*, ['inline script #1', 'end script #1', 'frame/popup script', 'inline script #2'] */
+ }
+ onload = t.step_func(function(){
+ try{
+ document.body.appendChild(document.importNode( top.frames[0].document.getElementsByTagName('script')[0], true ));
+ }catch(e){ log('ERROR - tested functionality not supported'); }
+ setTimeout(t.step_func(test), 200);
+ });
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/036.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/036.html
new file mode 100644
index 0000000000..113541dab9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/036.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM cloning</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div></div>
+ <script>log('inline script #1');
+ testlib.addScript('log(\'head script #1\')', {}, document.getElementsByTagName('head')[0], true);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ try{
+ var script = document.getElementsByTagName('script')[0].cloneNode(true);
+ document.body.appendChild(script);
+ }catch(e){ log('ERROR - tested functionality not supported'); }
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'head script #1', 'end script #1', 'inline script #2' ]);
+ t.done();
+ }
+ onload = t.step_func(function(){
+ setTimeout(t.step_func(test), 200);
+ });
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/037.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/037.html
new file mode 100644
index 0000000000..15bd8a96e0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/037.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM movement with appendChild, inline</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div></div>
+ <script>log('inline script #1');
+ var script = testlib.addScript('log(\'head script #1\')', {}, document.getElementsByTagName('head')[0], true);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ try{
+ document.body.appendChild(script);
+ }catch(e){ log('ERROR - tested functionality not supported'); }
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'head script #1', 'end script #1', 'inline script #2' ]);
+ t.done();
+ }
+ onload = t.step_func(function(){
+ setTimeout(t.step_func(test), 200);
+ });
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/038.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/038.html
new file mode 100644
index 0000000000..db6cec3520
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/038.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM movement with appendChild, external</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div></div>
+ <script>log('inline script #1');
+ var script = testlib.addScript('', { 'src':'scripts/include-1.js' }, document.getElementsByTagName('head')[0], true);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ try{
+ document.body.appendChild(script);
+ }catch(e){ log('ERROR - tested functionality not supported'); }
+ var t = async_test()
+
+ function test() {
+ assert_any(assert_array_equals, eventOrder, [['inline script #1', 'end script #1', 'external script #1', 'inline script #2'],
+ ['inline script #1', 'end script #1', 'inline script #2', 'external script #1']]);
+ t.done();
+ }
+ onload = function() {
+ setTimeout(t.step_func(test), 200);
+ };
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/039.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/039.html
new file mode 100644
index 0000000000..3720e24866
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/039.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: IFRAMEs added with DOM</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div></div>
+ <script>log('inline script #1');
+ for( var i=0; i<2; i++ ){
+ var iframe=document.createElement('iframe');
+ document.getElementsByTagName('div')[1].appendChild(iframe);
+ iframe.src='pages/helloworld.html?'+i+'&'+Math.random();
+ }
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ promise_test(() => {
+ const frames = document.querySelectorAll("iframe");
+ return Promise.all([
+ new Promise(resolve => window.addEventListener('load', resolve)),
+ new Promise(resolve => frames[0].addEventListener('load', resolve)),
+ new Promise(resolve => frames[1].addEventListener('load', resolve)),
+ ]).then(() => {
+ assert_equals(eventOrder.length, 5);
+ assert_array_equals(
+ eventOrder.slice(0, 3),
+ ['inline script #1', 'end script #1', 'inline script #2'],
+ "inline scripts should run first");
+ assert_in_array('frame/popup script 0', eventOrder.slice(3, 5), 'iframe should have loaded');
+ assert_in_array('frame/popup script 1', eventOrder.slice(3, 5), 'iframe should have loaded');
+ });
+ }, 'iframes should load asynchronously after inline script run');
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/040.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/040.html
new file mode 100644
index 0000000000..3cdf87f07b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/040.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: IFRAMEs added with DOM (innerHTML), javascript: URL</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div></div>
+ <script>log('inline script #1');
+ document.getElementsByTagName('div')[1].innerHTML = '<iframe src="javascript:parent.log(\'JS URL\');\'<html><script>parent.log(\\\'frame script\\\')<\/script></html>\'"></iframe>';
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_any(assert_array_equals, eventOrder, [
+ ['inline script #1', 'end script #1', 'JS URL', 'inline script #2', 'frame script'],
+ /* the following combination seems quite unlikely? */
+ ['inline script #1', 'end script #1', 'JS URL', 'frame script', 'inline script #2'],
+ ['inline script #1', 'end script #1', 'inline script #2', 'JS URL', 'frame script']]);
+ t.done();
+ }
+ onload = t.step_func(function(){
+ setTimeout(t.step_func(test), 200);
+ });
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/041.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/041.html
new file mode 100644
index 0000000000..bce7041185
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/041.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write scripts that write scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.write( '<script>log(\'inline with doc.write #1\'); document.write(\'<script src="scripts/include-4.js"><\\\/script>\');log(\'end inline with doc.write\');<\/script>' );
+ document.write( '<script src="scripts/include-1.js">log(\'ignore this\')<\/script>' );
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'inline with doc.write #1', 'end inline with doc.write', 'end script #1', 'include-4 before doc write', 'include-4 after doc write', 'external script before doc write', 'document.write external script', 'external script after doc write', 'external script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/042.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/042.html
new file mode 100644
index 0000000000..df3a2f88f2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/042.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM mutation events when adding scripts: DOMNodeInserted </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.addEventListener( 'DOMNodeInserted', function(){ log('DOMNodeInserted'); }, false );
+ testlib.addScript('log(\'head script #1\')', {}, document.getElementsByTagName('head')[0], false);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'head script #1', 'DOMNodeInserted', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/043.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/043.html
new file mode 100644
index 0000000000..bcfd90cba4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/043.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM mutation events when adding external scripts: DOMNodeInserted </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.addEventListener( 'DOMNodeInserted', function(){ log('DOMNodeInserted'); }, false );
+ testlib.addScript('', { src: 'scripts/include-1.js' }, document.getElementsByTagName('head')[0], false);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_any(assert_array_equals, eventOrder, [['inline script #1', 'DOMNodeInserted', 'end script #1', 'external script #1', 'inline script #2'],
+ ['inline script #1', 'DOMNodeInserted', 'end script #1', 'inline script #2', 'external script #1']]
+ );
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/044.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/044.html
new file mode 100644
index 0000000000..8d412079e4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/044.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM mutation events when adding scripts: DOMNodeInsertedIntoDocument </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ testlib.addScript('log(\'head script #1\')', {}, document.getElementsByTagName('head')[0], false, function(s){s.addEventListener( 'DOMNodeInsertedIntoDocument', function(){ log('DOMNodeInsertedIntoDocument'); }, false ); } );
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'head script #1', 'DOMNodeInsertedIntoDocument', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/045.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/045.html
new file mode 100644
index 0000000000..254e0d1366
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/045.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: DOM mutation events when adding external scripts: DOMNodeInsertedIntoDocument </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ testlib.addScript('', {src:'scripts/include-1.js'}, document.getElementsByTagName('head')[0], false, function(s){s.addEventListener( 'DOMNodeInsertedIntoDocument', function(){ log('DOMNodeInsertedIntoDocument'); }, false);});
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ onload = t.step_func(function() {
+ assert_any(assert_array_equals, eventOrder, [['inline script #1', 'DOMNodeInsertedIntoDocument', 'end script #1', 'external script #1', 'inline script #2'],
+ ['inline script #1', 'DOMNodeInsertedIntoDocument', 'end script #1', 'inline script #2', 'external script #1']]);
+ t.done();
+ });
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/046.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/046.html
new file mode 100644
index 0000000000..4f145d63e1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/046.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: no readystatechange events when adding external scripts </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ testlib.addScript('', {src:'scripts/include-1.js', onreadystatechange:function() {log( 'readystatechange '+ this.readyState );}}, document.getElementsByTagName('head')[0], false );
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'external script #1']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/047.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/047.html
new file mode 100644
index 0000000000..88509e9d43
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/047.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: adding and removing external script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=testlib.addScript('', {src:'scripts/include-1.js'}, document.getElementsByTagName('head')[0], false );
+ script.parentNode.removeChild(script);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'external script #1']);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/048.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/048.html
new file mode 100644
index 0000000000..8879f035d3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/048.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: adding inline script which sets its own .src </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=testlib.addScript('log(\'HEAD script start\');document.getElementsByTagName(\'script\')[0].src=\'scripts/include-1.js\';log(\'HEAD script end\')', {}, document.getElementsByTagName('head')[0], true );
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'HEAD script start', 'HEAD script end', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/049.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/049.html
new file mode 100644
index 0000000000..455a20c549
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/049.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: adding external script but removeAttribute( src ) before it runs</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=testlib.addScript('log(\'HEAD script\');', { src:'scripts/include-1.js' }, document.getElementsByTagName('head')[0], false );
+ script.removeAttribute('src');
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'external script #1']);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/050.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/050.html
new file mode 100644
index 0000000000..a400749f18
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/050.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: adding external script that removes all scripts from document</title>
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=testlib.addScript('', { src:'scripts/include-5.js' }, document.getElementsByTagName('head')[0], false );
+ // caching might affect whether the below script runs or not. Adding Math.random() makes the test a bit more predictable? :-p
+ var script=testlib.addScript('', { src:'scripts/include-1.js?pipe=trickle(d1)&'+Math.random() }, document.getElementsByTagName('head')[0], false );
+ log('end script #1');
+ </script>
+ <script src="scripts/include-2.js?pipe=trickle(d4)"></script>
+ <script type="text/javascript">
+ var t = async_test()
+
+ function test() {
+ // Per-spec, non-blocking/async scripts can execute at any time.
+ // Therefore, there are two possibilities for the script order here.
+ // 1. inline script first, followed by include-5 (async), then
+ // external script #1 (slow async) and finally external #2
+ // (inline).
+ // 2. inline script, external '2, 'include 5', then include-1.
+ assert_array_equals(eventOrder.slice(0, 2), [
+ 'inline script #1', 'end script #1'
+ ]);
+ if (eventOrder[2] == 'include-5 before removing scripts') {
+ assert_array_equals(eventOrder.slice(3), [
+ 'include-5 after removing scripts', 'external script #1',
+ 'external script #2'
+ ]);
+ } else {
+ assert_array_equals(eventOrder.slice(2), ['external script #2',
+ 'include-5 before removing scripts',
+ 'include-5 after removing scripts',
+ 'external script #1'
+ ]);
+ }
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/051.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/051.html
new file mode 100644
index 0000000000..a0b674304f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/051.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: interaction of parsing and script execution - script added through DOM</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ testlib.addScript('', { src: 'scripts/count-script-tags.js' }, document.body, true);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_any(assert_array_equals, eventOrder, [['inline script #1', 'end script #1', 'script tags in DOM: 5', 'inline script #2'],
+ ['inline script #1', 'end script #1', 'inline script #2', 'script tags in DOM: 6']]);
+ t.done();
+ }
+ onload = function(){setTimeout(t.step_func(test), 100); }
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/052.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/052.html
new file mode 100644
index 0000000000..21a151cb79
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/052.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: interaction of parsing and script execution - external script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script src="scripts/count-script-tags.js"></script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['script tags in DOM: 4', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(function(){setTimeout(t.step_func(test), 100); })
+ </script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/053.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/053.html
new file mode 100644
index 0000000000..810197437d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/053.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: adding external script that removes itself from document when loading</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=testlib.addScript('', { src:'scripts/include-1.js', onload:function() {this.parentNode.removeChild(this);log('removed ' + this.localName);} }, document.getElementsByTagName('body')[0], true );
+ log('end script #1');
+ </script>
+ <script src="scripts/include-2.js"></script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_any(assert_array_equals, eventOrder, [['inline script #1', 'end script #1', 'external script #1', 'removed script', 'external script #2', 'inline script #2'],
+ ['inline script #1', 'end script #1', 'external script #2', 'external script #1', 'removed script', 'inline script #2'],
+ ['inline script #1', 'end script #1', 'external script #2', 'inline script #2', 'external script #1', 'removed script']]
+ );
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/054.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/054.html
new file mode 100644
index 0000000000..29ede23414
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/054.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: removing newly inserted script from DOMNodeInserted handler - external script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.addEventListener( 'DOMNodeInserted', function listener(e){
+ log('DOMNodeInserted event');
+ e.target.parentNode.removeChild(e.target);
+ document.removeEventListener('DOMNodeInserted', listener);
+ }, false );
+ var script=testlib.addScript('', { src:'scripts/include-1.js?'+Math.random() }, document.getElementsByTagName('body')[0], true );
+ log('end script #1');
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_any(assert_array_equals, eventOrder, [['inline script #1', 'DOMNodeInserted event', 'end script #1', 'inline script #2', 'external script #1'],
+ ['inline script #1', 'DOMNodeInserted event', 'end script #1', 'external script #1', 'inline script #2']]);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/055.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/055.html
new file mode 100644
index 0000000000..c837d78174
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/055.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: removing newly inserted script from DOMNodeInserted handler - inline script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ document.addEventListener( 'DOMNodeInserted', function listener(e){
+ log('DOMNodeInserted event');
+ e.target.parentNode.removeChild(e.target);
+ document.removeEventListener('DOMNodeInserted', listener);
+ }, false );
+ var script=testlib.addScript('log(\'added script\')', { }, document.getElementsByTagName('body')[0], true );
+ log('end script #1');
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'added script', 'DOMNodeInserted event', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/056.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/056.html
new file mode 100644
index 0000000000..e2d0868034
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/056.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: appending code to initially empty SCRIPT tag in DOM </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=document.getElementsByTagName('script')[3];
+ script.appendChild( document.createTextNode('log("injected script code");') );
+ log('end script #1');
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ // Test asserts the injected script should run
+ assert_array_equals(eventOrder, ['inline script #1', 'injected script code', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/057.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/057.html
new file mode 100644
index 0000000000..4dc8e1384e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/057.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: appending code to initially non-empty SCRIPT tag in DOM (whitespace only) </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script>
+ </script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=document.getElementsByTagName('script')[3];
+ script.appendChild( document.createTextNode('log("injected script code");') );
+ log('end script #1');
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/058.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/058.html
new file mode 100644
index 0000000000..15deb785c2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/058.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: appending code to initially non-empty SCRIPT tag in DOM (comment only) </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script>/**/</script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=document.getElementsByTagName('script')[3];
+ script.appendChild( document.createTextNode('log("injected script code");') );
+ log('end script #1');
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/059.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/059.html
new file mode 100644
index 0000000000..b3a34367b2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/059.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: appending code to initially non-empty SCRIPT tag in DOM after removing its initial child </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script>/**/</script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=document.getElementsByTagName('script')[3];
+ script.removeChild(script.firstChild);
+ script.appendChild( document.createTextNode('log("injected script code");') );
+ log('end script #1');
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/060.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/060.html
new file mode 100644
index 0000000000..905dfe233f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/060.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: appending code to initially non-empty SCRIPT tag in DOM after setting textContent/innerHTML</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script>log('HEAD script');</script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=document.getElementsByTagName('script')[3];
+ script.innerHTML='';
+ script.appendChild( document.createTextNode('log("injected script code 1");') );
+ script.textContent='';
+ script.appendChild( document.createTextNode('log("injected script code 2");') );
+ log('end script #1');
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['HEAD script', 'inline script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/061.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/061.html
new file mode 100644
index 0000000000..9950b1c7ac
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/061.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: cloneNode and script execution</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script>log('HEAD script');</script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=document.getElementsByTagName('script')[3];
+ document.body.appendChild( script.cloneNode(true) );
+ log('end script #1');
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['HEAD script', 'inline script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/062.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/062.html
new file mode 100644
index 0000000000..c5e0ee2d46
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/062.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: cloneNode (shallow) and script execution</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script>log('HEAD script');</script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=document.getElementsByTagName('script')[3];
+ document.body.appendChild( script.cloneNode(false) ).appendChild(document.createTextNode('log("clone");'));
+ log('end script #1');
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['HEAD script', 'inline script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/063.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/063.html
new file mode 100644
index 0000000000..6824074a8c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/063.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: cloneNode (deep) of the currently executing script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script>log('HEAD script');
+ if( !window.already_cloned ){
+ window.already_cloned=true;
+ var script=document.getElementsByTagName('script')[3];
+ document.getElementsByTagName('head')[0].appendChild( script.cloneNode(true) );
+ }
+ </script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['HEAD script', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/064.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/064.html
new file mode 100644
index 0000000000..ceedc3da2d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/064.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: cloneNode with external script</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script src="scripts/include-1.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=document.getElementsByTagName('script')[3];
+ document.body.appendChild( script.cloneNode(true) );
+ log('end script #1');
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['external script #1', 'inline script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/065.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/065.html
new file mode 100644
index 0000000000..5859dc9e0b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/065.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: cloneNode with external script, changed .src</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script src="scripts/include-1.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=document.getElementsByTagName('script')[3].cloneNode(true);
+ script.src='scripts/include-2.js'
+ document.body.appendChild( script );
+ log('end script #1');
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['external script #1', 'inline script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/066.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/066.html
new file mode 100644
index 0000000000..a8e346dfd8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/066.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: cloneNode with external script, removing .src and adding content</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script src="scripts/include-1.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=document.getElementsByTagName('script')[3].cloneNode(true);
+ script.removeAttribute('src');
+ script.appendChild(document.createTextNode( 'log("cloned script");' ));
+ document.body.appendChild( script );
+ log('end script #1');
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['external script #1', 'inline script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/067.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/067.html
new file mode 100644
index 0000000000..7be0fd0ab0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/067.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: is a script with syntax error marked as "has run"? </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script>setup({allow_uncaught_exception:true})
+ var t = async_test()
+ </script>
+ <script>
+ log(This script will never run..
+ </script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>t.step(function() {
+ log('inline script #1');
+ var script=document.getElementsByTagName('script')[3].cloneNode(true);
+ script.removeChild(script.firstChild);
+ script.appendChild(document.createTextNode( 'log("cloned script");' ));
+ document.body.appendChild( script );
+ log('end script #1');
+ })
+ </script>
+ <script type="text/javascript">
+ t.step(function() {
+ log( 'inline script #2' );
+ });
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'inline script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+</script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/068.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/068.html
new file mode 100644
index 0000000000..a58158b6c0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/068.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: external script and parsing of markup added with document.write </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>
+ log('inline script #1');
+ document.write('<script src="scripts/find-foo.js">log(\'inline code in external script (not expected to run!!)\')<\/script>' + '<div id="foo"></div>');
+ log('end script #1');
+
+ var t = async_test()
+
+
+ function test() {
+ if(!window.findFooLoaded) {
+ return setTimeout(t.step_func(test),200);
+ }
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'found #foo element: NO']);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/069.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/069.html
new file mode 100644
index 0000000000..4d4aed2658
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/069.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: external files added through DOM should not block further parsing while loading</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script>
+ testlib.addScript('',{src:'scripts/find-body.js?pipe=trickle(d1)'},document.getElementsByTagName('head')[0], true );
+ </script>
+</head>
+<body>
+ <script>
+ testlib.addScript('', {src:'scripts/find-foo.js?pipe=trickle(d1)'}, document.getElementsByTagName('head')[0], true);
+ </script>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <p><span id="foo"></span></p>
+
+ <script type="text/javascript">
+ var t = async_test()
+
+ function test() {
+ if(!(window.findFooLoaded && window.findBodyLoaded)) {
+ return setTimeout(t.step_func(test), 200);
+ }
+ assert_any(assert_array_equals, eventOrder,
+ [['document.body: <BODY>', 'found #foo element: YES'],
+ ['found #foo element: YES', 'document.body: <BODY>']]);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/070.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/070.html
new file mode 100644
index 0000000000..4b82a9e83f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/070.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write into IFRAME a script that adds a SCRIPT through DOM</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <iframe></iframe>
+
+ <script type="text/javascript">
+ var doc = document.getElementsByTagName('iframe')[0].contentDocument;
+ doc.open();
+
+ log("calling document.write");
+ doc.write('<script>top.log("inline script #1");'+
+ 'var s=document.createElement("script");'+
+ 's.src="scripts/include-6.js?'+new Date().getTime()+'";'+
+ 'document.getElementsByTagName("head")[0].appendChild(s);'+
+ '<\/script>'+
+ '<div id="foo"></div>'+
+ '<script>top.log("inline script #2");<\/script>'
+ );
+
+ log("calling document.close");
+ doc.close();
+
+ var t = async_test()
+
+
+ function test() {
+ if(!window.include6Loaded) {
+ return setTimeout(t.step_func(test),200);
+ }
+ assert_array_equals(eventOrder, ['calling document.write', 'inline script #1', 'inline script #2', 'calling document.close', 'external script (#foo found? YES)']);
+ t.done();
+ }
+
+ onload = t.step_func(test)
+ </script>
+</head>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/071.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/071.html
new file mode 100644
index 0000000000..802c4a8ce3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/071.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write several scripts into IFRAME </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <iframe style="width:1px;height:1px"></iframe>
+
+ <script type="text/javascript">
+ var doc = document.getElementsByTagName('iframe')[0].contentDocument;
+ doc.open();
+
+ var html = '<html><head><title>test</title></head>'+
+ '<script>top.log("inline script #1");'+
+ '<\/script>'+
+ /* made url unique because Chrome will change
+ order depending on file cached status */
+ '<script src="scripts/include-6.js?'+new Date().getTime()+'"><\/script>'+
+ '</head>'+
+ '<body>'+
+ '<div id="foo"></div>'+
+ '</body></html>'+
+ '<script>top.log("inline script #2");<\/script>';
+ log("calling document.write");
+ doc.write(html);
+
+ log("calling document.close");
+ doc.close();
+
+ var t = async_test()
+
+
+ function test() {
+ if( !window.include6Loaded )return setTimeout(t.step_func(test),200); // try checking again if external script didn't run yet
+ assert_array_equals(eventOrder, ['calling document.write',
+ 'inline script #1',
+ 'calling document.close',
+ 'external script (#foo found? NO)',
+ 'inline script #2'
+ ]);
+ t.done();
+ }
+
+ onload = t.step_func(test)
+ </script>
+</head>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/072.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/072.html
new file mode 100644
index 0000000000..e502a35736
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/072.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write into IFRAME a script that creates new inline script in parent </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <iframe style="width:1px;height:1px"></iframe>
+
+ <script type="text/javascript">
+ var doc = document.getElementsByTagName('iframe')[0].contentDocument;
+ doc.open();
+
+ var html = '<html><head><title>test</title></head>'+
+ '<script>top.log("inline script #1");'+
+ '<\/script>'+
+ '</head>'+
+ '<body>'+
+ '<div id="foo"></div>'+
+ '</body></html>'+
+ '<script>top.testlib.addScript( \'log("inline script added to parent")\', null, top.document.body, true )<\/script>';
+ log("calling document.write");
+ doc.write(html);
+
+ log("calling document.close");
+ doc.close();
+
+ var t = async_test()
+
+
+ function test() {
+ assert_array_equals(eventOrder, ['calling document.write',
+ 'inline script #1',
+ 'inline script added to parent',
+ 'calling document.close',
+ ]);
+ t.done();
+ }
+
+ onload = t.step_func(test)
+ </script>
+</head>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/073.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/073.html
new file mode 100644
index 0000000000..6f65c4ae19
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/073.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write into IFRAME a script that creates new external script in parent </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <iframe style="width:1px;height:1px"></iframe>
+
+ <script type="text/javascript">
+ var doc = document.getElementsByTagName('iframe')[0].contentDocument;
+ doc.open();
+
+ var html = '<html><head><title>test</title></head>'+
+ '<script>top.log("inline script #1");'+
+ '<\/script>'+
+ '</head>'+
+ '<body>'+
+ '<div id="foo"></div>'+
+ '</body></html>'+
+ '<script>top.testlib.addScript( \'\', { src:\'scripts/include-1.js\' }, top.document.body, true )<\/script>';
+ log("calling document.write");
+ doc.write(html);
+
+ log("calling document.close");
+ doc.close();
+
+ var t = async_test()
+
+
+ function test() {
+
+ assert_array_equals(eventOrder, ['calling document.write',
+ 'inline script #1',
+ 'calling document.close',
+ 'external script #1'
+ ]);
+
+ t.done();
+ }
+
+ onload = t.step_func(test)
+ </script>
+</head>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/074.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/074.html
new file mode 100644
index 0000000000..70d7b88b74
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/074.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: document.write into IFRAME a script that creates new inline script in parent that again adds script to IFRAME </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <iframe style="width:1px;height:1px"></iframe>
+
+ <script type="text/javascript">
+ var doc = document.getElementsByTagName('iframe')[0].contentDocument;
+ doc.open();
+ var str1='';
+ var html = '<html><head><title>test</title></head>'+
+ '<script>top.log("inline script #1");'+
+ '<\/script>'+
+ '</head>'+
+ '<body>'+
+ '<script>top.testlib.addScript( \'top.log("inline script added to parent");top.doc.write( "<script>top.log(\\\\"inline script added to iframe\\\\")<\\\/script>");\', null, top.document.body, true ) <\/script>'+
+ '</body></html>';
+ log("calling document.write");
+ doc.write(html);
+
+ log("calling document.close");
+ doc.close();
+
+ var t = async_test()
+
+
+ function test() {
+ assert_array_equals(eventOrder, ['calling document.write',
+ 'inline script #1',
+ 'inline script added to parent',
+ 'inline script added to iframe',
+ 'calling document.close',
+ ]);
+ t.done();
+ }
+
+ onload = t.step_func(test)
+ </script>
+</head>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/075.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/075.html
new file mode 100644
index 0000000000..40ec9bbb6a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/075.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>dispatchEvent from child frame during document.write :-o </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ </head>
+ <body onclick="log('click event')">
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <iframe></iframe>
+ <script>
+ var doc = document.getElementsByTagName("iframe")[0].contentDocument;
+ doc.open("text/html");
+ doc.write('<html><head><title>event dispatcher</title></head><body>Before script<script>top.log("inline script before event");var ev = parent.document.createEvent("MouseEvents");ev.initMouseEvent("click", true, false, null, 0, 0, 0, 0, 0, false, false, false, false, 0, null);parent.document.body.dispatchEvent(ev);top.log("inline script after event");</sc'+'ript> After script</body>');
+ log( 'end main script' );
+
+
+ </script>
+
+<script>
+ var t = async_test()
+
+ function test() {
+ if(test.ran)return; test.ran=true;
+
+ assert_array_equals(eventOrder, ['inline script before event',
+ 'click event',
+ 'inline script after event',
+ 'end main script'
+ ]);
+ doc.close();
+ t.done();
+}
+
+ onload = t.step_func(test)
+ /* onload doesn't fire in this test, a fallback.. */
+ setTimeout(t.step_func(test), 800 );
+</script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/076.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/076.html
new file mode 100644
index 0000000000..2b8b692d92
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/076.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: adding and removing external and inline scripts </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>log('inline script #1');
+ var script=testlib.addScript('', {src:'scripts/include-1.js', onload:function(e){ e.target.parentNode.removeChild(e.target); }}, document.getElementsByTagName('head')[0], false );
+ var script=testlib.addScript( 'log( "dynamically added inline script" )', null, document.getElementsByTagName('head')[0], false );
+ script.parentNode.removeChild(script);
+ log('end script #1');
+ </script>
+
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ }
+ onload = t.step_func(function() {
+ assert_any(assert_array_equals, eventOrder, [['inline script #1', 'dynamically added inline script', 'end script #1', 'external script #1', 'inline script #2'],
+ ['inline script #1', 'dynamically added inline script', 'end script #1', 'inline script #2', 'external script #1']]);
+ t.done();
+ })
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/077.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/077.html
new file mode 100644
index 0000000000..dbcd16bea5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/077.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title> adding several types of scripts through the DOM and removing some of them confuses scheduler </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script type="text/javascript">
+ var head = document.getElementsByTagName('head')[0];
+ function createScript(url, contents) {
+ props = {};
+ if (url) {
+ props.src = url;
+ }
+ return testlib.addScript(contents, props, head, false);
+ }
+ var t = async_test()
+
+ function test() {
+ var script = createScript('data:text\/javascript,log("Script %231 ran")');
+ var script2 = createScript('','log("Script #2 ran")');
+ if(script2) {
+ head.removeChild(script2);
+ }
+ var script3 = createScript('data:text\/javascript, log("Script %233 ran"); createScript(\'\', \'log("Script %234 ran")\')');
+ if(script3) {
+ head.removeChild(script3);
+ }
+ setTimeout(t.step_func(function(){
+ assert_array_equals(eventOrder, ['Script #2 ran', 'Script #1 ran', 'Script #3 ran','Script #4 ran']);
+ t.done();
+ }), 400);
+
+ };
+ onload = t.step_func(test)
+ </script>
+ </head>
+ <body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ </body>
+</html*>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/078.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/078.html
new file mode 100644
index 0000000000..da4db4a2e6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/078.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title> adding several types of scripts through the DOM and removing some of them confuses scheduler (slow-loading scripts) </title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="testlib/testlib.js"></script>
+<script type="text/javascript">
+ setup({ explicit_done: true });
+ var head = document.getElementsByTagName('head')[0];
+ function createScript(url, contents) {
+ props = {};
+ if (url) {
+ props.src = url;
+ }
+ return testlib.addScript(contents, props, head, false);
+ }
+ var t = async_test()
+
+ function test() {
+ document.getElementById("log").textContent = "Please wait..."
+ var url = 'scripts/include-1.js?pipe=trickle(d1)';
+ var script = createScript(url);
+ var script2 = createScript('', 'log("Script #2 ran")');
+ head.removeChild(script2);
+ var url = 'scripts/include-2.js?pipe=trickle(d2)';
+ var script3 = createScript(url);
+ head.removeChild(script3);
+
+ setTimeout(t.step_func(function () {
+ done();
+ assert_array_equals(eventOrder, ['Script #2 ran', 'external script #1', 'external script #2']);
+ t.done();
+ }), 5500);
+
+ };
+ onload = t.step_func(test)
+</script>
+</head>
+<body>
+<div id="log">FAILED (This TC requires JavaScript enabled)</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/079.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/079.html
new file mode 100644
index 0000000000..8d684cebf2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/079.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title> setting location to javascript URL from event handler </title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="testlib/testlib.js"></script>
+<script type="text/javascript">
+log('inline script #1');
+var t = async_test()
+
+onload = t.step_func(function() {
+ log('onload handler');
+ document.getElementById("log").textContent = 'please wait...';
+ window.location='javascript:log("javascript: URL")';
+ setTimeout(t.step_func(function(){
+ log('timeout');
+ assert_array_equals(eventOrder, ['inline script #1', 'onload handler', 'onload ends', 'javascript: URL', 'timeout']);
+ t.done();
+ }), 200);
+ log('onload ends');
+});
+</script>
+</head>
+<body>
+<div id="log">FAILED (This TC requires JavaScript enabled)</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/081.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/081.html
new file mode 100644
index 0000000000..1b9bc991c6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/081.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: slow loading external script added with DOM (appendChild)</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div></div>
+ <script>
+ var t = async_test()
+ log('inline script #1');
+ testlib.addScript('', { src:'scripts/include-1.js?pipe=trickle(d1)&'+Math.random() }, document.getElementsByTagName('head')[0], false );
+ log('end script #1');
+ </script>
+ <script src="scripts/include-2.js"></script>
+ <script>
+ log( 'inline script #2' );
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'external script #2', 'inline script #2', 'external script #1']);
+ t.done();
+ }
+ onload = t.step_func(function() {
+ setTimeout(t.step_func(test), 12);
+ });
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/082.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/082.html
new file mode 100644
index 0000000000..3e88fc73d4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/082.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: multiple slow loading external scripts added with DOM (appendChild)</title>
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div></div>
+ <script>
+
+ log('inline script #1');
+ function scriptLoadListener(){
+ log( 'load on '+this.src.match( /include-\d\.js/ ) );
+ }
+ var script=testlib.addScript('', { src:'scripts/include-1.js?pipe=trickle(d1)&' + Math.random(), onload:scriptLoadListener }, document.getElementsByTagName('head')[0], false );
+ var script=testlib.addScript('', { src:'scripts/include-2.js?pipe=trickle(d3)&' + Math.random(), onload:scriptLoadListener }, document.getElementsByTagName('head')[0], false );
+ var script=testlib.addScript('', { src:'scripts/include-7.js?pipe=trickle(d2)&' + Math.random() , onload:scriptLoadListener }, document.getElementsByTagName('head')[0], false );
+ log('end script #1');
+ </script>
+ <script type="text/javascript">
+ log('inline script #2');
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'inline script #2', 'external script #1', 'load on include-1.js', 'external script #7', 'load on include-7.js', 'external script #2', 'load on include-2.js']);
+ t.done();
+ }
+ onload = function() {setTimeout(t.step_func(test), 12)};
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/083.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/083.html
new file mode 100644
index 0000000000..2ac0015c8b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/083.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: event listener defined by script in a document in history</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <iframe src="about:blank"></iframe>
+ <script>
+ log('inline script #1');
+ function fireFooEvent(){
+ var evt=document.createEvent('Event');
+ evt.initEvent('foo', true, true);
+ document.dispatchEvent(evt);
+ }
+ var doc = frames[0].document;
+ doc.open('text/html');
+ doc.write('<script>top.log("IFRAME script");top.document.addEventListener("foo", function(e){ top.log("event: "+e.type); }, false)<\/script>');
+ log('end script #1');
+ </script>
+ <script>
+ fireFooEvent();
+ frames[0].location='about:blank'; // returning to about:blank should de-activate document that defined event listener..?
+ </script>
+ <script>
+ fireFooEvent();
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_equals(frames[0].location.toString(), "about:blank");
+ assert_array_equals(eventOrder, ['inline script #1',
+ 'IFRAME script',
+ 'end script #1',
+ 'event: foo',
+ 'inline script #2'
+ ]);
+ t.done();
+ }
+ onload = function() {setTimeout(t.step_func(function() {fireFooEvent(); test()}), 80)};
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/084.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/084.html
new file mode 100644
index 0000000000..3027fa1994
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/084.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: event listener defined by script in a removed IFRAME</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <iframe src="about:blank"></iframe>
+ <script>
+ log('inline script #1');
+ function fireFooEvent(){
+ var evt=document.createEvent('Event');
+ evt.initEvent('foo', true, true);
+ document.dispatchEvent(evt);
+ }
+ var doc=frames[0].document;
+ doc.open( 'text/html' );
+ doc.write( '<script>top.log("IFRAME script");top.document.addEventListener("foo", function(e){ top.log("event: "+e.type); }, false)<\/script>' );
+ log('end script #1');
+ </script>
+ <script>
+ fireFooEvent();
+ frames[0].frameElement.parentNode.removeChild( frames[0].frameElement ); // removing the IFRAME should de-activate document that defined event listener..?
+ </script>
+ <script>
+ fireFooEvent();
+ </script>
+ <script type="text/javascript">
+ log( 'inline script #2' );
+ var t = async_test()
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1',
+ 'IFRAME script',
+ 'end script #1',
+ 'event: foo',
+ 'inline script #2'
+ ]);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/085.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/085.html
new file mode 100644
index 0000000000..6577527a8e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/085.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: async script and slow-loading defer script</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script src="scripts/include-1.js?pipe=trickle(d1)" defer></script>
+ <script src="scripts/include-2.js" async></script>
+
+ <script type="text/javascript">
+ var t = async_test();
+ function test() {
+ assert_array_equals(eventOrder, ['external script #2', 'external script #1']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/086.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/086.html
new file mode 100644
index 0000000000..4ccd16de89
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/086.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: async script and slow-loading async script</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script src="scripts/include-1.js?pipe=trickle(d2)" async></script>
+ <script src="scripts/include-2.js" async></script>
+
+ <script type="text/javascript">
+ var t = async_test();
+ function test() {
+ assert_array_equals(eventOrder, ['external script #2', 'external script #1']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/087.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/087.html
new file mode 100644
index 0000000000..8e225f48ae
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/087.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: multiple defer scripts, one slow loading</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script src="scripts/include-1.js?pipe=trickle(d2)" defer></script>
+ <script src="scripts/include-2.js" defer></script>
+
+ <script type="text/javascript">
+ var t = async_test();
+ function test() {
+ assert_array_equals(eventOrder, ['external script #1', 'external script #2']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/088.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/088.html
new file mode 100644
index 0000000000..f41f3d5be2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/088.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: multiple scripts with defer and async attributes</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script src="scripts/include-1.js?pipe=trickle(d2)" defer async></script>
+ <script src="scripts/include-2.js" defer async></script>
+
+ <script type="text/javascript">
+ var t = async_test();
+ function test() {
+ assert_array_equals(eventOrder, ['external script #2', 'external script #1']);
+ t.done();
+ }
+ onload = t.step_func(test);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/089.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/089.html
new file mode 100644
index 0000000000..9ed5e0e1da
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/089.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: async attribute on inline script</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script async>
+ var t = async_test();
+ log('inline script #1');
+ t.step(function() {
+ assert_array_equals(eventOrder, ['inline script #1']);
+ });
+ </script>
+ <script async>
+ log('inline script #2');
+ t.step(function() {
+ assert_array_equals(eventOrder, ['inline script #1', 'inline script #2']);
+ });
+ </script>
+
+ <script>
+ log('inline script #3');
+ t.step(function() {
+ assert_array_equals(eventOrder, ['inline script #1', 'inline script #2', 'inline script #3']);
+ });
+ onload = function() {t.done()};
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/090.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/090.html
new file mode 100644
index 0000000000..17d1d1effe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/090.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: defer attribute on inline script</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script defer>
+ var t = async_test();
+ log('inline script #1');
+ t.step(function() {
+ assert_array_equals(eventOrder, ['inline script #1']);
+ });
+ </script>
+ <script defer>
+ log('inline script #2');
+ t.step(function() {
+ assert_array_equals(eventOrder, ['inline script #1', 'inline script #2']);
+ });
+ </script>
+
+ <script>
+ log('inline script #3');
+ t.step(function() {
+ assert_array_equals(eventOrder, ['inline script #1', 'inline script #2', 'inline script #3']);
+ });
+ onload = function() {t.done()};
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/091.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/091.html
new file mode 100644
index 0000000000..f706cd31f5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/091.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: force-async off on non-parser-inserted script</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>
+ var t = async_test();
+
+ sources = ["scripts/include-1.js?pipe=trickle(d2)", "scripts/include-2.js?pipe=trickle(d1)"];
+ sources.forEach(function(x) {
+ var script = document.createElement("script");
+ script.src = x;
+ t.step(function() {assert_equals(script.async, true, "async IDL attribute on script creation")});
+ script.async = false;
+ t.step(function() {assert_equals(script.async, false, "async IDL attribute after setting")});
+ t.step(function() {assert_equals(script.getAttribute("async"), null, "async content attribute after setting")});
+ document.head.appendChild(script);
+ });
+
+ onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ['external script #1', 'external script #2']);
+ t.done();
+ });
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/092.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/092.html
new file mode 100644
index 0000000000..40fda5a0c0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/092.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: defer script and slow-loading non-async external script</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>
+ var t = async_test();
+
+ var script = document.createElement("script");
+ script.src = "scripts/include-2.js?pipe=trickle(d2)";
+ script.async = false;
+ document.head.appendChild(script);
+
+ onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ['external script #1', 'external script #2']);
+ t.done();
+ });
+ </script>
+ <script defer src="scripts/include-1.js"></script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/094.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/094.html
new file mode 100644
index 0000000000..cc9d1bf0fb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/094.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: parser-created defer script after document load</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <iframe id="myFrame"></iframe>
+
+ <script>
+ var t = async_test();
+ onload = t.step_func(function() {
+ var doc = document.getElementById("myFrame").contentDocument;
+ var win = document.getElementById("myFrame").contentWindow;
+ doc.open();
+ doc.write("<title> scheduler: parser-created defer script after document load</title><script src='/resources/testharness.js'><\/script><script src='/resources/testharnessreport.js'><\/script><script src='testlib/testlib.js'><\/script><script>var t=async_test()<\/script><div id=log></div><script defer src='data:text/javascript,parent.t.done();'><\/script>");
+ doc.close();
+ })
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/095.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/095.html
new file mode 100644
index 0000000000..5e3b388cf1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/095.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: slow-loading script added from defer blocking load event</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>
+ var t = async_test();
+ function test() {
+ t.step(function() {
+ assert_array_equals(eventOrder, ['external script #8', 'external script #9']);
+ t.done();
+ });
+ }
+ //assert that the test is completed before onload fires
+ onload = t.step_func(function() {assert_unreached()});
+ </script>
+ <script defer src="scripts/include-8.js"></script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/096.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/096.html
new file mode 100644
index 0000000000..a2e15b782a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/096.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: defer script added from document.write relative to DOMContentLoaded</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>
+ log("inline script #1");
+ document.write("<script defer src='scripts/include-1.js'><\/script>")
+ </script>
+ <script>
+ log("inline script #2");
+ var t = async_test();
+
+ addEventListener("DOMContentLoaded", t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "external script #1"]);
+ log("inline script #3");
+ }), false);
+
+ onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "external script #1", "inline script #3"]);
+ t.done();
+ });
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/097.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/097.html
new file mode 100644
index 0000000000..a31d49e5b0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/097.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: slow-loading async script added from document.write</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>
+ log("inline script #1");
+ document.write("<script async src='scripts/include-1.js?pipe=trickle(d2)'><\/script>")
+ </script>
+ <script>
+ log("inline script #2");
+ var t = async_test();
+
+ addEventListener("DOMContentLoaded", t.step_func(function() {assert_array_equals(eventOrder, ["inline script #1", "inline script #2"])}), false);
+
+ onload = t.step_func(
+ function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "external script #1"]);
+ t.done();
+ });
+
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/099.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/099.html
new file mode 100644
index 0000000000..987fcc7c71
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/099.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: defer adding iframe containing script</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script defer src="scripts/include-11.js"></script>
+ <script>
+ var t = async_test();
+
+ onload = t.step_func(function() {assert_array_equals(eventOrder, ["external script before adding iframe", "script in iframe"]); t.done();});
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/101.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/101.html
new file mode 100644
index 0000000000..b868f9a447
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/101.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: defer script after initial onload event</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <iframe id="myFrame"></iframe>
+
+ <script>
+ var t = async_test();
+ onload = t.step_func(
+ function() {
+ var doc = document.getElementById("myFrame").contentDocument;
+ var win = document.getElementById("myFrame").contentWindow;
+ doc.open();
+ doc.write("<title> scheduler: defer script after initial onload event</title><script src='testlib/testlib.js'><\/script><div id='log'>document.written content</div><script>log('inline script #1');<\/script><script src='scripts/include-1.js'><\/script><script defer src='scripts/include-2.js'><\/script>");
+ doc.close();
+ //Note that the *window* object has changed but the *global scope* of the script has not.
+ var run_t = window.t.step_func(function() {
+ if (!win.eventOrder || win.eventOrder.length != 3) {
+ window.setTimeout(run_t, 100);
+ return;
+ }
+ window.assert_array_equals(win.eventOrder, ['inline script #1', 'external script #1', 'external script #2']);
+ window.t.done();
+ });
+ run_t();
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/102.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/102.html
new file mode 100644
index 0000000000..439023833f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/102.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: defer script after initial onload event</title>
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>
+ onload = function() {
+ document.open();
+ document.write("<title> scheduler: defer script after initial onload event</title><script src='/resources/testharness.js'><\/script><script src='/resources/testharnessreport.js'><\/script><script src='testlib/testlib.js'><\/script><div id='log'>document.written content</div><script>var t = async_test(); log('inline script #1')<\/script><script src='scripts/include-1.js'><\/script><script async src='scripts/include-2.js'><\/script>");
+ document.close();
+ window.setTimeout(function() {
+ window.t.step(function() {
+ window.assert_any(window.assert_array_equals, window.eventOrder,
+ [['inline script #1', 'external script #1', 'external script #2'],
+ ['inline script #1', 'external script #2', 'external script #1']]);
+ window.t.done();
+ })},
+ 1000);
+ };
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/103.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/103.html
new file mode 100644
index 0000000000..f619472a4f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/103.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: removing defer attribute at runtime</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="test"></div>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script id="defer-script" defer src="scripts/include-2.js"></script>
+ <script src="scripts/include-1.js"></script>
+
+ <script>
+ var t = async_test();
+
+ t.step(function() {
+ document.getElementById("defer-script").removeAttribute("defer");
+ });
+
+ var ran_defer_check = false;
+
+ document.addEventListener("readystatechange", t.step_func(function () {
+ if (document.readyState == "interactive") {
+ ran_defer_check = true;
+ assert_array_equals(eventOrder, ["external script #1"]);
+ }
+ }), false);
+
+ addEventListener("load", t.step_func(function () {
+ assert_true(ran_defer_check);
+ assert_array_equals(eventOrder, ["external script #1", "external script #2"]);
+ t.done();
+ }), false);
+
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/104.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/104.html
new file mode 100644
index 0000000000..95a5a22237
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/104.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: adding defer attribute at runtime</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="test"></div>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script id="defer-script" src="scripts/include-1.js"></script>
+ <script src="scripts/include-2.js"></script>
+
+ <script>
+ var t = async_test();
+
+ t.step(function() {
+ document.getElementById("defer-script").setAttribute("defer", "defer");
+ });
+
+ var ran_defer_check = false;
+
+ document.addEventListener("readystatechange", t.step_func(function () {
+ if (document.readyState == "interactive") {
+ ran_defer_check = true;
+ assert_array_equals(eventOrder, ["external script #1", "external script #2"]);
+ }
+ }), false);
+
+ addEventListener("load", t.step_func(function () {
+ assert_true(ran_defer_check);
+ assert_array_equals(eventOrder, ["external script #1", "external script #2"]);
+ t.done();
+ }), false);
+
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/105.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/105.html
new file mode 100644
index 0000000000..19be9e1d03
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/105.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: adding async attribute at runtime</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>
+ var t = async_test();
+
+ var sources = ["scripts/include-1.js?pipe=trickle(d2)",
+ "scripts/include-2.js"]
+ var scripts = sources.map(function(x) {
+ var script = document.createElement("script");
+ script.src = x;
+ script.async = false;
+ document.body.appendChild(script);
+ return script;
+ });
+ scripts[0].async = true;
+
+ addEventListener("load", t.step_func(function () {
+ assert_array_equals(eventOrder, ["external script #1", "external script #2"]);
+ t.done();
+ }), false);
+
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-defer-import.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-defer-import.html
new file mode 100644
index 0000000000..451e218ef7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-defer-import.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: stylesheets blocking defer scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/import.css?pipe=trickle(d2)">
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div id="test">Test</div>
+
+ <script defer src="scripts/check-style-sheet.js"></script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-defer-noimport.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-defer-noimport.html
new file mode 100644
index 0000000000..704b880bcf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-defer-noimport.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: stylesheets blocking defer scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/background.css?pipe=trickle(d2)">
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div id="test">Test</div>
+
+ <script defer src="scripts/check-style-sheet.js"></script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-import.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-import.html
new file mode 100644
index 0000000000..4fe526a274
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-import.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: stylesheets blocking external parser-blocking scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/import.css?pipe=trickle(d2)">
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div id="test">Test</div>
+
+ <script src="scripts/check-style-sheet.js"></script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-module-import.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-module-import.html
new file mode 100644
index 0000000000..ea873746e0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-module-import.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: stylesheets blocking external module scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/import.css?pipe=trickle(d2)">
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div id="test">Test</div>
+
+ <script src="scripts/check-style-sheet.js" type="module"></script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-module-noimport.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-module-noimport.html
new file mode 100644
index 0000000000..71c59fb4d6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-module-noimport.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: stylesheets blocking external module scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/background.css?pipe=trickle(d2)">
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div id="test">Test</div>
+
+ <script src="scripts/check-style-sheet.js" type="module"></script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-noimport.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-noimport.html
new file mode 100644
index 0000000000..3694481b86
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-external-noimport.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: stylesheets blocking external parser-blocking scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/background.css?pipe=trickle(d2)">
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div id="test">Test</div>
+
+ <script src="scripts/check-style-sheet.js"></script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-import-xhtml.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-import-xhtml.xhtml
new file mode 100644
index 0000000000..3b2e8b5296
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-import-xhtml.xhtml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Stylesheet in XHTML HEAD with @import blocking scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/import.css?pipe=trickle(d2)" />
+</head>
+<body>
+ <div id="test">Test</div>
+ <script>
+ test(function() {
+ assert_equals(getComputedStyle(document.getElementById("test")).position,
+ "fixed");
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-import.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-import.html
new file mode 100644
index 0000000000..b8afeda135
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-import.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: stylesheets blocking scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/import.css?pipe=trickle(d2)">
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div id="test">Test</div>
+
+ <script>
+ test(function() {
+ assert_equals(getComputedStyle(document.getElementById("test")).position,
+ "fixed");
+ });
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-module-import.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-module-import.html
new file mode 100644
index 0000000000..d3f02ffd19
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-module-import.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: stylesheets blocking module scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/import.css?pipe=trickle(d2)">
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div id="test">Test</div>
+
+ <script type="module">
+ test(function() {
+ assert_equals(getComputedStyle(document.getElementById("test")).position,
+ "fixed");
+ });
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-module-noimport.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-module-noimport.html
new file mode 100644
index 0000000000..83cd29f267
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-module-noimport.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: stylesheets blocking module scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/background.css?pipe=trickle(d2)">
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div id="test">Test</div>
+
+ <script type="module">
+ test(function() {
+ assert_equals(getComputedStyle(document.getElementById("test")).position,
+ "fixed");
+ });
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-noimport-xhtml.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-noimport-xhtml.xhtml
new file mode 100644
index 0000000000..1cae3c9965
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-noimport-xhtml.xhtml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Stylesheet in XHTML HEAD blocking scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/background.css?pipe=trickle(d2)" />
+</head>
+<body>
+ <div id="test">Test</div>
+ <script>
+ test(function() {
+ assert_equals(getComputedStyle(document.getElementById("test")).position,
+ "fixed");
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-noimport.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-noimport.html
new file mode 100644
index 0000000000..bd8ec8633e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/106-noimport.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: stylesheets blocking scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/background.css?pipe=trickle(d2)">
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div id="test">Test</div>
+
+ <script>
+ test(function() {
+ assert_equals(getComputedStyle(document.getElementById("test")).position,
+ "fixed");
+ });
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/107-import.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/107-import.html
new file mode 100644
index 0000000000..0b572b0724
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/107-import.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: stylesheets blocking scripts document.write</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div id="test">Test</div>
+
+ <!-- this stylesheet blocks scripts -->
+ <script>
+ test(function() {
+ document.write("<link rel='stylesheet' href='css/import.css?pipe=trickle(d2)'>");
+ // note that the pass condition here is not per spec (but does match implementations) as of 2012-06-26
+ assert_equals(getComputedStyle(document.getElementById("test")).position,
+ "static");
+ });
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/107-noimport.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/107-noimport.html
new file mode 100644
index 0000000000..ce57d1f1c5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/107-noimport.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: stylesheets blocking scripts document.write</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div id="test">Test</div>
+
+ <!-- this stylesheet blocks scripts -->
+ <script>
+ test(function() {
+ document.write("<link rel='stylesheet' href='css/background.css?pipe=trickle(d2)'>");
+ // note that the pass condition here is not per spec (but does match implementations) as of 2012-06-26
+ assert_equals(getComputedStyle(document.getElementById("test")).position,
+ "static");
+ });
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/108.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/108.html
new file mode 100644
index 0000000000..79be9721d2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/108.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: javascript URL in iframe</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+ <div id="log">Not tested</div>
+ <script>
+ var t = async_test();
+ var iframe_onload = false;
+
+ t.step(function() {
+ log('inline script #1');
+ document.write("<iframe src='javascript:void(top.log(&quot;iframe script #1&quot;));'></iframe>");
+ log('inline script #2')
+ })
+
+ onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "iframe script #1"]);
+ t.done();
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/109.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/109.html
new file mode 100644
index 0000000000..d103ffcbd7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/109.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: javascript URL in iframe, src set via DOM</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+ <div id="log">Not tested</div>
+ <script>
+ var t = async_test();
+
+ t.step(function() {
+ var iframe_onload = false;
+ log("inline script #1");
+ var iframe = document.createElement("iframe");
+ iframe.src = "javascript:void(top.log('JS URL'));";
+ log("inline script #2");
+ iframe.onload = function () { log("iframe onload") };
+ document.body.appendChild(iframe);
+ log("inline script #3");
+ })
+
+ onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "inline script #3", "JS URL", "iframe onload"]);
+ t.done();
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/110.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/110.html
new file mode 100644
index 0000000000..5affb9ed23
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/110.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: removing defer script at runtime</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="test"></div>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script id="defer-script" defer src="scripts/include-2.js"></script>
+ <script src="scripts/include-1.js"></script>
+
+ <script>
+ var t = async_test();
+
+ t.step(function() {
+ var s = document.getElementById("defer-script");
+ s.parentNode.removeChild(s);
+ });
+
+ addEventListener("load", t.step_func(function () {
+ assert_array_equals(eventOrder, ["external script #1", "external script #2"]);
+ t.done();
+ }), false);
+
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/111.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/111.html
new file mode 100644
index 0000000000..c932a7b95c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/111.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: removing async attribute at runtime</title>
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="test"></div>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script id="async-script" async src="scripts/include-2.js?pipe=trickle(d3)"></script>
+
+ <script>
+ var t = async_test();
+
+ t.step(function() {
+ document.getElementById("async-script").removeAttribute("async");
+ var s = document.createElement("script");
+ s.async = false;
+ s.src = "scripts/include-1.js";
+ document.body.appendChild(s);
+ });
+
+ addEventListener("load", t.step_func(function () {
+ assert_array_equals(eventOrder, ["external script #1", "external script #2"]);
+ t.done();
+ }), false);
+
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/112.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/112.html
new file mode 100644
index 0000000000..a0cc647e0d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/112.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: removing async attribute at runtime, script also has defer attribute</title>
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="test"></div>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script id="async-script" async defer src="scripts/include-1.js?pipe=trickle(d3)"></script>
+
+ <script>
+ var t = async_test();
+ document.getElementById("async-script").removeAttribute("async");
+
+ addEventListener("DOMContentLoaded", t.step_func(function () {
+ assert_array_equals(eventOrder, []);
+ }), false);
+
+ addEventListener("load", t.step_func(function () {
+ assert_array_equals(eventOrder, ["external script #1"]);
+ t.done();
+ }), false);
+
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/113.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/113.html
new file mode 100644
index 0000000000..32740be37e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/113.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: Altering DOM using innerHTML during parse </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="test"></div>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script>
+ var t = async_test();
+
+ addEventListener("load", t.step_func(function () {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2"]);
+ t.done();
+ }), false);
+
+ </script>
+ <div id="container">
+ <script>t.step(function() {
+ log("inline script #1");
+ document.getElementById("container").innerHTML = "";
+ });
+ </script>
+ <script>t.step(function() {log("inline script #2")});</script>
+ </div>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/114.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/114.html
new file mode 100644
index 0000000000..ce3733208b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/114.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: Changing src of defer script before it runs </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="test"></div>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script id="defer-script" defer src="scripts/include-1.js"></script>
+
+ <script>
+ var t = async_test();
+
+ document.getElementById("defer-script").src = "scripts/include-2.js"
+
+ addEventListener("load", t.step_func(function () {
+ assert_array_equals(eventOrder, ["external script #1"]);
+ t.done();
+ }), false);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/115.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/115.html
new file mode 100644
index 0000000000..6234e02020
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/115.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html><head>
+ <title> scheduler: Removing src of defer script before it runs </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+
+ <div id="test"></div>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+
+ <script id="defer-script" defer src="scripts/include-1.js">t.step(function() {assert_unreached()})</script>
+
+ <script>
+ var t = async_test();
+
+ document.getElementById("defer-script").removeAttribute("src");
+
+ addEventListener("load", t.step_func(function () {
+ assert_array_equals(eventOrder, ["external script #1"]);
+ t.done();
+ }), false);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/116.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/116.html
new file mode 100644
index 0000000000..62da398868
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/116.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: adding script to head of frameset document</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+ <script>
+ // add a script that looks for document.body as first child of HEAD
+ testlib.addScript('',{src:'scripts/find-body.js'},document.getElementsByTagName('head')[0], true );
+ var div = document.createElement("div");
+ div.id = "log";
+ var t = async_test();
+ function test() {
+ if(!(window.findBodyLoaded)) {
+ return setTimeout(t.step_func(test),200);
+ }
+ document.body.appendChild(div);
+ assert_array_equals(eventOrder, ['document.body: <FRAMESET>']);
+ t.done();
+ }
+ onload = t.step_func(test)
+ </script>
+</head>
+<frameset>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/117.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/117.html
new file mode 100644
index 0000000000..46a9900c94
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/117.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: inline script created with createContextualFragment</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+ <div id="log"></div>
+ <script>
+ log('inline script #1');
+ var t = async_test();
+
+ t.step(function() {
+ var range = document.createRange();
+ var fragment = range.createContextualFragment("<script>log('fragment script #1')<\/script>");
+ document.body.appendChild(fragment.firstChild);
+ });
+
+ function test() {
+ assert_array_equals(eventOrder, ['inline script #1', 'fragment script #1', 'end inline script #1']);
+ t.done();
+ }
+ onload = t.step_func(test)
+ log('end inline script #1');
+ </script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/118.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/118.html
new file mode 100644
index 0000000000..e002ea9601
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/118.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: external script created with createContextualFragment</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+ <div id="log"></div>
+ <script>
+ log('inline script #1');
+ var t = async_test();
+
+ t.step(function() {
+ var range = document.createRange();
+ var fragment = range.createContextualFragment("<script src='scripts/include-1.js'><\/script>");
+ document.body.appendChild(fragment.firstChild);
+ });
+
+ addEventListener("load", t.step_func(function() {
+ assert_array_equals(eventOrder, ['inline script #1', 'end inline script #1', 'external script #1']);
+ t.done();
+ }), false);
+
+ log('end inline script #1');
+ </script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/119.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/119.html
new file mode 100644
index 0000000000..d1ed823093
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/119.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: external defer script created with createContextualFragment</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+ <div id="log"></div>
+ <script>
+ log('inline script #1');
+ var t = async_test();
+
+ t.step(function () {
+ var range = document.createRange();
+ var fragment = range.createContextualFragment("<script defer src='scripts/include-1.js?pipe=trickle(d1)'><\/script>");
+ document.body.appendChild(fragment.firstChild);
+ });
+
+ addEventListener("DOMContentLoaded", t.step_func(function () {
+ assert_array_equals(eventOrder, ['inline script #1', 'end inline script #1']);
+ }));
+
+ addEventListener("load", t.step_func_done(function () {
+ assert_array_equals(eventOrder, ['inline script #1', 'end inline script #1', 'external script #1']);
+ }));
+
+ log('end inline script #1');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/120.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/120.html
new file mode 100644
index 0000000000..2cfe5221dd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/120.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: script created without a window </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+
+ var doc = document.implementation.createHTMLDocument("");
+ doc.write("<script>t.step(function() {assert_unreached()})<\/script>");
+
+ document.body.appendChild(doc.head.firstChild);
+
+ onload = function() {t.done()}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/121.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/121.html
new file mode 100644
index 0000000000..d6de27025f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/121.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: altering the type attribute </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<script id="test" type="text/plain">t.step(function() {assert_unreached()}</script>
+<script>
+t.step(function() {
+ document.getElementById("test").removeAttribute("type");
+ setTimeout(t.step_func(function() {t.done()}), 100);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/122.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/122.html
new file mode 100644
index 0000000000..a8994c6aeb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/122.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: altering the type attribute and adding/removing external script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<script id="test" type="text/plain" src="scripts/include-1.js?pipe=trickle(d1)"></script>
+<script>
+t.step(function() {
+ var script = document.getElementById("test");
+ script.removeAttribute("type");
+ var marker = document.createElement("script");
+ marker.src = "scripts/include-2.js?pipe=trickle(d2)";
+ marker.async = false;
+ script.parentNode.appendChild(marker);
+ script.parentNode.appendChild(script);
+ test(function() {assert_true(script.async)}, "Reinserted script async IDL attribute");
+});
+onload = t.step_func(function () {
+ assert_array_equals(eventOrder, ["external script #1", "external script #2"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/123.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/123.html
new file mode 100644
index 0000000000..dc145eb550
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/123.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: altering the type attribute and adding/removing external script with async=false </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<script id="test" type="text/plain" src="scripts/include-2.js?pipe=trickle(d1)"></script>
+<script>
+t.step(function() {
+ var script = document.getElementById("test");
+ script.removeAttribute("type");
+ script.async = false;
+ var marker = document.createElement("script");
+ marker.src = "scripts/include-1.js?pipe=trickle(d2)";
+ marker.async = false;
+ script.parentNode.appendChild(marker);
+ script.parentNode.appendChild(script);
+});
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["external script #1", "external script #2"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/124.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/124.html
new file mode 100644
index 0000000000..5c7208dfde
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/124.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: altering the type attribute and changing script data inline script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<script id="test" type="text/plain">t.step(function() {log("inline script #1")});</script>
+<script>
+t.step(function() {
+ log("inline script #2");
+ var script = document.getElementById("test");
+ script.removeAttribute("type");
+ script.appendChild(document.createTextNode(""));
+ log("end inline script #2");
+});
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #2", "inline script #1", "end inline script #2"]);
+ t.done();
+})
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/125.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/125.html
new file mode 100644
index 0000000000..5074f2a107
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/125.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: altering the type attribute and changing script data external script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<script id="test" type="text/plain" src="scripts/include-1.js?pipe=trickle(d1)"></script>
+<script>
+t.step(function() {
+ var script = document.getElementById("test");
+ script.removeAttribute("type");
+ var marker = document.createElement("script");
+ marker.src = "scripts/include-2.js?pipe=trickle(d2)";
+ marker.async = false;
+ script.parentNode.appendChild(marker);
+ script.appendChild(document.createTextNode(""));
+});
+
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["external script #1", "external script #2"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/126.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/126.html
new file mode 100644
index 0000000000..1b2bb17634
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/126.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: altering the type attribute and changing script data external script async=false </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<script id="test" type="text/plain" src="scripts/include-2.js"></script>
+<script>
+t.step(function() {
+ var script = document.getElementById("test");
+ script.removeAttribute("type");
+ script.async = false;
+ var marker = document.createElement("script");
+ marker.src = "scripts/include-1.js?pipe=trickle(d2)";
+ marker.async = false;
+ script.parentNode.appendChild(marker);
+ script.appendChild(document.createTextNode(""));
+});
+
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["external script #1", "external script #2"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/127.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/127.html
new file mode 100644
index 0000000000..149078a327
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/127.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: appending non-text children to script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<script id="test"></script>
+<script>
+t.step(function() {
+ log("inline script #1");
+ var script = document.getElementById("test");
+
+ var frag = document.createDocumentFragment();
+ var div = document.createElement("div");
+
+ div.textContent = "assert_unreached();"
+ frag.appendChild(document.createTextNode("t.step(function() {log('inline script #2');\n"));
+ frag.appendChild(div);
+ frag.appendChild(document.createTextNode("log('end inline script #2');})"));
+
+ script.appendChild(frag);
+ log("end inline script #1");
+});
+
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "end inline script #2", "end inline script #1"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/128.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/128.html
new file mode 100644
index 0000000000..39ec24f681
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/128.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: appending script element to script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<script id="test"></script>
+<script>
+t.step(function() {
+ log("inline script #1");
+ var script = document.getElementById("test");
+
+ var frag = document.createDocumentFragment();
+ var inner_script = document.createElement("script");
+
+ inner_script.textContent = "t.step(function() {log('inline script #3');});"
+ frag.appendChild(document.createTextNode("t.step(function() {log('inline script #2');\n"));
+ frag.appendChild(inner_script);
+ frag.appendChild(document.createTextNode("log('end inline script #2');})"));
+
+ script.appendChild(frag);
+ log("end inline script #1");
+});
+
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #3", "inline script #2", "end inline script #2", "end inline script #1"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/129.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/129.html
new file mode 100644
index 0000000000..8c12735677
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/129.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: appending multiple script elements</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<div id="container"></div>
+<script>
+t.step(function() {
+ log("inline script #1");
+
+ var frag = document.createDocumentFragment();
+
+ scripts = ["2", "3", "4"].map(function(x) {
+ var s = document.createElement("script");
+ s.textContent = "t.step(function() {log('inline script #" + x + "')});";
+ return s
+ });
+
+
+ frag.appendChild(scripts[0]);
+ var div = document.createElement(div);
+ div.appendChild(scripts[1]);
+ frag.appendChild(div);
+ frag.appendChild(scripts[2]);
+
+ document.getElementById("container").appendChild(frag);
+ log("end inline script #1");
+});
+
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "inline script #3", "inline script #4", "end inline script #1"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/130.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/130.html
new file mode 100644
index 0000000000..c6643d9fdf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/130.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: appending external script element to script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<script id="test"></script>
+<script>
+t.step(function() {
+ log("inline script #1");
+ var script = document.getElementById("test");
+
+ var frag = document.createDocumentFragment();
+ var inner_script = document.createElement("script");
+
+ inner_script.src = "scripts/include-1.js?pipe=trickle(d1)";
+ frag.appendChild(document.createTextNode("t.step(function() {log('inline script #2');\n"));
+ frag.appendChild(inner_script);
+ frag.appendChild(document.createTextNode("log('end inline script #2');})"));
+
+ script.appendChild(frag);
+ log("end inline script #1");
+});
+
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "end inline script #2", "end inline script #1", "external script #1"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/131.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/131.html
new file mode 100644
index 0000000000..541483ed6b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/131.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: inline svg script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+ log("inline script #1")
+</script>
+<svg>
+<script>log("inline script #2")</script>
+</svg>
+<script>
+log("inline script #3");
+t.step(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "inline script #3"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/132.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/132.html
new file mode 100644
index 0000000000..3edb959594
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/132.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: external svg script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+ log("inline script #1")
+</script>
+<svg>
+<script xlink:href="scripts/include-1.js"></script>
+</svg>
+<script>
+log("inline script #2");
+t.step(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "external script #1", "inline script #2"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/133.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/133.html
new file mode 100644
index 0000000000..e6b327f709
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/133.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: inline HTML script added by SVG script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+ log("inline script #1")
+</script>
+<svg>
+<script>
+log("inline script #2")
+var s = document.createElement("script");
+s.textContent = "log('inline script #3');";
+document.getElementsByTagName("svg")[0].appendChild(s);
+log("end inline script #2");
+</script>
+</svg>
+<script>
+log("inline script #4");
+t.step(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "inline script #3",
+ "end inline script #2", "inline script #4"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/134.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/134.html
new file mode 100644
index 0000000000..bb2ad4f66b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/134.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: external HTML script added by SVG script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+ log("inline script #1")
+</script>
+<svg>
+<script>
+log("inline script #2")
+var s = document.createElement("script");
+s.src = "scripts/include-1.js"
+document.getElementsByTagName("svg")[0].appendChild(s);
+log("end inline script #2");
+</script>
+</svg>
+<script>
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2",
+ "end inline script #2", "external script #1"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/135.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/135.html
new file mode 100644
index 0000000000..dd8b895218
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/135.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: external SVG script added by SVG script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+ log("inline script #1")
+</script>
+<svg>
+<script>
+log("inline script #2")
+var s = document.createElementNS("http://www.w3.org/2000/svg", "script");
+s.setAttributeNS("http://www.w3.org/1999/xlink", "href", "scripts/include-1.js");
+document.getElementsByTagName("svg")[0].appendChild(s);
+log("end inline script #2");
+</script>
+</svg>
+<script>
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2",
+ "end inline script #2", "external script #1"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/136.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/136.html
new file mode 100644
index 0000000000..b08a8b9798
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/136.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: DOM added external SVG script, force-async? </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<svg>
+<script>
+
+var s1 = document.createElement("script");
+s1.src = "scripts/include-1.js";
+s1.async = false;
+
+var s = document.createElementNS("http://www.w3.org/2000/svg", "script");
+s.setAttributeNS("http://www.w3.org/1999/xlink", "href", "scripts/include-2.js?pipe=trickle(d2)");
+
+document.getElementsByTagName("svg")[0].appendChild(s);
+document.getElementsByTagName("svg")[0].appendChild(s1);
+
+</script>
+</svg>
+<script>
+onload = t.step_func(function() {
+ <!-- assumes that the SVg script should be async -->
+ assert_array_equals(eventOrder, ["external script #1", "external script #2"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/137.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/137.html
new file mode 100644
index 0000000000..35a49b806d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/137.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: SVG script empty xlink:href</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<svg>
+<script xlink:href="">
+t.step(function() {assert_unreached()});
+</script>
+</svg>
+<script>
+onload = t.step_func(function() {
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/138.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/138.html
new file mode 100644
index 0000000000..0ff25471e0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/138.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: SVG script nested inlines</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<svg>
+<script>
+t.step(function() {
+log("inline script #2");
+var a = {
+ <script>
+ t.step(function() {log("inline script #1")})
+ </script>
+a:1}
+log("end inline script #2");
+});
+</script>
+</svg>
+<script>
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "end inline script #2"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/139.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/139.html
new file mode 100644
index 0000000000..7bb703d8a2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/139.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: SVG script nested external in inline</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<svg>
+<script>
+t.step(function() {
+log("inline script #1");
+var a = {
+ <script xlink:href="scripts/include-1.js">
+ t.step(function() {assert_unreached()})
+ </script>
+a:1}
+log("end inline script #1");
+});
+</script>
+</svg>
+<script>
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["external script #1", "inline script #1", "end inline script #1"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/140.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/140.html
new file mode 100644
index 0000000000..9b54d09d58
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/140.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: SVG script nested inline in external</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<svg>
+<script xlink:href="scripts/include-1.js">
+ <script>
+ t.step(function() {log("inline script #1")});
+ </script>
+</script>
+</svg>
+<script>
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "external script #1"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/141.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/141.html
new file mode 100644
index 0000000000..54aa3f3686
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/141.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: SVG inline script that document.writes inline script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<svg>
+<script>
+ t.step(function() {
+ log('inline script #1');
+ document.write("<" + "script>t.step(function() {log('inline script #2')})<" + "/script>");
+ log('end inline script #1');
+ });
+</script>
+</svg>
+<script>
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "end inline script #1"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/142.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/142.html
new file mode 100644
index 0000000000..d314eddb90
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/142.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: SVG inline script that document.writes external script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<svg>
+<script>
+ t.step(function() {
+ log('inline script #1');
+ document.write("<" + "script xlink:href='scripts/include-1.js'><" + "/script>");
+ log('end inline script #1');
+ });
+</script>
+<script>t.step(function() {log("inline script #2")});</script>
+</svg>
+<script>
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "end inline script #1", "external script #1", "inline script #2"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/143.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/143.html
new file mode 100644
index 0000000000..a0d9012f68
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/143.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: SVG nested inline script that document.writes inline script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<svg>
+<script>
+ t.step(function() {
+ log('inline script #3');
+ });
+ <script>
+ log("inline script #1")
+ document.write("<" + "script>t.step(function() {log('inline script #2')})<" + "/script><" + "/script>");
+ </script>
+ t.step(function() {
+ assert_unreached():
+ });
+</script>
+</svg>
+<script>
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "inline script #3"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/144.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/144.html
new file mode 100644
index 0000000000..3962c4d692
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/144.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: SVG inline script changing the type attribute </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<svg>
+<script type="text/plain">
+t.step(function() {assert_unreached()});
+</script>
+</svg>
+<script>
+t.step(function() {
+ var s = document.querySelector("svg > script");
+ s.textContent = "t.step(function() {log('inline script #1')})";
+ s.type = "";
+ s.parentNode.appendChild(s);
+});
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/145.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/145.html
new file mode 100644
index 0000000000..9e2d73bafc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/145.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: SVG inline script adding text to empty script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>
+ var t = async_test();
+</script>
+<svg>
+<script></script>
+</svg>
+<script>
+t.step(function() {
+ var s = document.querySelector("svg > script");
+ s.textContent = "t.step(function() {log('inline script #1')})";
+});
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/146-href.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/146-href.html
new file mode 100644
index 0000000000..6c0869db54
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/146-href.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: SVG script adding src attribute </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>var t = async_test();</script>
+<svg>
+<script></script>
+</svg>
+<script>
+t.step(function() {
+ var s = document.querySelector("svg > script");
+ s.setAttribute("href", "scripts/include-1.js");
+});
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["external script #1"]);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/146.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/146.html
new file mode 100644
index 0000000000..333ac3fa0f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/146.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: SVG script adding src attribute </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<div id="log"></div>
+<script>var t = async_test();</script>
+<svg>
+<script></script>
+</svg>
+<script>
+t.step(function() {
+ var s = document.querySelector("svg > script");
+ s.src = "scripts/include-1.js";
+});
+onload = t.step_func(function() {
+ // SVG <script> element uses href attribute, so src attribute is ignored.
+ assert_array_equals(eventOrder, []);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/147.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/147.html
new file mode 100644
index 0000000000..07dc4d97c9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/147.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: insert multiple inline scripts; first script moves subsequent scripts </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+var t = async_test();
+</script>
+<div id="container"></div>
+<script>
+t.step(function() {
+ log("inline script #1");
+ var container = document.getElementById("container");
+
+ var frag = document.createDocumentFragment();
+ var frag_script_1 = document.createElement("script");
+ var frag_script_2 = document.createElement("script");
+ frag_script_2.id = "movee";
+ var frag_script_3 = document.createElement("script");
+
+ frag_script_1.textContent = "t.step(function() {log('inline script #2'); var s = document.getElementById('movee'); s.parentNode.appendChild(s)});";
+ frag_script_2.textContent = "t.step(function() {log('inline script #3');})";
+ frag_script_3.textContent = "t.step(function() {log('inline script #4');})";
+
+ [frag_script_1, frag_script_2, frag_script_3].forEach(function(x) {frag.appendChild(x)});
+
+ container.appendChild(frag);
+});
+
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "inline script #3", "inline script #4"]);
+ t.done();
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/148.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/148.html
new file mode 100644
index 0000000000..e2da8e8f0b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/148.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: insert multiple inline scripts; first script deletes subsequent script </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+var t = async_test();
+</script>
+<div id="container"></div>
+<script>
+t.step(function() {
+ log("inline script #1");
+ var container = document.getElementById("container");
+
+ var frag = document.createDocumentFragment();
+ var frag_script_1 = document.createElement("script");
+ var frag_script_2 = document.createElement("script");
+ frag_script_2.id = "delete";
+ var frag_script_3 = document.createElement("script");
+
+ frag_script_1.textContent = "t.step(function() {log('inline script #2'); var s = document.getElementById('delete'); s.parentNode.removeChild(s)});";
+ frag_script_2.textContent = "t.step(function() {log('inline script #3');})";
+ frag_script_3.textContent = "t.step(function() {log('inline script #4');})";
+
+ [frag_script_1, frag_script_2, frag_script_3].forEach(function(x) {frag.appendChild(x)});
+
+ container.appendChild(frag);
+});
+
+onload = t.step_func(function() {
+ assert_array_equals(eventOrder, ["inline script #1", "inline script #2", "inline script #3", "inline script #4"]);
+ t.done();
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/149.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/149.html
new file mode 100644
index 0000000000..40594d8048
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/149.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html><head>
+ <title>scheduler: event/for attribute on script</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+attributes = [
+ {for:"window", event:"onload()", expect:true},
+ {for:"window", event:"onload", expect:true},
+ {for:" WINdow\t\n", event:"ONload\t\n", expect:true},
+ {for:"window", event:"load", expect:false},
+ {for:"window", event:"onpageshow", expect:false},
+ {for:"document", event:"onload", expect:false},
+]
+
+function test_maker(array_name) {
+ return function(x, i) {
+ var title = "for='" + x.for + "' event='" + x.event + "' " + array_name.replace("_", " ") + " " + (x.expect ? "executes immediately" : "does not execute");
+ script_content = "var d =" + array_name + "[" + i + "];"
+ script_content += x.expect?"d[1].step(function() {d[3] = true});":"d[1].step(function() {assert_unreached()});"
+ return [x, async_test(title), script_content, false];
+ }
+}
+
+parser_inserted = attributes.map(test_maker("parser_inserted"));
+dom_inserted = attributes.map(test_maker("dom_inserted"));
+
+parser_inserted.forEach(function(x) {
+ var d = x[0];
+ document.write("<script for='" + d.for + "' event='" + d.event + "'>" + x[2] + "<\/script>");
+});
+
+dom_inserted.forEach(function(x) {
+ var d = x[0];
+ var s = document.createElement("script");
+ s.setAttribute("event", d.event);
+ s.setAttribute("for", d.for);
+ s.textContent = x[2];
+ document.body.appendChild(s);
+});
+</script>
+
+<script>
+var all_tests = parser_inserted.concat(dom_inserted);
+
+all_tests.filter(function(x) {return x[0]["expect"]}).forEach(function(x) {var t = x[1]; t.step(function() {assert_true(x[3])});})
+
+onload = function() {
+ all_tests.forEach(function(x) {var t = x[1]; t.step(function() {t.done()})});
+}
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-import-xhtml.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-import-xhtml.xhtml
new file mode 100644
index 0000000000..d6144795db
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-import-xhtml.xhtml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Stylesheet in XHTML BODY with @import blocking scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+ <div id="log">FAILED (This TC requires JavaScript enabled)</div>
+ <div id="test">Test</div>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/import.css?pipe=trickle(d2)" />
+ <script>
+ test(function() {
+ assert_equals(getComputedStyle(document.getElementById("test")).position,
+ "fixed");
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-import.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-import.html
new file mode 100644
index 0000000000..40223ed104
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-import.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Stylesheet in BODY with @import blocking scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+ <div id="test">Test</div>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/import.css?pipe=trickle(d2)">
+ <script>
+ test(function() {
+ assert_equals(getComputedStyle(document.getElementById("test")).position,
+ "fixed");
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-noimport-xhtml.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-noimport-xhtml.xhtml
new file mode 100644
index 0000000000..fa587a2ddb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-noimport-xhtml.xhtml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Stylesheet in XHTML BODY blocking scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+ <div id="test">Test</div>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/background.css?pipe=trickle(d2)" />
+ <script>
+ test(function() {
+ assert_equals(getComputedStyle(document.getElementById("test")).position,
+ "fixed");
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-noimport.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-noimport.html
new file mode 100644
index 0000000000..b5829d4da5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/150-noimport.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Stylesheet in BODY blocking scripts</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="testlib/testlib.js"></script>
+</head>
+<body>
+ <div id="test">Test</div>
+ <!-- this stylesheet blocks scripts -->
+ <link rel="stylesheet" href="css/background.css?pipe=trickle(d2)">
+ <script>
+ test(function() {
+ assert_equals(getComputedStyle(document.getElementById("test")).position,
+ "fixed");
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/css/background.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/css/background.css
new file mode 100644
index 0000000000..86a155b811
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/css/background.css
@@ -0,0 +1 @@
+#test {position:fixed} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/css/import.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/css/import.css
new file mode 100644
index 0000000000..d1664c29a1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/css/import.css
@@ -0,0 +1 @@
+@import url("background.css") \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/non-external-no-import.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/non-external-no-import.html
new file mode 100644
index 0000000000..50836e0d7b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/non-external-no-import.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Module scripts with no imports always execute asynchronously</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="help" href="https://github.com/whatwg/html/issues/3746">
+</head>
+<body>
+<script>
+async_test(t => {
+ window.results = [];
+ window.logExecution = msg => window.results.push(msg);
+
+ const script = document.createElement('script');
+ script.type = 'module';
+ script.textContent = "window.logExecution('module')";
+ document.body.append(script);
+ window.logExecution('classic');
+
+ window.onload = t.step_func_done(e => {
+ assert_array_equals(window.results, ['classic', 'module']);
+ });
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/pages/helloworld-postMessage.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/pages/helloworld-postMessage.html
new file mode 100644
index 0000000000..2ed8731ceb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/pages/helloworld-postMessage.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html><head>
+ <title> TC component </title>
+</head>
+<body>
+
+ <p>This page should appear in popup or frame</p>
+
+ <script type="text/javascript">
+ var target = opener || top;
+ var id = location.search?' '+location.search.substring(1) : '';
+ target.log('frame/popup script'+id);
+ window.onload=function(){
+ target.log('load event inside frame/popup script'+id);
+ target.postMessage('msg evt frame/popup script'+id, '*');
+ }
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/pages/helloworld.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/pages/helloworld.html
new file mode 100644
index 0000000000..271bc8f569
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/pages/helloworld.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html><head>
+ <title> TC component </title>
+</head>
+<body>
+
+ <p>This page should appear in popup or frame</p>
+
+ <script type="text/javascript">
+ var target = top || opener;
+ var id = location.search?' '+parseInt(location.search.substring(1)) : '';
+ target.log('frame/popup script'+id);
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/check-style-sheet.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/check-style-sheet.js
new file mode 100644
index 0000000000..cbab154f5a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/check-style-sheet.js
@@ -0,0 +1,4 @@
+test(function() {
+ assert_equals(getComputedStyle(document.getElementById("test")).position,
+ "fixed");
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/count-script-tags.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/count-script-tags.js
new file mode 100644
index 0000000000..8fba4ecb3c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/count-script-tags.js
@@ -0,0 +1 @@
+log('script tags in DOM: '+document.getElementsByTagName('script').length); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/find-body.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/find-body.js
new file mode 100644
index 0000000000..1ce198f13e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/find-body.js
@@ -0,0 +1,4 @@
+log(
+ 'document.body: ' +
+ (document.body ? '<' + document.body.localName.toUpperCase() + '>' : null));
+var findBodyLoaded=true;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/find-foo.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/find-foo.js
new file mode 100644
index 0000000000..52d0ec91cb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/find-foo.js
@@ -0,0 +1,2 @@
+log('found #foo element: ' + ( document.getElementById('foo') ? 'YES' : 'NO' ));
+var findFooLoaded=true; \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-1.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-1.js
new file mode 100644
index 0000000000..8ff291ad57
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-1.js
@@ -0,0 +1 @@
+log('external script #1'); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-10.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-10.js
new file mode 100644
index 0000000000..8dc770ddc0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-10.js
@@ -0,0 +1 @@
+document.write("<script src='scripts/include-9.js?pipe=trickle(d2)' defer></script>");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-11.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-11.js
new file mode 100644
index 0000000000..016913c4b8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-11.js
@@ -0,0 +1,4 @@
+log("external script before adding iframe");
+var iframe = document.createElement("iframe");
+iframe.srcdoc = "<script>parent.log('script in iframe')</script>"
+document.body.appendChild(iframe);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-2.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-2.js
new file mode 100644
index 0000000000..31319423af
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-2.js
@@ -0,0 +1 @@
+log('external script #2'); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-3.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-3.js
new file mode 100644
index 0000000000..53352e0f83
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-3.js
@@ -0,0 +1,3 @@
+log('external script before doc write');
+document.write( '<script>log(\'document.write external script\');</script>');
+log('external script after doc write'); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-4.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-4.js
new file mode 100644
index 0000000000..0597a22624
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-4.js
@@ -0,0 +1,3 @@
+log('include-4 before doc write');
+document.write( '<script src="scripts/include-3.js"></script>');
+log('include-4 after doc write'); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-5.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-5.js
new file mode 100644
index 0000000000..52952d7379
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-5.js
@@ -0,0 +1,7 @@
+log('include-5 before removing scripts');
+var scripts=[].slice.call(document.getElementsByTagName('script'), 3);
+for(var i = 0; i < scripts.length; i++) {
+ var s = scripts[i];
+ s.parentNode.removeChild(s);
+}
+log('include-5 after removing scripts');
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-6.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-6.js
new file mode 100644
index 0000000000..77da2af232
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-6.js
@@ -0,0 +1,6 @@
+top.log(
+ 'external script (#foo found? ' +
+ (document.getElementById('foo') ? 'YES' : 'NO' ) +
+ ')'
+);
+top.include6Loaded=true; \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-7.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-7.js
new file mode 100644
index 0000000000..57c5508015
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-7.js
@@ -0,0 +1 @@
+log('external script #7'); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-8.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-8.js
new file mode 100644
index 0000000000..960f2129fe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-8.js
@@ -0,0 +1,4 @@
+log("external script #8");
+var s = document.createElement("script")
+s.src='scripts/include-9.js?pipe=trickle(d2)'
+document.body.appendChild(s);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-9.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-9.js
new file mode 100644
index 0000000000..9042882024
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/scripts/include-9.js
@@ -0,0 +1,2 @@
+log("external script #9");
+test(); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/testlib/testlib.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/testlib/testlib.js
new file mode 100644
index 0000000000..a6fd39426b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/testlib/testlib.js
@@ -0,0 +1,43 @@
+/*
+* Utility functions for script scheduler test
+*/
+(function(){ /* namespace hiding local variables like arOrderOfAllEvents from global scope */
+ window.testlib = {};
+ window.eventOrder = [];
+ var arNumberOfScriptsParsedPerEvent=[];
+ window.log = function (str){
+ eventOrder.push(str);
+ arNumberOfScriptsParsedPerEvent.push(document.getElementsByTagName('script').length);
+ }
+
+ window.testlib.addScript = function(source, attributes, parent, firstInParent,funcPrepare) {
+ try{
+ parent = parent||document.body;
+ var script = document.createElement('script');
+ if(funcPrepare) {
+ funcPrepare(script);
+ }
+ if(source)script.appendChild( document.createTextNode(source) );
+ for( var name in attributes){
+ if(/^on/i.test(name)) {
+ script[name] = attributes[name];
+ } else {
+ script.setAttribute(name, attributes[name]);
+ }
+ }
+ if (firstInParent && parent.firstChild) {
+ parent.insertBefore(script, parent.firstChild);
+ } else {
+ parent.appendChild(script);
+ }
+ } catch(e) {
+ log('ERROR when adding script to DOM!');
+ alert(e);
+ }
+ return script;
+ }
+
+ window.testlib.urlParam = function(relativeURL) {
+ return location.href.replace( /\d*\.html$/, relativeURL);
+ }
+})();
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/external-script-utf8.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/external-script-utf8.js
new file mode 100644
index 0000000000..eb442c97bc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/external-script-utf8.js
@@ -0,0 +1,5 @@
+(function() {
+ window.getSomeString = function() {
+ return "śćążź"; //<- these are five Polish letters, similar to scazz. It can be read correctly only with windows 1250 encoding.
+ };
+})();
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/external-script-windows1250.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/external-script-windows1250.js
new file mode 100644
index 0000000000..50de6932ba
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/external-script-windows1250.js
@@ -0,0 +1,5 @@
+(function() {
+ window.getSomeString = function() {
+ return "湿"; //<- these are five Polish letters, similar to scazz. It can be read correctly only with windows 1250 encoding.
+ };
+})();
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/alpha/base.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/alpha/base.html
new file mode 100644
index 0000000000..dc0fa9dabb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/alpha/base.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Script src with a base URL</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<base href=../beta/>
+<div id=log></div>
+<script>
+function do_test(path) {
+ test(function() {
+ assert_equals(path, "beta");
+ });
+}
+</script>
+<script src=test.js></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/alpha/test.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/alpha/test.js
new file mode 100644
index 0000000000..3cbbb12e2e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/alpha/test.js
@@ -0,0 +1 @@
+do_test("alpha");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/beta/test.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/beta/test.js
new file mode 100644
index 0000000000..4ce0f5338d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/beta/test.js
@@ -0,0 +1 @@
+do_test("beta");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/empty-with-base.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/empty-with-base.html
new file mode 100644
index 0000000000..edc2c3d6f8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/empty-with-base.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Script src with an empty URL</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<base href=unreachable.js>
+<div id=log></div>
+<script>
+async_test(function(t) {
+ window.unreachable = this.unreached_func("Should not load unreachable.js");
+ var queued = false;
+ var script = document.createElement("script");
+ script.onerror = this.step_func_done(function(ev) {
+ assert_equals(ev.type, "error");
+ assert_false(ev.bubbles, "bubbles");
+ assert_false(ev.cancelable, "cancelable");
+ assert_true(ev.isTrusted, "isTrusted");
+ assert_equals(ev.target, script);
+ assert_true(ev instanceof Event, "instanceof Event");
+ assert_class_string(ev, "Event");
+ assert_true(queued, "event should not be dispatched synchronously");
+ });
+ script.setAttribute("src", "");
+ document.body.appendChild(script);
+ queued = true;
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/empty.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/empty.html
new file mode 100644
index 0000000000..d127f1eb3b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/empty.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Script src with an empty URL</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+// For a better error message in case the UA tries to load "" (which resolves
+// to this document).
+setup({
+ "allow_uncaught_exception": true,
+});
+async_test(function(t) {
+ window.onerror = this.unreached_func("Should not get an error reported to " +
+ "the window before the script");
+ var queued = false;
+ var script = document.createElement("script");
+ script.onerror = this.step_func_done(function(ev) {
+ assert_equals(ev.type, "error");
+ assert_false(ev.bubbles, "bubbles");
+ assert_false(ev.cancelable, "cancelable");
+ assert_true(ev.isTrusted, "isTrusted");
+ assert_equals(ev.target, script);
+ assert_true(ev instanceof Event, "instanceof Event");
+ assert_class_string(ev, "Event");
+ assert_true(queued, "event should not be dispatched synchronously");
+ });
+ script.setAttribute("src", "");
+ document.body.appendChild(script);
+ queued = true;
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/failure.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/failure.html
new file mode 100644
index 0000000000..b49e51740f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/failure.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Script src with an invalid URL</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+async_test(function(t) {
+ var queued = false;
+ var script = document.createElement("script");
+ script.onerror = this.step_func_done(function(ev) {
+ assert_equals(ev.type, "error");
+ assert_false(ev.bubbles, "bubbles");
+ assert_false(ev.cancelable, "cancelable");
+ assert_true(ev.isTrusted, "isTrusted");
+ assert_equals(ev.target, script);
+ assert_true(ev instanceof Event, "instanceof Event");
+ assert_class_string(ev, "Event");
+ assert_true(queued, "event should not be dispatched synchronously");
+ });
+ script.setAttribute("src", "//[]");
+ document.body.appendChild(script);
+ queued = true;
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/unreachable.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/unreachable.js
new file mode 100644
index 0000000000..ca7fdba71f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/fetch-src/unreachable.js
@@ -0,0 +1 @@
+unreachable();
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/historical.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/historical.html
new file mode 100644
index 0000000000..1f1a91228c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/historical.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<title>Historical script element features should not be supported</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+function t(property) {
+ test(function() {
+ assert_false(property in document.createElement('script'));
+ }, 'script.' + property + ' should not be supported');
+}
+// added in https://github.com/whatwg/html/commit/69f83cf2eacf4543860ccb7abab0ff5bb1e8b594
+// removed in https://github.com/whatwg/html/commit/1a0b5e8377d59462e05a5cffda4b8592324a2785
+t('onbeforescriptexecute');
+t('onafterscriptexecute');
+
+var t_onbeforescriptexecute_attr = async_test('onbeforescriptexecute content attribute should not be supported');
+var t_onafterscriptexecute_attr = async_test('onafterscriptexecute content attribute should not be supported');
+var t_beforescriptexecute_event = async_test(function() {
+ addEventListener('beforescriptexecute', this.step_func(function() {
+ assert_unreached();
+ }), true);
+}, 'beforescriptexecute event should not be supported');
+var t_afterscriptexecute_event = async_test(function() {
+ addEventListener('afterscriptexecute', this.step_func(function() {
+ assert_unreached();
+ }), true);
+}, 'afterscriptexecute event should not be supported');
+
+var a = false;
+
+onload = function() {
+ t_onbeforescriptexecute_attr.step(function() {
+ assert_true(a);
+ this.done();
+ });
+ t_onafterscriptexecute_attr.step(function() {
+ assert_true(a);
+ this.done();
+ });
+ t_beforescriptexecute_event.step(function() {
+ assert_true(a);
+ this.done();
+ });
+ t_afterscriptexecute_event.step(function() {
+ assert_true(a);
+ this.done();
+ });
+};
+</script>
+<script onbeforescriptexecute="t_onbeforescriptexecute_attr.step(function() { assert_unreached(); });"
+ onafterscriptexecute="t_onafterscriptexecute_attr.step(function() { assert_unreached(); });">
+a = true;
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/dynamic-import-with-assertion-argument.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/dynamic-import-with-assertion-argument.any.js
new file mode 100644
index 0000000000..7efb2050b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/dynamic-import-with-assertion-argument.any.js
@@ -0,0 +1,18 @@
+// META: global=window,dedicatedworker,sharedworker
+
+promise_test(async test => {
+ const result = await import("./export-hello.js", { assert: { } });
+ assert_equals(result.default, "hello");
+}, "Dynamic import with an empty assert clause should succeed");
+
+promise_test(async test => {
+ return promise_rejects_js(test, TypeError,
+ import("./export-hello.js", { assert: { unsupportedAssertionKey: "unsupportedAssertionValue"} }),
+ "Dynamic import with an unsupported import assertion should fail");
+}, "Dynamic import with an unsupported import assertion should fail");
+
+promise_test(test => {
+ return promise_rejects_js(test, TypeError,
+ import("./export-hello.js", { assert: { type: "notARealType"} } ),
+ "Dynamic import with an unsupported type assertion should fail");
+}, "Dynamic import with an unsupported type assertion should fail");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/empty-assertion-clause.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/empty-assertion-clause.html
new file mode 100644
index 0000000000..3a7c371189
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/empty-assertion-clause.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Handling of empty import assertion clause</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that no error occurs when an empty import assertion clause is provided.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(window.log, ["hello", "empty-assertion-clause"]);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./empty-assertion-clause.js" onerror="unreachable()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/empty-assertion-clause.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/empty-assertion-clause.js
new file mode 100644
index 0000000000..6913dd61df
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/empty-assertion-clause.js
@@ -0,0 +1,2 @@
+import "./hello.js" assert { };
+log.push("empty-assertion-clause");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/empty-type-assertion.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/empty-type-assertion.js
new file mode 100644
index 0000000000..5bb9b1ddb8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/empty-type-assertion.js
@@ -0,0 +1,2 @@
+import "./hello.js#2" assert { type: "" };
+log.push("empty-type-assertion");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/export-hello.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/export-hello.js
new file mode 100644
index 0000000000..34b58e6e12
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/export-hello.js
@@ -0,0 +1 @@
+export default "hello";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/hello.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/hello.js
new file mode 100644
index 0000000000..2f34844460
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/hello.js
@@ -0,0 +1 @@
+log.push("hello"); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/invalid-import-errors-order.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/invalid-import-errors-order.html
new file mode 100644
index 0000000000..17c95faea0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/invalid-import-errors-order.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Handling of invalid module type import attributes</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ var log = [];
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that the errors order for invalid import declarations is" +
+ " specifier, then attribute key, and then type attribute value.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 3);
+ assert_equals(log[0].constructor, SyntaxError);
+ assert_equals(log[1].constructor, SyntaxError);
+ assert_equals(log[2].constructor, SyntaxError);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" onerror="unreachable()">
+ // unknown attribute is reported before invalid specifier
+ import "INVALID" assert { unknown: "foo" };
+</script>
+<script type="module" onerror="unreachable()">
+ // unknown attribute is reported before unknown type
+ import "./valid" assert { unknown: "foo", type: "unknown" };
+</script>
+<script type="module" onerror="unreachable()">
+ // unknown attribute is reported before invalid specifier in subsequent import
+ import "./valid" assert { unknown: "foo" };
+ import "INVALID";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/invalid-type-assertion-error.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/invalid-type-assertion-error.html
new file mode 100644
index 0000000000..0adcc47569
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/invalid-type-assertion-error.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Handling of invalid module type import assertions</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that invalid module type assertion leads to TypeError on window.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 4);
+ assert_equals(log[0].constructor, TypeError);
+ assert_equals(log[1].constructor, TypeError);
+ assert_equals(log[2].constructor, TypeError);
+ assert_equals(log[3].constructor, TypeError);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./invalid-type-assertion.js" onerror="unreachable()"></script>
+<script type="module" src="./empty-type-assertion.js" onerror="unreachable()"></script>
+<script type="module" src="./js-type-assertion.js" onerror="unreachable()"></script>
+<script type="module" src="./javascript-type-assertion.js" onerror="unreachable()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/invalid-type-assertion.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/invalid-type-assertion.js
new file mode 100644
index 0000000000..e28c0176d5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/invalid-type-assertion.js
@@ -0,0 +1,2 @@
+import "./hello.js#1" assert { type: "notARealType" };
+log.push("invalid-type-assertion");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/javascript-type-assertion.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/javascript-type-assertion.js
new file mode 100644
index 0000000000..cc0f531026
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/javascript-type-assertion.js
@@ -0,0 +1,2 @@
+import "./hello.js#4" assert { type: "javascript" };
+log.push("javascript-type-assertion");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/js-type-assertion.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/js-type-assertion.js
new file mode 100644
index 0000000000..c649c95ffe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/js-type-assertion.js
@@ -0,0 +1,2 @@
+import "./hello.js#3" assert { type: "js" };
+log.push("js-type-assertion");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/unsupported-assertion.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/unsupported-assertion.html
new file mode 100644
index 0000000000..72977347a7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/unsupported-assertion.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>Handling of unsupported assertion</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that invalid module assertion leads to SyntaxError on window.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 1);
+ assert_equals(log[0].constructor, SyntaxError);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./unsupported-assertion.js" onerror="unreachable()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/unsupported-assertion.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/unsupported-assertion.js
new file mode 100644
index 0000000000..45f6d60c9d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-assertions/unsupported-assertion.js
@@ -0,0 +1,2 @@
+import "./hello.js" assert { unsupportedAssertionKey: "unsupportedAssertionValue" };
+log.push("unsupported-assertion");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/dynamic-import-with-attributes-argument.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/dynamic-import-with-attributes-argument.any.js
new file mode 100644
index 0000000000..2addb0fcb9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/dynamic-import-with-attributes-argument.any.js
@@ -0,0 +1,18 @@
+// META: global=window,dedicatedworker,sharedworker
+
+promise_test(async test => {
+ const result = await import("./export-hello.js", { with: { } });
+ assert_equals(result.default, "hello");
+}, "Dynamic import with an empty with clause should succeed");
+
+promise_test(async test => {
+ return promise_rejects_js(test, TypeError,
+ import("./export-hello.js", { with: { unsupportedAssertionKey: "unsupportedAssertionValue"} }),
+ "Dynamic import with an unsupported import attribute should fail");
+}, "Dynamic import with an unsupported import attribute should fail");
+
+promise_test(test => {
+ return promise_rejects_js(test, TypeError,
+ import("./export-hello.js", { with: { type: "notARealType"} } ),
+ "Dynamic import with an unsupported type attribute should fail");
+}, "Dynamic import with an unsupported type attribute should fail");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/empty-attributes-clause.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/empty-attributes-clause.html
new file mode 100644
index 0000000000..0a8868b2f4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/empty-attributes-clause.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Handling of empty import attributes clause</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that no error occurs when an empty import attributes clause is provided.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(window.log, ["hello", "empty-attributes-clause"]);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./empty-attributes-clause.js" onerror="unreachable()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/empty-attributes-clause.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/empty-attributes-clause.js
new file mode 100644
index 0000000000..98c4573179
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/empty-attributes-clause.js
@@ -0,0 +1,2 @@
+import "./hello.js" with { };
+log.push("empty-attributes-clause");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/empty-type-attribute.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/empty-type-attribute.js
new file mode 100644
index 0000000000..72272b5102
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/empty-type-attribute.js
@@ -0,0 +1,2 @@
+import "./hello.js#2" with { type: "" };
+log.push("empty-type-assertion");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/export-hello.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/export-hello.js
new file mode 100644
index 0000000000..34b58e6e12
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/export-hello.js
@@ -0,0 +1 @@
+export default "hello";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/hello.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/hello.js
new file mode 100644
index 0000000000..2f34844460
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/hello.js
@@ -0,0 +1 @@
+log.push("hello"); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/invalid-import-errors-order.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/invalid-import-errors-order.html
new file mode 100644
index 0000000000..8c438f626b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/invalid-import-errors-order.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Handling of invalid module type import attributes</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ var log = [];
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that the errors order for invalid import declarations is" +
+ " specifier, then attribute key, and then type attribute value.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 3);
+ assert_equals(log[0].constructor, SyntaxError);
+ assert_equals(log[1].constructor, SyntaxError);
+ assert_equals(log[2].constructor, SyntaxError);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" onerror="unreachable()">
+ // unknown attribute is reported before invalid specifier
+ import "INVALID" with { unknown: "foo" };
+</script>
+<script type="module" onerror="unreachable()">
+ // unknown attribute is reported before unknown type
+ import "./valid" with { unknown: "foo", type: "unknown" };
+</script>
+<script type="module" onerror="unreachable()">
+ // unknown attribute is reported before invalid specifier in subsequent import
+ import "./valid" with { unknown: "foo" };
+ import "INVALID";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/invalid-type-attribute-error.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/invalid-type-attribute-error.html
new file mode 100644
index 0000000000..ac6d61a642
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/invalid-type-attribute-error.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Handling of invalid module type import attributes</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that invalid module type attribute leads to TypeError on window.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 4);
+ assert_equals(log[0].constructor, TypeError);
+ assert_equals(log[1].constructor, TypeError);
+ assert_equals(log[2].constructor, TypeError);
+ assert_equals(log[3].constructor, TypeError);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./invalid-type-attribute.js" onerror="unreachable()"></script>
+<script type="module" src="./empty-type-attribute.js" onerror="unreachable()"></script>
+<script type="module" src="./js-type-attribute.js" onerror="unreachable()"></script>
+<script type="module" src="./javascript-type-attribute.js" onerror="unreachable()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/invalid-type-attribute.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/invalid-type-attribute.js
new file mode 100644
index 0000000000..10e35b6b56
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/invalid-type-attribute.js
@@ -0,0 +1,2 @@
+import "./hello.js#1" with { type: "notARealType" };
+log.push("invalid-type-attribute");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/javascript-type-attribute.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/javascript-type-attribute.js
new file mode 100644
index 0000000000..568db18196
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/javascript-type-attribute.js
@@ -0,0 +1,2 @@
+import "./hello.js#4" with { type: "javascript" };
+log.push("javascript-type-attribute");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/js-type-attribute.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/js-type-attribute.js
new file mode 100644
index 0000000000..b7a454b4f2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/js-type-attribute.js
@@ -0,0 +1,2 @@
+import "./hello.js#3" with { type: "js" };
+log.push("js-type-attribute");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/unsupported-attribute.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/unsupported-attribute.html
new file mode 100644
index 0000000000..3656e465ce
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/unsupported-attribute.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>Handling of unsupported attribute</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that invalid module attribute leads to SyntaxError on window.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 1);
+ assert_equals(log[0].constructor, SyntaxError);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./unsupported-attribute.js" onerror="unreachable()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/unsupported-attribute.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/unsupported-attribute.js
new file mode 100644
index 0000000000..2fc6829cfb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/import-attributes/unsupported-attribute.js
@@ -0,0 +1,2 @@
+import "./hello.js" with { unsupportedAttributeKey: "unsupportedAttributeValue" };
+log.push("unsupported-attribute");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/is-module-goal.mjs b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/is-module-goal.mjs
new file mode 100644
index 0000000000..b533fc2e90
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/is-module-goal.mjs
@@ -0,0 +1 @@
+import "./serve-with-content-type.py?fn=is-module-goal.mjs&ct=text%2Fjavascript%3Bgoal=module"; \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/is-script-goal.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/is-script-goal.js
new file mode 100644
index 0000000000..069363dd40
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/is-script-goal.js
@@ -0,0 +1,3 @@
+with ({}) {
+ ;
+}; \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/array.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/array.json
new file mode 100644
index 0000000000..e77e32d338
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/array.json
@@ -0,0 +1 @@
+["en", "try"]
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/bom-utf-16be.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/bom-utf-16be.json
new file mode 100644
index 0000000000..d22a45a591
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/bom-utf-16be.json
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/bom-utf-16le.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/bom-utf-16le.json
new file mode 100644
index 0000000000..4d1aa264a6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/bom-utf-16le.json
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/bom-utf-8.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/bom-utf-8.json
new file mode 100644
index 0000000000..07ba933e86
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/bom-utf-8.json
@@ -0,0 +1 @@
+{ "data": "hello" } \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/charset-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/charset-2.html
new file mode 100644
index 0000000000..686178bfe1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/charset-2.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="windows-1250">
+<title>JSON modules: UTF-8 decoding</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script type="module">
+ import json from "../serve-with-content-type.py?fn=json-module-assertions/utf-8.json" assert { type: "json"};
+ test(() => {
+ assert_equals(json.data, "śćążź");
+ }, "JSON module should be loaded as utf-8 even though document's encoding is windows-1250");
+</script>
+<script type="module">
+ import json from "../serve-with-content-type.py?fn=json-module-assertions/windows-1250.json&ct=text/json%3Bcharset=windows-1250" assert { type: "json"};
+ test(() => {
+ assert_not_equals(json.data, "śćążź",
+ 'Should be decoded as UTF-8');
+ }, "JSON module should be loaded as utf-8 even if it is encoded in windows-1250 and served with a windows-1250 charset response header, and this document's encoding is windows-1250");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/charset-bom.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/charset-bom.any.js
new file mode 100644
index 0000000000..d2dbe3e468
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/charset-bom.any.js
@@ -0,0 +1,17 @@
+// META: global=window,dedicatedworker,sharedworker
+// META: script=/common/utils.js
+
+promise_test(async () => {
+ const jsonModule = await import('./bom-utf-8.json', { assert: { type: 'json' } });
+ assert_equals(jsonModule.default.data, 'hello');
+}, 'UTF-8 BOM should be stripped when decoding JSON module script');
+
+promise_test(async test => {
+ await promise_rejects_js(test, SyntaxError,
+ import('./bom-utf-16be.json', { assert: { type: 'json' } }), 'Expected parse error from UTF-16BE BOM');
+}, 'UTF-16BE BOM should result in parse error in JSON module script');
+
+promise_test(async test => {
+ await promise_rejects_js(test, SyntaxError,
+ import('./bom-utf-16le.json', { assert: { type: 'json' } }), 'Expected parse error from UTF-16LE BOM');
+}, 'UTF-16LE BOM should result in parse error in JSON module script');
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/charset.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/charset.html
new file mode 100644
index 0000000000..7c74b9bf6c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/charset.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>JSON modules: UTF-8 decoding</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script type="module" onerror="unreachable()">
+ import json from "../serve-with-content-type.py?fn=json-module-assertions/utf-8.json&ct=text/json%3Bcharset=utf-8" assert { type: "json"};
+ test(() => {
+ assert_equals(json.data, "śćążź");
+ }, "JSON module should be loaded as utf-8 when charset=utf8 is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import json from "../serve-with-content-type.py?fn=json-module-assertions/utf-8.json&ct=text/json%3Bcharset=shift-jis" assert { type: "json"};
+ test(() => {
+ assert_equals(json.data, "śćążź");
+ }, "JSON module should be loaded as utf-8 when charset=shift-jis is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import json from "../serve-with-content-type.py?fn=json-module-assertions/utf-8.json&ct=text/json%3Bcharset=windows-1252" assert { type: "json"};
+ test(() => {
+ assert_equals(json.data, "śćążź");
+ }, "JSON module should be loaded as utf-8 when charset=windows-1252 is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import json from "../serve-with-content-type.py?fn=json-module-assertions/utf-8.json&ct=text/json%3Bcharset=utf-7" assert { type: "json"};;
+ test(() => {
+ assert_equals(json.data, "śćążź");
+ }, "JSON module should be loaded as utf-8 when charset=utf-7 is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import json from "../serve-with-content-type.py?fn=json-module-assertions/windows-1250.json&ct=text/json%3Bcharset=windows-1250" assert { type: "json"};
+ test(() => {
+ assert_not_equals(json.data, "śćążź",
+ 'Should be decoded as UTF-8');
+ }, "JSON module should be loaded as utf-8 even if it is encoded in windows-1250 and served with a windows-1250 charset response header");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/cors-crossorigin-requests.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/cors-crossorigin-requests.html
new file mode 100644
index 0000000000..757af2901c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/cors-crossorigin-requests.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+<head>
+ <title>json-module-assertions-crossorigin</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <h1>json-module-assertions-crossorigin</h1>
+ <iframe id="import-WithCORS" src="crossorigin-import-with-cors.sub.html"></iframe>
+ <iframe id="import-NoCORS" src="crossorigin-import-without-cors.sub.html"></iframe>
+ <iframe id="import-parseerror-WithCors" src="crossorigin-import-parse-error-with-cors.sub.html"></iframe>
+ <script>
+
+ var tests = [
+ { "obj": async_test("Imported JSON module, cross-origin with CORS"), "id": "import-WithCORS", "expected": "imported JSON: 42" },
+ { "obj": async_test("Imported JSON module, cross-origin, missing CORS ACAO header"), "id": "import-NoCORS", "expected": "error" },
+ { "obj": async_test("Imported JSON module with parse error, cross-origin, with CORS"), "id": "import-parseerror-WithCors", "expected": "0-0" },
+ ];
+
+ window.addEventListener("load", function () {
+ tests.forEach(function (test) {
+ var target = document.getElementById(test.id);
+ test.obj.step(function () {
+ assert_equals(target.contentDocument._log, test.expected, "Unexpected _log value");
+ });
+ test.obj.done();
+ });
+ });
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/credentials-iframe.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/credentials-iframe.sub.html
new file mode 100644
index 0000000000..44e6402faa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/credentials-iframe.sub.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<script type="module">
+ import json from "./cross-origin.py?id=sameOriginNoneDescendant&origin=http://{{host}}:{{ports[http][0]}}" assert { type: "json" };
+ window.sameOriginNoneDescendant = json.requestHadCookies;
+</script>
+<script type="module" crossOrigin="anonymous">
+ import json from "./cross-origin.py?id=sameOriginAnonymousDescendant&origin=http://{{host}}:{{ports[http][0]}}" assert { type: "json" };
+ window.sameOriginAnonymousDescendant = json.requestHadCookies;
+</script>
+<script type="module" crossOrigin="use-credentials">
+ import json from "./cross-origin.py?id=sameOriginUseCredentialsDescendant&origin=http://{{host}}:{{ports[http][0]}}" assert { type: "json" };
+ window.sameOriginUseCredentialsDescendant = json.requestHadCookies;
+</script>
+<script type="module">
+ import json from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module-assertions/cross-origin.py?id=crossOriginNoneDescendant&origin=http://{{host}}:{{ports[http][0]}}" assert { type: "json" };
+ window.crossOriginNoneDescendant = json.requestHadCookies;
+</script>
+<script type="module" crossOrigin="anonymous">
+ import json from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module-assertions/cross-origin.py?id=crossOriginAnonymousDescendant&origin=http://{{host}}:{{ports[http][0]}}" assert { type: "json" };
+ window.crossOriginAnonymousDescendant = json.requestHadCookies;
+</script>
+<script type="module" crossOrigin="use-credentials">
+import json from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module-assertions/cross-origin.py?id=crossOriginUseCredentialsDescendant&origin=http://{{host}}:{{ports[http][0]}}" assert { type: "json" };
+window.crossOriginUseCredentialsDescendant = json.requestHadCookies;
+</script>
+
+<script type="text/javascript">
+window.addEventListener('load', event => {
+ window.parent.postMessage({}, '*');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/credentials.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/credentials.sub.html
new file mode 100644
index 0000000000..d11d2f9589
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/credentials.sub.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+document.cookie = 'milk=1';
+
+const setCookiePromise = fetch(
+ 'http://{{domains[www2]}}:{{ports[http][0]}}/cookies/resources/set-cookie.py?name=milk&path=/html/semantics/scripting-1/the-script-element/json-module-assertions/',
+ {
+ mode: 'no-cors',
+ credentials: 'include',
+ });
+
+const windowLoadPromise = new Promise(resolve => {
+ window.addEventListener('load', () => {
+ resolve();
+ });
+});
+
+promise_test(t => {
+ const iframe = document.createElement('iframe');
+
+ return Promise.all([setCookiePromise, windowLoadPromise]).then(() => {
+ const messagePromise = new Promise(resolve => {
+ window.addEventListener('message', event => {
+ resolve();
+ });
+ });
+
+ iframe.src = 'credentials-iframe.sub.html';
+ document.body.appendChild(iframe);
+
+ return messagePromise;
+ }).then(() => {
+ const w = iframe.contentWindow;
+
+ assert_equals(w.sameOriginNoneDescendant, true,
+ 'Descendant JSON modules should be loaded with the credentials when the crossOrigin attribute is not specified and the target is same-origin');
+ assert_equals(w.sameOriginAnonymousDescendant, true,
+ 'Descendant JSON modules should be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is same-origin');
+ assert_equals(w.sameOriginUseCredentialsDescendant, true,
+ 'Descendant JSON modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is same-origin');
+ assert_equals(w.crossOriginNoneDescendant, false,
+ 'Descendant JSON modules should not be loaded with the credentials when the crossOrigin attribute is not specified and the target is cross-origin');
+ assert_equals(w.crossOriginAnonymousDescendant, false,
+ 'Descendant JSON modules should not be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is cross-origin');
+ assert_equals(w.crossOriginUseCredentialsDescendant, true,
+ 'Descendant JSON modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is cross-origin');
+});
+}, 'JSON Modules should be loaded with or without the credentials based on the same-origin-ness and the crossOrigin attribute');
+</script>
+<body>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/cross-origin.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/cross-origin.py
new file mode 100644
index 0000000000..cd56c3628a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/cross-origin.py
@@ -0,0 +1,16 @@
+def main(request, response):
+
+ headers = [
+ (b"Content-Type", b"application/json"),
+ (b"Access-Control-Allow-Origin", request.GET.first(b"origin")),
+ (b"Access-Control-Allow-Credentials", b"true")
+ ]
+
+ milk = request.cookies.first(b"milk", None)
+
+ if milk is None:
+ return headers, u'{"requestHadCookies": false}'
+ elif milk.value == b"1":
+ return headers, u'{"requestHadCookies": true}'
+
+ return headers, u'{"requestHadCookies": false}'
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/crossorigin-import-parse-error-with-cors.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/crossorigin-import-parse-error-with-cors.sub.html
new file mode 100644
index 0000000000..7d044f8579
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/crossorigin-import-parse-error-with-cors.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>json-module-assertions-import-cross-domain-parse-error-WithCORS</title>
+ <script src="../module/crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>json-module-assertions-import-cross-domain-parse-error-WithCORS</h1>
+ <script type="module" crossorigin>
+ import json from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module-assertions/parse-error.json?pipe=header(Access-Control-Allow-Origin,*)" assert { type: "json" };
+ // Push an event to the log indicating that the script was executed.
+ document._log.push(`imported JSON: ${json.answer}`);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/crossorigin-import-with-cors.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/crossorigin-import-with-cors.sub.html
new file mode 100644
index 0000000000..d71938dae4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/crossorigin-import-with-cors.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>json-module-assertions-import-cross-domain-WithCORS</title>
+ <script src="../module/crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>json-module-assertions-import-cross-domain-WithCORS</h1>
+ <script type="module" crossorigin>
+ import json from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module-assertions/data.json?pipe=header(Access-Control-Allow-Origin,*)" assert { type: "json" };
+ // Push an event to the log indicating that the script was executed.
+ document._log.push(`imported JSON: ${json.answer}`);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/crossorigin-import-without-cors.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/crossorigin-import-without-cors.sub.html
new file mode 100644
index 0000000000..9d07d6c727
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/crossorigin-import-without-cors.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>json-module-assertions-import-cross-domain-NoCORS</title>
+ <script src="../module/crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>json-module-assertions-import-cross-domain-NoCORS</h1>
+ <script type="module" onerror="document._log.push('error');">
+ import json from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module-assertions/data.json" assert { type: "json" };
+ // Push an event to the log indicating that the script was executed.
+ document._log.push(`imported JSON: ${json.answer}`);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/data.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/data.json
new file mode 100644
index 0000000000..14a0526ebb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/data.json
@@ -0,0 +1,3 @@
+{
+ "answer": 42
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/false.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/false.json
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/false.json
@@ -0,0 +1 @@
+false
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/integrity-matches.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/integrity-matches.js
new file mode 100644
index 0000000000..969c90c290
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/integrity-matches.js
@@ -0,0 +1,2 @@
+import json from "./data.json" assert { type: "json" };
+window.matchesLog.push(`integrity-matches,json:${json.answer}`);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/integrity-mismatches.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/integrity-mismatches.js
new file mode 100644
index 0000000000..3c88a98dbc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/integrity-mismatches.js
@@ -0,0 +1,2 @@
+import json "./data.json" assert { type: "json" };
+window.mismatchesLog.push(`integrity-mismatches,json:${json.answer}`);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/integrity.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/integrity.html
new file mode 100644
index 0000000000..68a794b973
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/integrity.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>&lt;script> integrity=""</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+window.matchesLog = [];
+window.matchesEvents = [];
+
+window.mismatchesLog = [];
+window.mismatchesEvents = [];
+</script>
+<script type="module" src="integrity-matches.js" integrity="sha384-VmQQfGzBiLKdyzw4FA4kL4ohu4tyujV68ddgW1aN/1v3cBZNNBn2gDFdVQxfL7+a" onload="window.matchesEvents.push('load');" onerror="window.matchesEvents.push('error')"></script>
+<script type="module" src="integrity-mismatches.js" integrity="sha384-doesnotmatch" onload="window.mismatchesEvents.push('load');" onerror="window.mismatchesEvents.push('error')"></script>
+
+<script type="module">
+test(() => {
+ assert_array_equals(window.matchesLog, ["integrity-matches,json:42"], "The module and its dependency must have executed");
+ assert_array_equals(window.matchesEvents, ["load"], "The load event must have fired");
+}, "The integrity attribute must be verified on the top-level of a module loading a JSON module and allow it to execute when it matches");
+
+test(() => {
+ assert_array_equals(window.mismatchesLog, [], "The module and its dependency must not have executed");
+ assert_array_equals(window.mismatchesEvents, ["error"], "The error event must have fired");
+}, "The integrity attribute must be verified on the top-level of a module loading a JSON module and not allow it to execute when there's a mismatch");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/invalid-content-type.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/invalid-content-type.any.js
new file mode 100644
index 0000000000..cbccbd4842
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/invalid-content-type.any.js
@@ -0,0 +1,17 @@
+// META: global=window,dedicatedworker,sharedworker
+
+const content_types = [
+ "application/json+protobuf",
+ "application/json+blah",
+ "text/x-json",
+ "text/json+blah",
+ "application/blahjson",
+ "image/json",
+];
+for (const content_type of content_types) {
+ promise_test(async test => {
+ await promise_rejects_js(test, TypeError,
+ import(`./module.json?pipe=header(Content-Type,${content_type})`, { assert: { type: "json"} }),
+ `Import of a JSON module with MIME type ${content_type} should fail`);
+ }, `Try importing JSON module with MIME type ${content_type}`);
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/json-module-service-worker-test.https.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/json-module-service-worker-test.https.html
new file mode 100644
index 0000000000..cc47da1499
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/json-module-service-worker-test.https.html
@@ -0,0 +1,29 @@
+<!doctype html>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+ promise_test(async (test) => {
+ const reg = await navigator.serviceWorker.register('./serviceworker.js', { type: 'module' });
+ test.add_cleanup(() => reg.unregister());
+ assert_not_equals(reg.installing, undefined);
+ }, "Javascript importing JSON Module should load within the context of a service worker");
+
+ promise_test(test => {
+ return promise_rejects_dom(test, "SecurityError",
+ navigator.serviceWorker.register('./module.json', { type: 'module' }),
+ "Attempting to load JSON as a service worker should fail");
+ }, "Trying to register a service worker with a top-level JSON Module should fail");
+
+ promise_test(async (test) => {
+ const reg = await navigator.serviceWorker.register('./serviceworker-dynamic-import.js', { type: 'module' });
+ test.add_cleanup(() => reg.unregister());
+ assert_not_equals(reg.installing, undefined);
+ reg.installing.postMessage("PING");
+ const msgEvent = await new Promise(resolve => {
+ navigator.serviceWorker.onmessage = resolve;
+ });
+ assert_equals(msgEvent.data, "FAILED");
+ }, "JSON Module dynamic import should not load within the context of a service worker");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/load-error-events.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/load-error-events.html
new file mode 100644
index 0000000000..a9dfc1e691
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/load-error-events.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<head>
+<title>load/error events for JSON modules</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/load-error-events-helpers.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+</head>
+<script>
+ "use strict";
+
+ var test1_load = event_test('inline, 200, parser-inserted', false, false);
+ var test1_error = event_test('inline, 404, parser-inserted', false, true);
+
+ var test2_load = event_test('src, 200, parser-inserted', true, false);
+ var test2_error = event_test('src, 404, parser-inserted', false, true);
+
+ var test3_dynamic_load = event_test('src, 200, not parser-inserted', true, false);
+ var test3_dynamic_error = event_test('src, 404, not parser-inserted', false, true);
+
+ var test4_dynamic_load = event_test('inline, 200, not parser-inserted', false, false);
+ var test4_dynamic_error = event_test('inline, 404, not parser-inserted', false, true);
+
+ var script3_dynamic_load = document.createElement('script');
+ script3_dynamic_load.setAttribute('type', 'module');
+ script3_dynamic_load.onload = () => onLoad(test3_dynamic_load);
+ script3_dynamic_load.onerror = () => onError(test3_dynamic_load);
+ script3_dynamic_load.src = "./load-error-events.py?test=test3_dynamic_load";
+ document.head.appendChild(script3_dynamic_load);
+
+ var script3_dynamic_error = document.createElement('script');
+ script3_dynamic_error.setAttribute('type', 'module');
+ script3_dynamic_error.onload = () => onLoad(test3_dynamic_error);
+ script3_dynamic_error.onerror = () => onError(test3_dynamic_error);
+ script3_dynamic_error.src = "./load-error-events.py?test=test3_dynamic_error";
+ document.head.appendChild(script3_dynamic_error);
+
+ var script4_dynamic_load = document.createElement('script');
+ script4_dynamic_load.setAttribute('type', 'module');
+ script4_dynamic_load.onload = () => onLoad(test4_dynamic_load);
+ script4_dynamic_load.onerror = () => onError(test4_dynamic_load);
+ script4_dynamic_load.async = true;
+ script4_dynamic_load.appendChild(document.createTextNode(`
+ import "./module.json" assert { type: "json" };
+ onExecute(test4_dynamic_load);`
+ ));
+ document.head.appendChild(script4_dynamic_load);
+
+ var script4_dynamic_error = document.createElement('script');
+ script4_dynamic_error.setAttribute('type', 'module');
+ script4_dynamic_error.onload = () => onLoad(test4_dynamic_error);
+ script4_dynamic_error.onerror = () => onError(test4_dynamic_error);
+ script4_dynamic_error.async = true;
+ script4_dynamic_error.appendChild(document.createTextNode(`import "./not_found.json" assert { type: "json" };`));
+ document.head.appendChild(script4_dynamic_error);
+</script>
+<script onload="onLoad(test1_load);" onerror="onError(test1_load);" type="module">
+ import "./module.json" assert { type: "json"};
+ onExecute(test1_load);
+</script>
+<script onload="onLoad(test1_error);" onerror="onError(test1_error);" type="module">
+ import "./not_found.json" assert { type: "json"};
+ onExecute(test1_error);
+</script>
+<script src="./load-error-events.py?test=test2_load" onload="onLoad(test2_load);" onerror="onError(test2_load);" type="module"></script>
+<script src="./load-error-events.py?test=test2_error" onload="onLoad(test2_error);" onerror="onError(test2_error);" type="module"></script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/load-error-events.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/load-error-events.py
new file mode 100644
index 0000000000..4018adcdf7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/load-error-events.py
@@ -0,0 +1,14 @@
+import re
+
+def main(request, response):
+ headers = [(b"Content-Type", b"text/javascript")]
+ test = request.GET.first(b'test')
+ assert(re.match(b'^[a-zA-Z0-9_]+$', test))
+
+ status = 200
+ if test.find(b'_load') >= 0:
+ content = b'import "./module.json" assert { type: "json"}; %s.executed = true;' % test
+ else:
+ content = b'import "./not_found.json" assert { type: "json"}; %s.test.step(function() { assert_unreached("404 script should not be executed"); });' % test
+
+ return status, headers, content
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/module.html
new file mode 100644
index 0000000000..a495d4ac18
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/module.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>JSON modules</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+const t = async_test();
+</script>
+<script type="module" onerror="t.step(() => assert_unreached(event))">
+import v from "./module.json" assert { type: "json" };
+t.step(() => {
+ assert_equals(typeof v, "object");
+ assert_array_equals(Object.keys(v), ["test"]);
+ assert_equals(v.test, true);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/module.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/module.json
new file mode 100644
index 0000000000..f834b2a4e8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/module.json
@@ -0,0 +1,3 @@
+{
+ "test": true
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/non-object.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/non-object.any.js
new file mode 100644
index 0000000000..37fbcae9fa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/non-object.any.js
@@ -0,0 +1,14 @@
+// META: global=window,dedicatedworker,sharedworker
+
+for (const value of [null, true, false, "string"]) {
+ promise_test(async t => {
+ const result = await import(`./${value}.json`, { assert: { type: "json" } });
+ assert_equals(result.default, value);
+ }, `Non-object: ${value}`);
+}
+
+promise_test(async t => {
+ const result = await import("./array.json", { assert: { type: "json" } });
+ assert_array_equals(result.default, ["en", "try"]);
+}, "Non-object: array");
+
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/null.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/null.json
new file mode 100644
index 0000000000..19765bd501
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/null.json
@@ -0,0 +1 @@
+null
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/parse-error.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/parse-error.html
new file mode 100644
index 0000000000..9d0e3284fe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/parse-error.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>JSON modules: parse error</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+setup({
+ allow_uncaught_exception: true,
+});
+async_test(t => {
+ window.addEventListener("error", t.step_func_done(e => {
+ assert_true(e instanceof ErrorEvent, "ErrorEvent");
+ assert_equals(e.filename, new URL("parse-error.json", location).href);
+ assert_true(e.error instanceof SyntaxError, "SyntaxError");
+ }));
+});
+</script>
+<script type="module">
+import v from "./parse-error.json" assert { type: "json" };
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/parse-error.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/parse-error.json
new file mode 100644
index 0000000000..98232c64fc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/parse-error.json
@@ -0,0 +1 @@
+{
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/referrer-checker.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/referrer-checker.py
new file mode 100644
index 0000000000..e9f0f1789b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/referrer-checker.py
@@ -0,0 +1,6 @@
+def main(request, response):
+ referrer = request.headers.get(b"referer", b"")
+ response_headers = [(b"Content-Type", b"application/json"),
+ (b"Access-Control-Allow-Origin", b"*")]
+ return (200, response_headers,
+ b'{"referrer": "' + referrer + b'"}')
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/referrer-policies.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/referrer-policies.sub.html
new file mode 100644
index 0000000000..655c962ab7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/referrer-policies.sub.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Referrers with JSON module requests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+ // "name" parameter is necessary for bypassing the module map.
+ import referrerSame from "./referrer-checker.py?name=sameNoReferrerPolicy" assert { type: "json"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module-assertions/referrer-checker.py?name=remoteNoReferrerPolicy" assert { type: "json"};
+
+ const origin = (new URL(location.href)).origin + "/";
+ const originUrl = location.href;
+
+ test(t => {
+ assert_equals(
+ referrerSame.referrer, originUrl,
+ "Referrer URL should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the default referrer policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.referrer, origin,
+ "Referrer origin should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the default referrer policy.");
+</script>
+<script type="module" referrerpolicy="origin">
+ import referrerSame from "./referrer-checker.py?name=sameReferrerPolicyOrigin" assert { type: "json"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module-assertions/referrer-checker.py?name=remoteReferrerPolicyOrigin" assert { type: "json"};
+
+ const origin = (new URL(location.href)).origin + "/";
+
+ test(t => {
+ assert_equals(
+ referrerSame.referrer, origin,
+ "Referrer origin should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the origin policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.referrer, origin,
+ "Referrer origin should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the origin policy.");
+
+</script>
+<script type="module" referrerpolicy="no-referrer">
+ import referrerSame from "./referrer-checker.py?name=sameReferrerPolicyNoReferrer" assert { type: "json"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module-assertions/referrer-checker.py?name=remoteReferrerPolicyNoReferrer" assert { type: "json"};
+
+ test(t => {
+ assert_equals(
+ referrerSame.referrer, "",
+ "No referrer should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the no-referrer policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.referrer, "",
+ "No referrer should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the no-referrer policy.");
+
+</script>
+<script type="module" referrerpolicy="unsafe-url">
+ import referrerSame from "./referrer-checker.py?name=sameNoReferrerPolicyUnsafeUrl" assert { type: "json"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module-assertions/referrer-checker.py?name=remoteNoReferrerPolicyUnsafeUrl" assert { type: "json"};
+
+ const originUrl = location.href;
+
+ test(t => {
+ assert_equals(
+ referrerSame.referrer, originUrl,
+ "Referrer URL should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the unsafe-url referrer policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.referrer, originUrl,
+ "Referrer URL should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the unsafe-url referrer policy.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/repeated-imports.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/repeated-imports.any.js
new file mode 100644
index 0000000000..5cc3ee5b7c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/repeated-imports.any.js
@@ -0,0 +1,65 @@
+// META: global=window,dedicatedworker,sharedworker
+// META: script=/common/utils.js
+
+promise_test(async test => {
+ await promise_rejects_js(test, TypeError,
+ import("./module.json"),
+ "Dynamic import of a JSON module without a type assertion should fail");
+
+ // This time the import should succeed because we're using the correct
+ // import even though the previous attempt with the same specifier failed.
+ const result = await import("./module.json", { assert: { type: "json" } });
+ assert_true(result.default.test);
+}, "Importing a specifier that previously failed due to an incorrect type assertion can succeed if the correct assertion is later given");
+
+promise_test(async test => {
+ // Append a URL fragment to the specifier so that this is independent
+ // from the previous test.
+ const result = await import("./module.json#2", { assert: { type: "json" } });
+ assert_true(result.default.test);
+
+ await promise_rejects_js(test, TypeError,
+ import("./module.json#2"),
+ "Dynamic import should fail with the type assertion missing even if the same specifier previously succeeded");
+}, "Importing a specifier that previously succeeded with the correct type assertion should fail if the incorrect assertion is later given");
+
+promise_test(async test => {
+ const uuid_token = token();
+ // serve-json-then-js.py gives us JSON the first time
+ const result_json = await import(`../serve-json-then-js.py?key=${uuid_token}`, { assert: { type: "json" } });
+ assert_equals(result_json.default.hello, "world");
+
+ // Import using the same specifier again; this time we get JS, which
+ // should succeed since we're not asserting a non-JS type this time.
+ const result_js = await import(`../serve-json-then-js.py?key=${uuid_token}`);
+ assert_equals(result_js.default, "hello");
+}, "Two modules of different type with the same specifier can load if the server changes its responses");
+
+promise_test(async test => {
+ const uuid_token = token();
+ // serve-json-then-js.py gives us JSON the first time
+ await promise_rejects_js(test, TypeError,
+ import(`../serve-json-then-js.py?key=${uuid_token}`),
+ "Dynamic import of JS with a JSON type assertion should fail");
+
+ // Import using the same specifier/module type pair again; this time we get JS,
+ // but the import should still fail because the module map entry for this
+ // specifier/module type pair already contains a failure.
+ await promise_rejects_js(test, TypeError,
+ import(`../serve-json-then-js.py?key=${uuid_token}`),
+ "import should always fail if the same specifier/type assertion pair failed previously");
+}, "An import should always fail if the same specifier/type assertion pair failed previously");
+
+promise_test(async test => {
+ const uuid_token = token();
+ // serve-json-then-js.py gives us JSON the first time
+ const result_json = await import(`../serve-json-then-js.py?key=${uuid_token}`, { assert: { type: "json" } });
+ assert_equals(result_json.default.hello, "world");
+
+ // If this were to do another fetch, the import would fail because
+ // serve-json-then-js.py would give us JS this time. But, the module map
+ // entry for this specifier/module type pair already exists, so we
+ // successfully reuse the entry instead of fetching again.
+ const result_json_2 = await import(`../serve-json-then-js.py?key=${uuid_token}`, { assert: { type: "json" } });
+ assert_equals(result_json_2.default.hello, "world");
+}, "If an import previously succeeded for a given specifier/type assertion pair, future uses of that pair should yield the same result");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/script-element-json-src.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/script-element-json-src.html
new file mode 100644
index 0000000000..c6d7c9a76e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/script-element-json-src.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>&lt;script&gt; with JSON src</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ const test_load = async_test(
+ "Test that <script> doesn't load when the src is JSON.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(log, ["error"]);
+ }));
+</script>
+<script type="module" src="./module.json" onload="t.unreached_func('JSON src should fail to load')" onerror="log.push('error')"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/serviceworker-dynamic-import.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/serviceworker-dynamic-import.js
new file mode 100644
index 0000000000..9466c6fbe4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/serviceworker-dynamic-import.js
@@ -0,0 +1,5 @@
+onmessage = e => {
+ e.waitUntil(import("./module.json", { assert: { type: "json" } })
+ .then(module => e.source.postMessage("LOADED"))
+ .catch(error => e.source.postMessage("FAILED")));
+ }; \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/serviceworker.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/serviceworker.js
new file mode 100644
index 0000000000..3f0a4d1664
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/serviceworker.js
@@ -0,0 +1 @@
+import './module.json' assert { type: "json" }; \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/string.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/string.json
new file mode 100644
index 0000000000..ace2d72d9d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/string.json
@@ -0,0 +1 @@
+"string"
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/true.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/true.json
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/true.json
@@ -0,0 +1 @@
+true
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/utf-8.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/utf-8.json
new file mode 100644
index 0000000000..088d982358
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/utf-8.json
@@ -0,0 +1,4 @@
+{
+ "data": "śćążź",
+ "comment": "The data above are five Polish letters, similar to scazz. It can be read correctly only with utf-8 encoding."
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/valid-content-type.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/valid-content-type.html
new file mode 100644
index 0000000000..162f2d6afc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/valid-content-type.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>JSON modules: Content-Type</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+function check(t, v) {
+ t.step(() => {
+ assert_equals(typeof v, "object");
+ assert_array_equals(Object.keys(v), ["test"]);
+ assert_equals(v.test, true);
+ t.done();
+ });
+}
+const t1 = async_test("text/json");
+const t2 = async_test("application/json");
+const t3 = async_test("text/html+json");
+const t4 = async_test("image/svg+json");
+const t5 = async_test("text/json;boundary=something");
+const t6 = async_test("text/json;foo=bar");
+</script>
+<script type="module" onerror="t1.step(() => assert_unreached(event))">
+import v from "../serve-with-content-type.py?fn=json-module-assertions/module.json&ct=text/json" assert { type: "json"};
+check(t1, v);
+</script>
+<script type="module" onerror="t2.step(() => assert_unreached(event))">
+import v from "../serve-with-content-type.py?fn=json-module-assertions/module.json&ct=application/json" assert { type: "json"};
+check(t2, v);
+</script>
+<script type="module" onerror="t3.step(() => assert_unreached(event))">
+import v from "../serve-with-content-type.py?fn=json-module-assertions/module.json&ct=text/html%2Bjson" assert { type: "json"};
+check(t3, v);
+</script>
+<script type="module" onerror="t4.step(() => assert_unreached(event))">
+import v from "../serve-with-content-type.py?fn=json-module-assertions/module.json&ct=image/svg%2Bjson" assert { type: "json"};
+check(t4, v);
+</script>
+<script type="module" onerror="t5.step(() => assert_unreached(event))">
+import v from "../serve-with-content-type.py?fn=json-module-assertions/module.json&ct=text/json;boundary=something" assert { type: "json"};
+check(t5, v);
+</script>
+<script type="module" onerror="t6.step(() => assert_unreached(event))">
+import v from "../serve-with-content-type.py?fn=json-module-assertions/module.json&ct=text/json;foo=bar" assert { type: "json"};
+check(t6, v);
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/windows-1250.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/windows-1250.json
new file mode 100644
index 0000000000..490e752ce9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module-assertions/windows-1250.json
@@ -0,0 +1,4 @@
+{
+ "data": "�湿�",
+ "comment": "The data above are five Polish letters, similar to scazz. It can be read correctly only with windows1250 encoding."
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/array.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/array.json
new file mode 100644
index 0000000000..e77e32d338
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/array.json
@@ -0,0 +1 @@
+["en", "try"]
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/bom-utf-16be.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/bom-utf-16be.json
new file mode 100644
index 0000000000..d22a45a591
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/bom-utf-16be.json
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/bom-utf-16le.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/bom-utf-16le.json
new file mode 100644
index 0000000000..4d1aa264a6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/bom-utf-16le.json
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/bom-utf-8.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/bom-utf-8.json
new file mode 100644
index 0000000000..07ba933e86
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/bom-utf-8.json
@@ -0,0 +1 @@
+{ "data": "hello" } \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/charset-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/charset-2.html
new file mode 100644
index 0000000000..dfadaba4d1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/charset-2.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="windows-1250">
+<title>JSON modules: UTF-8 decoding</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script type="module">
+ import json from "../serve-with-content-type.py?fn=json-module/utf-8.json" with { type: "json"};
+ test(() => {
+ assert_equals(json.data, "śćążź");
+ }, "JSON module should be loaded as utf-8 even though document's encoding is windows-1250");
+</script>
+<script type="module">
+ import json from "../serve-with-content-type.py?fn=json-module/windows-1250.json&ct=text/json%3Bcharset=windows-1250" with { type: "json"};
+ test(() => {
+ assert_not_equals(json.data, "śćążź",
+ 'Should be decoded as UTF-8');
+ }, "JSON module should be loaded as utf-8 even if it is encoded in windows-1250 and served with a windows-1250 charset response header, and this document's encoding is windows-1250");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/charset-bom.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/charset-bom.any.js
new file mode 100644
index 0000000000..483936a4f7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/charset-bom.any.js
@@ -0,0 +1,17 @@
+// META: global=window,dedicatedworker,sharedworker
+// META: script=/common/utils.js
+
+promise_test(async () => {
+ const jsonModule = await import('./bom-utf-8.json', { with: { type: 'json' } });
+ assert_equals(jsonModule.default.data, 'hello');
+}, 'UTF-8 BOM should be stripped when decoding JSON module script');
+
+promise_test(async test => {
+ await promise_rejects_js(test, SyntaxError,
+ import('./bom-utf-16be.json', { with: { type: 'json' } }), 'Expected parse error from UTF-16BE BOM');
+}, 'UTF-16BE BOM should result in parse error in JSON module script');
+
+promise_test(async test => {
+ await promise_rejects_js(test, SyntaxError,
+ import('./bom-utf-16le.json', { with: { type: 'json' } }), 'Expected parse error from UTF-16LE BOM');
+}, 'UTF-16LE BOM should result in parse error in JSON module script');
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/charset.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/charset.html
new file mode 100644
index 0000000000..ce72f0ef1b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/charset.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>JSON modules: UTF-8 decoding</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script type="module" onerror="unreachable()">
+ import json from "../serve-with-content-type.py?fn=json-module/utf-8.json&ct=text/json%3Bcharset=utf-8" with { type: "json"};
+ test(() => {
+ assert_equals(json.data, "śćążź");
+ }, "JSON module should be loaded as utf-8 when charset=utf8 is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import json from "../serve-with-content-type.py?fn=json-module/utf-8.json&ct=text/json%3Bcharset=shift-jis" with { type: "json"};
+ test(() => {
+ assert_equals(json.data, "śćążź");
+ }, "JSON module should be loaded as utf-8 when charset=shift-jis is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import json from "../serve-with-content-type.py?fn=json-module/utf-8.json&ct=text/json%3Bcharset=windows-1252" with { type: "json"};
+ test(() => {
+ assert_equals(json.data, "śćążź");
+ }, "JSON module should be loaded as utf-8 when charset=windows-1252 is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import json from "../serve-with-content-type.py?fn=json-module/utf-8.json&ct=text/json%3Bcharset=utf-7" with { type: "json"};;
+ test(() => {
+ assert_equals(json.data, "śćążź");
+ }, "JSON module should be loaded as utf-8 when charset=utf-7 is specified");
+</script>
+<script type="module" onerror="unreachable()">
+ import json from "../serve-with-content-type.py?fn=json-module/windows-1250.json&ct=text/json%3Bcharset=windows-1250" with { type: "json"};
+ test(() => {
+ assert_not_equals(json.data, "śćążź",
+ 'Should be decoded as UTF-8');
+ }, "JSON module should be loaded as utf-8 even if it is encoded in windows-1250 and served with a windows-1250 charset response header");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/cors-crossorigin-requests.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/cors-crossorigin-requests.html
new file mode 100644
index 0000000000..99ff2f67e8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/cors-crossorigin-requests.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+<head>
+ <title>json-module-crossorigin</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <h1>json-module-crossorigin</h1>
+ <iframe id="import-WithCORS" src="crossorigin-import-with-cors.sub.html"></iframe>
+ <iframe id="import-NoCORS" src="crossorigin-import-without-cors.sub.html"></iframe>
+ <iframe id="import-parseerror-WithCors" src="crossorigin-import-parse-error-with-cors.sub.html"></iframe>
+ <script>
+
+ var tests = [
+ { "obj": async_test("Imported JSON module, cross-origin with CORS"), "id": "import-WithCORS", "expected": "imported JSON: 42" },
+ { "obj": async_test("Imported JSON module, cross-origin, missing CORS ACAO header"), "id": "import-NoCORS", "expected": "error" },
+ { "obj": async_test("Imported JSON module with parse error, cross-origin, with CORS"), "id": "import-parseerror-WithCors", "expected": "0-0" },
+ ];
+
+ window.addEventListener("load", function () {
+ tests.forEach(function (test) {
+ var target = document.getElementById(test.id);
+ test.obj.step(function () {
+ assert_equals(target.contentDocument._log, test.expected, "Unexpected _log value");
+ });
+ test.obj.done();
+ });
+ });
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/credentials-iframe.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/credentials-iframe.sub.html
new file mode 100644
index 0000000000..b89edf8d31
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/credentials-iframe.sub.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<script type="module">
+ import json from "./cross-origin.py?id=sameOriginNoneDescendant&origin=http://{{host}}:{{ports[http][0]}}" with { type: "json" };
+ window.sameOriginNoneDescendant = json.requestHadCookies;
+</script>
+<script type="module" crossOrigin="anonymous">
+ import json from "./cross-origin.py?id=sameOriginAnonymousDescendant&origin=http://{{host}}:{{ports[http][0]}}" with { type: "json" };
+ window.sameOriginAnonymousDescendant = json.requestHadCookies;
+</script>
+<script type="module" crossOrigin="use-credentials">
+ import json from "./cross-origin.py?id=sameOriginUseCredentialsDescendant&origin=http://{{host}}:{{ports[http][0]}}" with { type: "json" };
+ window.sameOriginUseCredentialsDescendant = json.requestHadCookies;
+</script>
+<script type="module">
+ import json from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module/cross-origin.py?id=crossOriginNoneDescendant&origin=http://{{host}}:{{ports[http][0]}}" with { type: "json" };
+ window.crossOriginNoneDescendant = json.requestHadCookies;
+</script>
+<script type="module" crossOrigin="anonymous">
+ import json from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module/cross-origin.py?id=crossOriginAnonymousDescendant&origin=http://{{host}}:{{ports[http][0]}}" with { type: "json" };
+ window.crossOriginAnonymousDescendant = json.requestHadCookies;
+</script>
+<script type="module" crossOrigin="use-credentials">
+import json from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module/cross-origin.py?id=crossOriginUseCredentialsDescendant&origin=http://{{host}}:{{ports[http][0]}}" with { type: "json" };
+window.crossOriginUseCredentialsDescendant = json.requestHadCookies;
+</script>
+
+<script type="text/javascript">
+window.addEventListener('load', event => {
+ window.parent.postMessage({}, '*');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/credentials.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/credentials.sub.html
new file mode 100644
index 0000000000..a6df506e21
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/credentials.sub.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+document.cookie = 'milk=1';
+
+const setCookiePromise = fetch(
+ 'http://{{domains[www2]}}:{{ports[http][0]}}/cookies/resources/set-cookie.py?name=milk&path=/html/semantics/scripting-1/the-script-element/json-module/',
+ {
+ mode: 'no-cors',
+ credentials: 'include',
+ });
+
+const windowLoadPromise = new Promise(resolve => {
+ window.addEventListener('load', () => {
+ resolve();
+ });
+});
+
+promise_test(t => {
+ const iframe = document.createElement('iframe');
+
+ return Promise.all([setCookiePromise, windowLoadPromise]).then(() => {
+ const messagePromise = new Promise(resolve => {
+ window.addEventListener('message', event => {
+ resolve();
+ });
+ });
+
+ iframe.src = 'credentials-iframe.sub.html';
+ document.body.appendChild(iframe);
+
+ return messagePromise;
+ }).then(() => {
+ const w = iframe.contentWindow;
+
+ assert_equals(w.sameOriginNoneDescendant, true,
+ 'Descendant JSON modules should be loaded with the credentials when the crossOrigin attribute is not specified and the target is same-origin');
+ assert_equals(w.sameOriginAnonymousDescendant, true,
+ 'Descendant JSON modules should be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is same-origin');
+ assert_equals(w.sameOriginUseCredentialsDescendant, true,
+ 'Descendant JSON modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is same-origin');
+ assert_equals(w.crossOriginNoneDescendant, false,
+ 'Descendant JSON modules should not be loaded with the credentials when the crossOrigin attribute is not specified and the target is cross-origin');
+ assert_equals(w.crossOriginAnonymousDescendant, false,
+ 'Descendant JSON modules should not be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is cross-origin');
+ assert_equals(w.crossOriginUseCredentialsDescendant, true,
+ 'Descendant JSON modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is cross-origin');
+});
+}, 'JSON Modules should be loaded with or without the credentials based on the same-origin-ness and the crossOrigin attribute');
+</script>
+<body>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/cross-origin.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/cross-origin.py
new file mode 100644
index 0000000000..cd56c3628a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/cross-origin.py
@@ -0,0 +1,16 @@
+def main(request, response):
+
+ headers = [
+ (b"Content-Type", b"application/json"),
+ (b"Access-Control-Allow-Origin", request.GET.first(b"origin")),
+ (b"Access-Control-Allow-Credentials", b"true")
+ ]
+
+ milk = request.cookies.first(b"milk", None)
+
+ if milk is None:
+ return headers, u'{"requestHadCookies": false}'
+ elif milk.value == b"1":
+ return headers, u'{"requestHadCookies": true}'
+
+ return headers, u'{"requestHadCookies": false}'
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/crossorigin-import-parse-error-with-cors.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/crossorigin-import-parse-error-with-cors.sub.html
new file mode 100644
index 0000000000..9972c53d1b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/crossorigin-import-parse-error-with-cors.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>json-module-import-cross-domain-parse-error-WithCORS</title>
+ <script src="../module/crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>json-module-import-cross-domain-parse-error-WithCORS</h1>
+ <script type="module" crossorigin>
+ import json from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module/parse-error.json?pipe=header(Access-Control-Allow-Origin,*)" with { type: "json" };
+ // Push an event to the log indicating that the script was executed.
+ document._log.push(`imported JSON: ${json.answer}`);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/crossorigin-import-with-cors.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/crossorigin-import-with-cors.sub.html
new file mode 100644
index 0000000000..95fd156df2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/crossorigin-import-with-cors.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>json-module-import-cross-domain-WithCORS</title>
+ <script src="../module/crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>json-module-import-cross-domain-WithCORS</h1>
+ <script type="module" crossorigin>
+ import json from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module/data.json?pipe=header(Access-Control-Allow-Origin,*)" with { type: "json" };
+ // Push an event to the log indicating that the script was executed.
+ document._log.push(`imported JSON: ${json.answer}`);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/crossorigin-import-without-cors.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/crossorigin-import-without-cors.sub.html
new file mode 100644
index 0000000000..b9318c8b20
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/crossorigin-import-without-cors.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>json-module-import-cross-domain-NoCORS</title>
+ <script src="../module/crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>json-module-import-cross-domain-NoCORS</h1>
+ <script type="module" onerror="document._log.push('error');">
+ import json from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module/data.json" with { type: "json" };
+ // Push an event to the log indicating that the script was executed.
+ document._log.push(`imported JSON: ${json.answer}`);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/data.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/data.json
new file mode 100644
index 0000000000..14a0526ebb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/data.json
@@ -0,0 +1,3 @@
+{
+ "answer": 42
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/false.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/false.json
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/false.json
@@ -0,0 +1 @@
+false
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/integrity-matches.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/integrity-matches.js
new file mode 100644
index 0000000000..20459c17e3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/integrity-matches.js
@@ -0,0 +1,2 @@
+import json from "./data.json" with { type: "json" };
+window.matchesLog.push(`integrity-matches,json:${json.answer}`);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/integrity-mismatches.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/integrity-mismatches.js
new file mode 100644
index 0000000000..0406dbcca5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/integrity-mismatches.js
@@ -0,0 +1,2 @@
+import json "./data.json" with { type: "json" };
+window.mismatchesLog.push(`integrity-mismatches,json:${json.answer}`);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/integrity.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/integrity.html
new file mode 100644
index 0000000000..68a794b973
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/integrity.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>&lt;script> integrity=""</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+window.matchesLog = [];
+window.matchesEvents = [];
+
+window.mismatchesLog = [];
+window.mismatchesEvents = [];
+</script>
+<script type="module" src="integrity-matches.js" integrity="sha384-VmQQfGzBiLKdyzw4FA4kL4ohu4tyujV68ddgW1aN/1v3cBZNNBn2gDFdVQxfL7+a" onload="window.matchesEvents.push('load');" onerror="window.matchesEvents.push('error')"></script>
+<script type="module" src="integrity-mismatches.js" integrity="sha384-doesnotmatch" onload="window.mismatchesEvents.push('load');" onerror="window.mismatchesEvents.push('error')"></script>
+
+<script type="module">
+test(() => {
+ assert_array_equals(window.matchesLog, ["integrity-matches,json:42"], "The module and its dependency must have executed");
+ assert_array_equals(window.matchesEvents, ["load"], "The load event must have fired");
+}, "The integrity attribute must be verified on the top-level of a module loading a JSON module and allow it to execute when it matches");
+
+test(() => {
+ assert_array_equals(window.mismatchesLog, [], "The module and its dependency must not have executed");
+ assert_array_equals(window.mismatchesEvents, ["error"], "The error event must have fired");
+}, "The integrity attribute must be verified on the top-level of a module loading a JSON module and not allow it to execute when there's a mismatch");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/invalid-content-type.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/invalid-content-type.any.js
new file mode 100644
index 0000000000..4226c3dc03
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/invalid-content-type.any.js
@@ -0,0 +1,17 @@
+// META: global=window,dedicatedworker,sharedworker
+
+const content_types = [
+ "application/json+protobuf",
+ "application/json+blah",
+ "text/x-json",
+ "text/json+blah",
+ "application/blahjson",
+ "image/json",
+];
+for (const content_type of content_types) {
+ promise_test(async test => {
+ await promise_rejects_js(test, TypeError,
+ import(`./module.json?pipe=header(Content-Type,${content_type})`, { with: { type: "json"} }),
+ `Import of a JSON module with MIME type ${content_type} should fail`);
+ }, `Try importing JSON module with MIME type ${content_type}`);
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/json-module-service-worker-test.https.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/json-module-service-worker-test.https.html
new file mode 100644
index 0000000000..cc47da1499
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/json-module-service-worker-test.https.html
@@ -0,0 +1,29 @@
+<!doctype html>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+ promise_test(async (test) => {
+ const reg = await navigator.serviceWorker.register('./serviceworker.js', { type: 'module' });
+ test.add_cleanup(() => reg.unregister());
+ assert_not_equals(reg.installing, undefined);
+ }, "Javascript importing JSON Module should load within the context of a service worker");
+
+ promise_test(test => {
+ return promise_rejects_dom(test, "SecurityError",
+ navigator.serviceWorker.register('./module.json', { type: 'module' }),
+ "Attempting to load JSON as a service worker should fail");
+ }, "Trying to register a service worker with a top-level JSON Module should fail");
+
+ promise_test(async (test) => {
+ const reg = await navigator.serviceWorker.register('./serviceworker-dynamic-import.js', { type: 'module' });
+ test.add_cleanup(() => reg.unregister());
+ assert_not_equals(reg.installing, undefined);
+ reg.installing.postMessage("PING");
+ const msgEvent = await new Promise(resolve => {
+ navigator.serviceWorker.onmessage = resolve;
+ });
+ assert_equals(msgEvent.data, "FAILED");
+ }, "JSON Module dynamic import should not load within the context of a service worker");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/load-error-events.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/load-error-events.html
new file mode 100644
index 0000000000..54c1892540
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/load-error-events.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<head>
+<title>load/error events for JSON modules</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/load-error-events-helpers.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+</head>
+<script>
+ "use strict";
+
+ var test1_load = event_test('inline, 200, parser-inserted', false, false);
+ var test1_error = event_test('inline, 404, parser-inserted', false, true);
+
+ var test2_load = event_test('src, 200, parser-inserted', true, false);
+ var test2_error = event_test('src, 404, parser-inserted', false, true);
+
+ var test3_dynamic_load = event_test('src, 200, not parser-inserted', true, false);
+ var test3_dynamic_error = event_test('src, 404, not parser-inserted', false, true);
+
+ var test4_dynamic_load = event_test('inline, 200, not parser-inserted', false, false);
+ var test4_dynamic_error = event_test('inline, 404, not parser-inserted', false, true);
+
+ var script3_dynamic_load = document.createElement('script');
+ script3_dynamic_load.setAttribute('type', 'module');
+ script3_dynamic_load.onload = () => onLoad(test3_dynamic_load);
+ script3_dynamic_load.onerror = () => onError(test3_dynamic_load);
+ script3_dynamic_load.src = "./load-error-events.py?test=test3_dynamic_load";
+ document.head.appendChild(script3_dynamic_load);
+
+ var script3_dynamic_error = document.createElement('script');
+ script3_dynamic_error.setAttribute('type', 'module');
+ script3_dynamic_error.onload = () => onLoad(test3_dynamic_error);
+ script3_dynamic_error.onerror = () => onError(test3_dynamic_error);
+ script3_dynamic_error.src = "./load-error-events.py?test=test3_dynamic_error";
+ document.head.appendChild(script3_dynamic_error);
+
+ var script4_dynamic_load = document.createElement('script');
+ script4_dynamic_load.setAttribute('type', 'module');
+ script4_dynamic_load.onload = () => onLoad(test4_dynamic_load);
+ script4_dynamic_load.onerror = () => onError(test4_dynamic_load);
+ script4_dynamic_load.async = true;
+ script4_dynamic_load.appendChild(document.createTextNode(`
+ import "./module.json" with { type: "json" };
+ onExecute(test4_dynamic_load);`
+ ));
+ document.head.appendChild(script4_dynamic_load);
+
+ var script4_dynamic_error = document.createElement('script');
+ script4_dynamic_error.setAttribute('type', 'module');
+ script4_dynamic_error.onload = () => onLoad(test4_dynamic_error);
+ script4_dynamic_error.onerror = () => onError(test4_dynamic_error);
+ script4_dynamic_error.async = true;
+ script4_dynamic_error.appendChild(document.createTextNode(`import "./not_found.json" with { type: "json" };`));
+ document.head.appendChild(script4_dynamic_error);
+</script>
+<script onload="onLoad(test1_load);" onerror="onError(test1_load);" type="module">
+ import "./module.json" with { type: "json"};
+ onExecute(test1_load);
+</script>
+<script onload="onLoad(test1_error);" onerror="onError(test1_error);" type="module">
+ import "./not_found.json" with { type: "json"};
+ onExecute(test1_error);
+</script>
+<script src="./load-error-events.py?test=test2_load" onload="onLoad(test2_load);" onerror="onError(test2_load);" type="module"></script>
+<script src="./load-error-events.py?test=test2_error" onload="onLoad(test2_error);" onerror="onError(test2_error);" type="module"></script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/load-error-events.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/load-error-events.py
new file mode 100644
index 0000000000..244552a693
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/load-error-events.py
@@ -0,0 +1,14 @@
+import re
+
+def main(request, response):
+ headers = [(b"Content-Type", b"text/javascript")]
+ test = request.GET.first(b'test')
+ assert(re.match(b'^[a-zA-Z0-9_]+$', test))
+
+ status = 200
+ if test.find(b'_load') >= 0:
+ content = b'import "./module.json" with { type: "json"}; %s.executed = true;' % test
+ else:
+ content = b'import "./not_found.json" with { type: "json"}; %s.test.step(function() { assert_unreached("404 script should not be executed"); });' % test
+
+ return status, headers, content
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/module.html
new file mode 100644
index 0000000000..05fc264f36
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/module.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>JSON modules</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+const t = async_test();
+</script>
+<script type="module" onerror="t.step(() => assert_unreached(event))">
+import v from "./module.json" with { type: "json" };
+t.step(() => {
+ assert_equals(typeof v, "object");
+ assert_array_equals(Object.keys(v), ["test"]);
+ assert_equals(v.test, true);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/module.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/module.json
new file mode 100644
index 0000000000..f834b2a4e8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/module.json
@@ -0,0 +1,3 @@
+{
+ "test": true
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/non-object.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/non-object.any.js
new file mode 100644
index 0000000000..ae78ddf072
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/non-object.any.js
@@ -0,0 +1,14 @@
+// META: global=window,dedicatedworker,sharedworker
+
+for (const value of [null, true, false, "string"]) {
+ promise_test(async t => {
+ const result = await import(`./${value}.json`, { with: { type: "json" } });
+ assert_equals(result.default, value);
+ }, `Non-object: ${value}`);
+}
+
+promise_test(async t => {
+ const result = await import("./array.json", { with: { type: "json" } });
+ assert_array_equals(result.default, ["en", "try"]);
+}, "Non-object: array");
+
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/null.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/null.json
new file mode 100644
index 0000000000..19765bd501
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/null.json
@@ -0,0 +1 @@
+null
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/parse-error.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/parse-error.html
new file mode 100644
index 0000000000..88fb23a00d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/parse-error.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>JSON modules: parse error</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+setup({
+ allow_uncaught_exception: true,
+});
+async_test(t => {
+ window.addEventListener("error", t.step_func_done(e => {
+ assert_true(e instanceof ErrorEvent, "ErrorEvent");
+ assert_equals(e.filename, new URL("parse-error.json", location).href);
+ assert_true(e.error instanceof SyntaxError, "SyntaxError");
+ }));
+});
+</script>
+<script type="module">
+import v from "./parse-error.json" with { type: "json" };
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/parse-error.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/parse-error.json
new file mode 100644
index 0000000000..98232c64fc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/parse-error.json
@@ -0,0 +1 @@
+{
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/referrer-checker.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/referrer-checker.py
new file mode 100644
index 0000000000..e9f0f1789b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/referrer-checker.py
@@ -0,0 +1,6 @@
+def main(request, response):
+ referrer = request.headers.get(b"referer", b"")
+ response_headers = [(b"Content-Type", b"application/json"),
+ (b"Access-Control-Allow-Origin", b"*")]
+ return (200, response_headers,
+ b'{"referrer": "' + referrer + b'"}')
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/referrer-policies.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/referrer-policies.sub.html
new file mode 100644
index 0000000000..1509c853e2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/referrer-policies.sub.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Referrers with JSON module requests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+ // "name" parameter is necessary for bypassing the module map.
+ import referrerSame from "./referrer-checker.py?name=sameNoReferrerPolicy" with { type: "json"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module/referrer-checker.py?name=remoteNoReferrerPolicy" with { type: "json"};
+
+ const origin = (new URL(location.href)).origin + "/";
+ const originUrl = location.href;
+
+ test(t => {
+ assert_equals(
+ referrerSame.referrer, originUrl,
+ "Referrer URL should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the default referrer policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.referrer, origin,
+ "Referrer origin should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the default referrer policy.");
+</script>
+<script type="module" referrerpolicy="origin">
+ import referrerSame from "./referrer-checker.py?name=sameReferrerPolicyOrigin" with { type: "json"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module/referrer-checker.py?name=remoteReferrerPolicyOrigin" with { type: "json"};
+
+ const origin = (new URL(location.href)).origin + "/";
+
+ test(t => {
+ assert_equals(
+ referrerSame.referrer, origin,
+ "Referrer origin should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the origin policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.referrer, origin,
+ "Referrer origin should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the origin policy.");
+
+</script>
+<script type="module" referrerpolicy="no-referrer">
+ import referrerSame from "./referrer-checker.py?name=sameReferrerPolicyNoReferrer" with { type: "json"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module/referrer-checker.py?name=remoteReferrerPolicyNoReferrer" with { type: "json"};
+
+ test(t => {
+ assert_equals(
+ referrerSame.referrer, "",
+ "No referrer should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the no-referrer policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.referrer, "",
+ "No referrer should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the no-referrer policy.");
+
+</script>
+<script type="module" referrerpolicy="unsafe-url">
+ import referrerSame from "./referrer-checker.py?name=sameNoReferrerPolicyUnsafeUrl" with { type: "json"};
+ import referrerRemote from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/json-module/referrer-checker.py?name=remoteNoReferrerPolicyUnsafeUrl" with { type: "json"};
+
+ const originUrl = location.href;
+
+ test(t => {
+ assert_equals(
+ referrerSame.referrer, originUrl,
+ "Referrer URL should be sent for the same-origin top-level script.");
+ }, "Importing a same-origin top-level script with the unsafe-url referrer policy.");
+
+ test(t => {
+ assert_equals(
+ referrerRemote.referrer, originUrl,
+ "Referrer URL should be sent for the remote-origin top-level script.");
+ }, "Importing a remote-origin top-level script with the unsafe-url referrer policy.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/repeated-imports.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/repeated-imports.any.js
new file mode 100644
index 0000000000..722251b84d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/repeated-imports.any.js
@@ -0,0 +1,65 @@
+// META: global=window,dedicatedworker,sharedworker
+// META: script=/common/utils.js
+
+promise_test(async test => {
+ await promise_rejects_js(test, TypeError,
+ import("./module.json"),
+ "Dynamic import of a JSON module without a type attribute should fail");
+
+ // This time the import should succeed because we're using the correct
+ // import even though the previous attempt with the same specifier failed.
+ const result = await import("./module.json", { with: { type: "json" } });
+ assert_true(result.default.test);
+}, "Importing a specifier that previously failed due to an incorrect type attribute can succeed if the correct attribute is later given");
+
+promise_test(async test => {
+ // Append a URL fragment to the specifier so that this is independent
+ // from the previous test.
+ const result = await import("./module.json#2", { with: { type: "json" } });
+ assert_true(result.default.test);
+
+ await promise_rejects_js(test, TypeError,
+ import("./module.json#2"),
+ "Dynamic import should fail with the type attribute missing even if the same specifier previously succeeded");
+}, "Importing a specifier that previously succeeded with the correct type attribute should fail if the incorrect attribute is later given");
+
+promise_test(async test => {
+ const uuid_token = token();
+ // serve-json-then-js.py gives us JSON the first time
+ const result_json = await import(`../serve-json-then-js.py?key=${uuid_token}`, { with: { type: "json" } });
+ assert_equals(result_json.default.hello, "world");
+
+ // Import using the same specifier again; this time we get JS, which
+ // should succeed since we're not asserting a non-JS type this time.
+ const result_js = await import(`../serve-json-then-js.py?key=${uuid_token}`);
+ assert_equals(result_js.default, "hello");
+}, "Two modules of different type with the same specifier can load if the server changes its responses");
+
+promise_test(async test => {
+ const uuid_token = token();
+ // serve-json-then-js.py gives us JSON the first time
+ await promise_rejects_js(test, TypeError,
+ import(`../serve-json-then-js.py?key=${uuid_token}`),
+ "Dynamic import of JS with a JSON type attribute should fail");
+
+ // Import using the same specifier/module type pair again; this time we get JS,
+ // but the import should still fail because the module map entry for this
+ // specifier/module type pair already contains a failure.
+ await promise_rejects_js(test, TypeError,
+ import(`../serve-json-then-js.py?key=${uuid_token}`),
+ "import should always fail if the same specifier/type attribute pair failed previously");
+}, "An import should always fail if the same specifier/type attribute pair failed previously");
+
+promise_test(async test => {
+ const uuid_token = token();
+ // serve-json-then-js.py gives us JSON the first time
+ const result_json = await import(`../serve-json-then-js.py?key=${uuid_token}`, { with: { type: "json" } });
+ assert_equals(result_json.default.hello, "world");
+
+ // If this were to do another fetch, the import would fail because
+ // serve-json-then-js.py would give us JS this time. But, the module map
+ // entry for this specifier/module type pair already exists, so we
+ // successfully reuse the entry instead of fetching again.
+ const result_json_2 = await import(`../serve-json-then-js.py?key=${uuid_token}`, { with: { type: "json" } });
+ assert_equals(result_json_2.default.hello, "world");
+}, "If an import previously succeeded for a given specifier/type attribute pair, future uses of that pair should yield the same result");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/script-element-json-src.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/script-element-json-src.html
new file mode 100644
index 0000000000..c6d7c9a76e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/script-element-json-src.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>&lt;script&gt; with JSON src</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ const test_load = async_test(
+ "Test that <script> doesn't load when the src is JSON.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(log, ["error"]);
+ }));
+</script>
+<script type="module" src="./module.json" onload="t.unreached_func('JSON src should fail to load')" onerror="log.push('error')"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker-dynamic-import.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker-dynamic-import.js
new file mode 100644
index 0000000000..cd39c789cb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker-dynamic-import.js
@@ -0,0 +1,5 @@
+onmessage = e => {
+ e.waitUntil(import("./module.json", { with: { type: "json" } })
+ .then(module => e.source.postMessage("LOADED"))
+ .catch(error => e.source.postMessage("FAILED")));
+ }; \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker.js
new file mode 100644
index 0000000000..65210fe3e7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker.js
@@ -0,0 +1 @@
+import './module.json' with { type: "json" }; \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/string.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/string.json
new file mode 100644
index 0000000000..ace2d72d9d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/string.json
@@ -0,0 +1 @@
+"string"
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/true.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/true.json
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/true.json
@@ -0,0 +1 @@
+true
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/utf-8.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/utf-8.json
new file mode 100644
index 0000000000..088d982358
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/utf-8.json
@@ -0,0 +1,4 @@
+{
+ "data": "śćążź",
+ "comment": "The data above are five Polish letters, similar to scazz. It can be read correctly only with utf-8 encoding."
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.html
new file mode 100644
index 0000000000..3232b84d27
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>JSON modules: Content-Type</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+function check(t, v) {
+ t.step(() => {
+ assert_equals(typeof v, "object");
+ assert_array_equals(Object.keys(v), ["test"]);
+ assert_equals(v.test, true);
+ t.done();
+ });
+}
+const t1 = async_test("text/json");
+const t2 = async_test("application/json");
+const t3 = async_test("text/html+json");
+const t4 = async_test("image/svg+json");
+const t5 = async_test("text/json;boundary=something");
+const t6 = async_test("text/json;foo=bar");
+</script>
+<script type="module" onerror="t1.step(() => assert_unreached(event))">
+import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=text/json" with { type: "json"};
+check(t1, v);
+</script>
+<script type="module" onerror="t2.step(() => assert_unreached(event))">
+import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=application/json" with { type: "json"};
+check(t2, v);
+</script>
+<script type="module" onerror="t3.step(() => assert_unreached(event))">
+import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=text/html%2Bjson" with { type: "json"};
+check(t3, v);
+</script>
+<script type="module" onerror="t4.step(() => assert_unreached(event))">
+import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=image/svg%2Bjson" with { type: "json"};
+check(t4, v);
+</script>
+<script type="module" onerror="t5.step(() => assert_unreached(event))">
+import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=text/json;boundary=something" with { type: "json"};
+check(t5, v);
+</script>
+<script type="module" onerror="t6.step(() => assert_unreached(event))">
+import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=text/json;foo=bar" with { type: "json"};
+check(t6, v);
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/windows-1250.json b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/windows-1250.json
new file mode 100644
index 0000000000..490e752ce9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/json-module/windows-1250.json
@@ -0,0 +1,4 @@
+{
+ "data": "�湿�",
+ "comment": "The data above are five Polish letters, similar to scazz. It can be read correctly only with windows1250 encoding."
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/load-error-events-1.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/load-error-events-1.html
new file mode 100644
index 0000000000..45571550e1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/load-error-events-1.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<head>
+<title>load/error events for classic scripts</title>
+<!-- For module scripts see module/load-error-events*.html -->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/load-error-events-helpers.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+</head>
+<script>
+"use strict";
+var test1_load = event_test('src, 200, parser-inserted, defer, no async', true, false);
+var test2_load = event_test('src, 200, parser-inserted, no defer, no async', true, false);
+var test4_load = event_test('src, 200, parser-inserted, no defer, async', true, false);
+
+var test3_dynamic_load = event_test('src, 200, not parser-inserted, no defer, no async, no non-blocking', true, false);
+var test4_dynamic_load = event_test('src, 200, not parser-inserted, no defer, async', true, false);
+
+var test1_error = event_test('src, 404, parser-inserted, defer, no async', false, true);
+var test2_error = event_test('src, 404, parser-inserted, no defer, no async', false, true);
+var test4_error = event_test('src, 404, parser-inserted, no defer, async', false, true);
+
+var test3_dynamic_error = event_test('src, 404, not parser-inserted, no defer, no async, no non-blocking', false, true);
+var test4_dynamic_error = event_test('src, 404, not parser-inserted, no defer, async', false, true);
+
+var test6_load = event_test('no src, parser-inserted, no style sheets blocking scripts', false, false);
+
+var script3_dynamic_load = document.createElement('script');
+script3_dynamic_load.onload = () => onLoad(test3_dynamic_load);
+script3_dynamic_load.onerror = () => onError(test3_dynamic_load);
+script3_dynamic_load.async = false;
+script3_dynamic_load.src = "resources/load-error-events.py?test=test3_dynamic_load";
+document.head.appendChild(script3_dynamic_load);
+
+var script3_dynamic_error = document.createElement('script');
+script3_dynamic_error.onload = () => onLoad(test3_dynamic_error);
+script3_dynamic_error.onerror = () => onError(test3_dynamic_error);
+script3_dynamic_error.async = false;
+script3_dynamic_error.src = "resources/load-error-events.py?test=test3_dynamic_error";
+document.head.appendChild(script3_dynamic_error);
+
+var script4_dynamic_load = document.createElement('script');
+script4_dynamic_load.onload = () => onLoad(test4_dynamic_load);
+script4_dynamic_load.onerror = () => onError(test4_dynamic_load);
+script4_dynamic_load.async = true;
+script4_dynamic_load.src = "resources/load-error-events.py?test=test4_dynamic_load";
+document.head.appendChild(script4_dynamic_load);
+
+var script4_dynamic_error = document.createElement('script');
+script4_dynamic_error.onload = () => onLoad(test4_dynamic_error);
+script4_dynamic_error.onerror = () => onError(test4_dynamic_error);
+script4_dynamic_error.async = true;
+script4_dynamic_error.src = "resources/load-error-events.py?test=test4_dynamic_error";
+document.head.appendChild(script4_dynamic_error);
+</script>
+
+<script src="resources/load-error-events.py?test=test1_load" onload="onLoad(test1_load);" onerror="onError(test1_load);" defer></script>
+<script src="resources/load-error-events.py?test=test2_load" onload="onLoad(test2_load);" onerror="onError(test2_load);"></script>
+<script src="resources/load-error-events.py?test=test4_load" onload="onLoad(test4_load);" onerror="onError(test4_load);" async></script>
+<script src="resources/load-error-events.py?test=test1_error" onload="onLoad(test1_error);" onerror="onError(test1_error);" defer></script>
+<script src="resources/load-error-events.py?test=test2_error" onload="onLoad(test2_error);" onerror="onError(test2_error);"></script>
+<script src="resources/load-error-events.py?test=test4_error" onload="onLoad(test4_error);" onerror="onError(test4_error);" async></script>
+
+<script onload="onLoad(test6_load);" onerror="onError(test6_load);">
+"use strict";
+onExecute(test6_load);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/load-error-events-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/load-error-events-2.html
new file mode 100644
index 0000000000..0748b45909
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/load-error-events-2.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<head>
+<title>load/error events for classic scripts with a style sheet that is blocking scripts</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/load-error-events-helpers.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script>
+"use strict";
+var test5_load = event_test('no src, parser-inserted, has style sheets blocking scripts, script nesting level == 1', false, false);
+</script>
+
+<link rel="stylesheet" href="/common/slow.py"></link>
+<!-- This is testing the case where an inline classic script is inserted
+by parser while there is an loading stylesheet. Therefore, it is critical to
+place a <link rel="stylesheet"> just above the <script> to be tested. -->
+<script onload="onLoad(test5_load);" onerror="onError(test5_load);">
+"use strict";
+onExecute(test5_load);
+</script>
+</head>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/load-error-events-3.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/load-error-events-3.html
new file mode 100644
index 0000000000..83a752ce2c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/load-error-events-3.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<head>
+<title>load/error events for classic scripts with a style sheet that is blocking scripts and script nesting level &gt; 1</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/load-error-events-helpers.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+</head>
+<script>
+"use strict";
+var test6_load = event_test('no src, parser-inserted, has style sheets blocking scripts, script nesting level == 2',
+ false, false);
+
+document.write(
+ `<link rel="stylesheet" href="/common/slow.py"></link>
+ <script onload="onLoad(test6_load);"
+ onerror="onError(test6_load);">
+ "use strict";
+ onExecute(test6_load);
+ </scr` + `ipt>`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/log.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/log.py
new file mode 100644
index 0000000000..2a6cc33029
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/log.py
@@ -0,0 +1,13 @@
+import time
+
+def main(request, response):
+ response.headers.append(b"Content-Type", b"text/javascript")
+ try:
+ script_id = int(request.GET.first(b"id"))
+ delay = int(request.GET.first(b"sec"))
+ except:
+ response.set_error(400, u"Invalid parameter")
+
+ time.sleep(int(delay))
+
+ return u"log('%s')" % script_id
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-window-onerror-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-window-onerror-module.html
new file mode 100644
index 0000000000..728ce32c38
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-window-onerror-module.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+ <title>Microtask checkpoint after window.onerror events (module)</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/checkpoint-after-error-event.js" type="module"></script>
+ <script type="module">self.postMessage("foo");</script>
+</head>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-window-onerror.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-window-onerror.html
new file mode 100644
index 0000000000..72a197ca6a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-window-onerror.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+ <title>Microtask checkpoint after window.onerror events (classic)</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/checkpoint-after-error-event.js"></script>
+ <script>self.postMessage("foo");</script>
+</head>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-workerglobalscope-onerror-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-workerglobalscope-onerror-module.html
new file mode 100644
index 0000000000..ff2b5d4943
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-workerglobalscope-onerror-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+ <title>Microtask checkpoint after window.onerror events (module)</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ const worker = new Worker(
+ "resources/checkpoint-after-error-event-worker-module.js",
+ {type: "module"});
+ fetch_tests_from_worker(worker);
+ worker.postMessage("foo");
+ </script>
+</head>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-workerglobalscope-onerror.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-workerglobalscope-onerror.html
new file mode 100644
index 0000000000..1932c7183b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-workerglobalscope-onerror.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+ <title>Microtask checkpoint after window.onerror events (module)</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ const worker = new Worker(
+ "resources/checkpoint-after-error-event-worker.js");
+ fetch_tests_from_worker(worker);
+ worker.postMessage("foo");
+ </script>
+</head>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-importScripts.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-importScripts.any.js
new file mode 100644
index 0000000000..8791a099b6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-importScripts.any.js
@@ -0,0 +1,40 @@
+// META: global=dedicatedworker,sharedworker
+
+// The `then` handlers for `Promise.resolve()` are evaluated in the first
+// microtasks checkpoint after `Promise.resolve()`.
+
+// ----------------------------------------------------------------
+// Check when microtasks checkpoint is performed around importScripts().
+
+// The expectation is: the `then` handlers are evaluated after the script
+// calling importScripts() is finished, not immediately after importScripts().
+// Although #clean-up-after-running-script is executed as a part of
+// #run-a-classic-script for importScripts()ed scripts, but at that time
+// microtasks checkpoint is NOT performed because JavaScript execution context
+// stack is not empty.
+
+self.log = [];
+
+// Microtasks should be executed before
+// #run-a-classic-script/#run-a-module-script is completed, and thus before
+// script evaluation scheduled by setTimeout().
+async_test(t => {
+ self.addEventListener('error',
+ t.unreached_func('error event should not be fired'));
+
+ t.step_timeout(() => {
+ assert_array_equals(log, [
+ 'importScripts()ed script',
+ 'catch',
+ 'promise'
+ ]);
+ t.done();
+ },
+ 0);
+}, "Promise resolved during importScripts()");
+
+try {
+ importScripts('resources/resolve-then-throw.js');
+} catch (e) {
+ self.log.push('catch');
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-nothrow-importScripts.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-nothrow-importScripts.any.js
new file mode 100644
index 0000000000..bacfc9fd04
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-nothrow-importScripts.any.js
@@ -0,0 +1,11 @@
+// META: global=dedicatedworker,sharedworker
+// META: script=./resources/evaluation-order-setup.js
+
+// Spec: https://html.spec.whatwg.org/C/#run-a-classic-script
+// called from https://html.spec.whatwg.org/C/#import-scripts-into-worker-global-scope
+setupTest("importScripts() queueing a microtask then throwing an exception", [
+ "body",
+ "microtask",
+]);
+
+importScripts('./resources/evaluation-order-1-nothrow.js');
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-nothrow-static-import.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-nothrow-static-import.any.js
new file mode 100644
index 0000000000..006eab7a7e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-nothrow-static-import.any.js
@@ -0,0 +1,5 @@
+// META: global=dedicatedworker-module,sharedworker-module
+// META: script=./resources/evaluation-order-setup.js
+
+import './resources/evaluation-order-1-nothrow-setup.js';
+import './resources/evaluation-order-1-nothrow.js';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-throw-importScripts.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-throw-importScripts.any.js
new file mode 100644
index 0000000000..0b42ea1e50
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-throw-importScripts.any.js
@@ -0,0 +1,22 @@
+// META: global=dedicatedworker,sharedworker
+// META: script=./resources/evaluation-order-setup.js
+
+// Spec: https://html.spec.whatwg.org/C/#run-a-classic-script
+// called from https://html.spec.whatwg.org/C/#import-scripts-into-worker-global-scope
+setupTest("importScripts() queueing a microtask then throwing an exception", [
+ // Step 6 of #run-a-classic-script.
+ "body",
+
+ // Step 7.1.1 ("Clean up after running script") is no-op because JavaScript
+ // execution context stack is still non-empty immediately after
+ // importScripts() as the outer script is still executing.
+
+ // Step 7.1.2 (Rethrowing an exception) causes worker onerror.
+ "global-error",
+
+ // Microtask checkpoint is performed later, perhaps
+ // "Clean up after running script" after the outer script is finished.
+ "microtask",
+]);
+
+importScripts('./resources/evaluation-order-1-throw.js');
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-throw-static-import.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-throw-static-import.any.js
new file mode 100644
index 0000000000..f6cc427c71
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-throw-static-import.any.js
@@ -0,0 +1,5 @@
+// META: global=dedicatedworker-module,sharedworker-module
+// META: script=./resources/evaluation-order-setup.js
+
+import './resources/evaluation-order-1-throw-setup.js';
+import './resources/evaluation-order-1-throw.js';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1.html
new file mode 100644
index 0000000000..4800ef81bc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/evaluation-order-setup.js"></script>
+
+<script>
+// Spec: https://html.spec.whatwg.org/C/#run-a-classic-script
+setupTest("Classic script queueing a microtask then throwing an exception", [
+ "body", // Step 6.
+ "global-error", // "Report the exception" at Step 7.3.
+ "microtask", // "Clean up after running script" at Step 7.2.
+ ]);
+</script>
+<script src="resources/evaluation-order-1-throw.js"
+ onerror="unreachable()" onload="testDone()"></script>
+
+<script>
+// Spec: https://html.spec.whatwg.org/C/#run-a-classic-script
+setupTest("Classic script queueing a microtask", [
+ "body", // Step 6.
+ "microtask", // "Clean up after running script" at Step 7.2.
+ ]);
+</script>
+<script src="resources/evaluation-order-1-nothrow.js"
+ onerror="unreachable()" onload="testDone()"></script>
+
+
+<script type="module" src="resources/evaluation-order-1-throw-setup.js"></script>
+<script type="module" src="resources/evaluation-order-1-throw.js"
+ onerror="unreachable()" onload="testDone()"></script>
+
+<script type="module" src="resources/evaluation-order-1-nothrow-setup.js"></script>
+<script type="module" src="resources/evaluation-order-1-nothrow.js"
+ onerror="unreachable()" onload="testDone()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-2.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-2.any.js
new file mode 100644
index 0000000000..bbc6474823
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-2.any.js
@@ -0,0 +1,5 @@
+// META: global=dedicatedworker-module,sharedworker-module
+// META: script=./resources/evaluation-order-setup.js
+
+import './resources/evaluation-order-2-setup.js';
+import './resources/evaluation-order-2.1.mjs';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-2.html
new file mode 100644
index 0000000000..e55c2ecbed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/evaluation-order-setup.js"></script>
+
+<script type="module" src="resources/evaluation-order-2-setup.js"></script>
+<script type="module" src="resources/evaluation-order-2.1.mjs"
+ onerror="unreachable()" onload="testDone()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-3.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-3.any.js
new file mode 100644
index 0000000000..19e94714e5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-3.any.js
@@ -0,0 +1,5 @@
+// META: global=dedicatedworker-module,sharedworker-module
+// META: script=./resources/evaluation-order-setup.js
+
+import './resources/evaluation-order-3-setup.js';
+import './resources/evaluation-order-3.1.mjs';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-3.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-3.html
new file mode 100644
index 0000000000..ef351acd28
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-3.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/evaluation-order-setup.js"></script>
+
+<script type="module" src="resources/evaluation-order-3-setup.js"></script>
+<script type="module" src="resources/evaluation-order-3.1.mjs"
+ onerror="unreachable()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-4.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-4.html
new file mode 100644
index 0000000000..f27678439d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-4.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/evaluation-order-setup.js"></script>
+
+<script>
+// Spec: https://html.spec.whatwg.org/C/#run-a-module-script
+setupTest("Module script queueing a microtask then top-level await", [
+ "step-4.1-1", "step-4.1-2",
+ "microtask-4.1",
+ "script-load",
+ "step-4.2-1", "step-4.2-2",
+ "microtask-4.2",
+]);
+window.onerror = testDone;
+</script>
+<script type="module" src="resources/evaluation-order-4.1.mjs"
+ onerror="unreachable()" onload="log.push('script-load')"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/checkpoint-after-error-event-worker-module.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/checkpoint-after-error-event-worker-module.js
new file mode 100644
index 0000000000..b9ae7142d5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/checkpoint-after-error-event-worker-module.js
@@ -0,0 +1,2 @@
+import "/resources/testharness.js";
+import "./checkpoint-after-error-event.js";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/checkpoint-after-error-event-worker.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/checkpoint-after-error-event-worker.js
new file mode 100644
index 0000000000..4694db1e44
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/checkpoint-after-error-event-worker.js
@@ -0,0 +1,2 @@
+importScripts("/resources/testharness.js");
+importScripts("checkpoint-after-error-event.js");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/checkpoint-after-error-event.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/checkpoint-after-error-event.js
new file mode 100644
index 0000000000..1a2b9404a7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/checkpoint-after-error-event.js
@@ -0,0 +1,89 @@
+// The `then` handlers for `Promise.resolve()` are evaluated in the first
+// microtasks checkpoint after `Promise.resolve()`.
+
+self.setup({allow_uncaught_exception: true});
+
+// ----------------------------------------------------------------
+// Check when microtasks checkpoint is performed
+// - Around `self`'s error event fired via
+// https://html.spec.whatwg.org/C/#report-the-error during
+// - https://html.spec.whatwg.org/C/#run-a-classic-script or
+// - https://html.spec.whatwg.org/C/#run-a-module-script.
+
+// The expectation is: the `then` handlers are evaluated after all error event
+// handlers are evaluated, not after each error event handler.
+
+// Just after each event handler is invoked,
+// https://webidl.spec.whatwg.org/#call-a-user-objects-operation
+// calls #clean-up-after-running-script, but this doesn't execute new
+// microtasks immediately, because:
+// - Before https://github.com/whatwg/html/pull/4352:
+// #report-the-error is called before #clean-up-after-running-script by
+// #run-a-classic-script/#run-a-module-script, so microtask checkpoint
+// is not performed because JavaScript execution context stack is not empty.
+// - After https://github.com/whatwg/html/pull/4352:
+// #report-the-error is called during #perform-a-microtask-checkpoint (because
+// it is called on rejection of promises), so #perform-a-microtask-checkpoint
+// is executed but early exited.
+self.log = [];
+
+self.addEventListener('error', () => {
+ log.push('handler 1');
+ Promise.resolve().then(() => log.push('handler 1 promise'));
+});
+self.addEventListener('error', () => {
+ log.push('handler 2');
+ Promise.resolve().then(() => log.push('handler 2 promise'));
+});
+
+// Microtasks should be executed before
+// #run-a-classic-script/#run-a-module-script is completed, and thus before
+// script evaluation scheduled by setTimeout().
+async_test(t => {
+ t.step_timeout(() => {
+ assert_array_equals(log, [
+ 'handler 1',
+ 'handler 2',
+ 'handler 1 promise',
+ 'handler 2 promise'
+ ]);
+ t.done();
+ },
+ 0);
+}, "Promise resolved during #report-the-error");
+
+// ----------------------------------------------------------------
+// Check when microtasks checkpoint is performed
+// around event events other than the `self` error event cases above.
+// In this case, microtasks are executed just after each event handler is
+// invoked via #clean-up-after-running-script called from
+// https://webidl.spec.whatwg.org/#call-a-user-objects-operation,
+// because the event handlers are executed outside the
+// #prepare-to-run-script/#clean-up-after-running-script scopes in
+// #run-a-classic-script/#run-a-module-script.
+self.log2 = [];
+self.t2 = async_test(
+ "Promise resolved during event handlers other than error");
+
+self.addEventListener('message', () => {
+ log2.push('handler 1');
+ Promise.resolve().then(() => log2.push('handler 1 promise'));
+});
+self.addEventListener('message', () => {
+ log2.push('handler 2');
+ Promise.resolve().then(t2.step_func_done(() => {
+ log2.push('handler 2 promise');
+ assert_array_equals(log2, [
+ 'handler 1',
+ 'handler 1 promise',
+ 'handler 2',
+ 'handler 2 promise'
+ ]);
+ }));
+});
+
+// ----------------------------------------------------------------
+
+done();
+
+throw new Error('script 1');
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-nothrow-setup.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-nothrow-setup.js
new file mode 100644
index 0000000000..1b42e99593
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-nothrow-setup.js
@@ -0,0 +1,5 @@
+// Spec: https://html.spec.whatwg.org/C/#run-a-module-script
+setupTest("Module script queueing a microtask", [
+ "body", // Step 6.
+ "microtask", // "Clean up after running script" at Step 8.
+]);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-nothrow.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-nothrow.js
new file mode 100644
index 0000000000..e19d9b1b13
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-nothrow.js
@@ -0,0 +1,2 @@
+queueMicrotask(() => globalThis.log.push("microtask"));
+globalThis.log.push("body");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-throw-setup.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-throw-setup.js
new file mode 100644
index 0000000000..651a494e53
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-throw-setup.js
@@ -0,0 +1,10 @@
+// Spec: https://html.spec.whatwg.org/C/#run-a-module-script
+setupTest("Module script queueing a microtask then throwing an exception", [
+ "body", // Step 6.
+ "microtask", // "Clean up after running script" at Step 8.
+ "global-error", // "Clean up after running script" at Step 8, because
+ // `evaluationPromise` is synchronously rejected and the rejection is
+ // processed in the microtask checkpoint here (See also Step 7).
+ // As `evaluationPromise` is rejected after the microtask queued during
+ // evaluation, "global-error" occurs after "microtask".
+]);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-throw.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-throw.js
new file mode 100644
index 0000000000..2451df1c15
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-1-throw.js
@@ -0,0 +1,4 @@
+queueMicrotask(() => globalThis.log.push("microtask"));
+globalThis.log.push("body");
+
+throw new Error("error");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-2-setup.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-2-setup.js
new file mode 100644
index 0000000000..46f7354538
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-2-setup.js
@@ -0,0 +1,10 @@
+// Spec: https://html.spec.whatwg.org/C/#run-a-module-script
+setupTest("Module script queueing a microtask then throwing an exception", [
+ "step-2.2-1", "step-2.2-2", // Step 6.
+ "microtask-2.2", // "Clean up after running script" at Step 8.
+ "global-error", // "Clean up after running script" at Step 8,
+ // because `evaluationPromise` is synchronously rejected and the rejection
+ // is processed in the microtask checkpoint here (See also Step 7).
+ // As `evaluationPromise` is rejected after the microtask queued during
+ // evaluation, "global-error" occurs after "microtask".
+]);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-2.1.mjs b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-2.1.mjs
new file mode 100644
index 0000000000..d6c2afa2f7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-2.1.mjs
@@ -0,0 +1,8 @@
+globalThis.log.push("step-2.1-1");
+queueMicrotask(() => globalThis.log.push("microtask-2.1"));
+globalThis.log.push("step-2.1-2");
+
+// import is evaluated first.
+import "./evaluation-order-2.2.mjs";
+
+globalThis.log.push("step-2.1-3");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-2.2.mjs b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-2.2.mjs
new file mode 100644
index 0000000000..5c67c4f9a2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-2.2.mjs
@@ -0,0 +1,7 @@
+globalThis.log.push("step-2.2-1");
+queueMicrotask(() => globalThis.log.push("microtask-2.2"));
+globalThis.log.push("step-2.2-2");
+
+globalThis.test_load.step_timeout(() => globalThis.testDone(), 0);
+
+throw new Error("error");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-3-setup.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-3-setup.js
new file mode 100644
index 0000000000..edc046910e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-3-setup.js
@@ -0,0 +1,7 @@
+setupTest("Module script queueing a microtask then throwing an exception", [
+ "step-3.1-1", "step-3.1-2", "step-3.1-3",
+ "microtask-3.1",
+ "step-3.2-1", "step-3.2-2",
+ "microtask-3.2",
+ "import-catch", "error",
+]);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-3.1.mjs b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-3.1.mjs
new file mode 100644
index 0000000000..ef320632af
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-3.1.mjs
@@ -0,0 +1,11 @@
+globalThis.log.push("step-3.1-1");
+queueMicrotask(() => globalThis.log.push("microtask-3.1"));
+globalThis.log.push("step-3.1-2");
+
+import("./evaluation-order-3.2.mjs").catch(
+ exception => {
+ globalThis.log.push("import-catch", exception.message);
+ globalThis.testDone();
+ });
+
+globalThis.log.push("step-3.1-3");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-3.2.mjs b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-3.2.mjs
new file mode 100644
index 0000000000..8ccb581206
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-3.2.mjs
@@ -0,0 +1,5 @@
+globalThis.log.push("step-3.2-1");
+queueMicrotask(() => globalThis.log.push("microtask-3.2"));
+globalThis.log.push("step-3.2-2");
+
+throw new Error("error");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-4.1.mjs b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-4.1.mjs
new file mode 100644
index 0000000000..f3347c1d28
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-4.1.mjs
@@ -0,0 +1,8 @@
+log.push("step-4.1-1");
+queueMicrotask(() => log.push("microtask-4.1"));
+log.push("step-4.1-2");
+
+await import("./evaluation-order-4.2.mjs");
+
+// Not happening as we throw in the above module.
+log.push("step-4.1-3");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-4.2.mjs b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-4.2.mjs
new file mode 100644
index 0000000000..96a5cca3a6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-4.2.mjs
@@ -0,0 +1,5 @@
+log.push("step-4.2-1");
+queueMicrotask(() => log.push("microtask-4.2"));
+log.push("step-4.2-2");
+
+throw new Error("error");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-setup.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-setup.js
new file mode 100644
index 0000000000..d2e28935c4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/evaluation-order-setup.js
@@ -0,0 +1,30 @@
+globalThis.setup({allow_uncaught_exception: true});
+
+// Must be called after previous tests are completed.
+globalThis.setupTest = (description, expectedLog) => {
+ globalThis.log = [];
+ globalThis.onerror = message => {
+ globalThis.log.push("global-error");
+ return true;
+ };
+ globalThis.onunhandledrejection =
+ () => globalThis.log.push('unhandled-promise-rejection');
+
+ globalThis.unreachable = () => globalThis.log.push("unreachable");
+
+ globalThis.test_load = async_test(description);
+ globalThis.testDone = globalThis.test_load.step_func_done(() => {
+ assert_array_equals(globalThis.log, expectedLog);
+ });
+
+ if (!('Window' in globalThis && globalThis instanceof Window)) {
+ // In workers, there are no <script> load event, so scheduling `testDone()`
+ // here, assuming the target script is loaded and evaluated soon.
+ globalThis.test_load.step_timeout(() => globalThis.testDone(), 1000);
+
+ // In workers, call `done()` here because the auto-generated `done()` calls
+ // by `any.js` etc. are at the end of main script and thus are not
+ // evaluated when the target script throws an exception.
+ done();
+ }
+};
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/resolve-then-throw.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/resolve-then-throw.js
new file mode 100644
index 0000000000..a841eb780a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/microtasks/resources/resolve-then-throw.js
@@ -0,0 +1,3 @@
+self.log.push('importScripts()ed script');
+Promise.resolve().then(() => self.log.push('promise'));
+throw new Error('foo');
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/bad-module-specifier.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/bad-module-specifier.js
new file mode 100644
index 0000000000..a53a3bebe7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/bad-module-specifier.js
@@ -0,0 +1,3 @@
+import "string-without-dot-slash-prefix";
+import "./this.js";
+log.push("bad-module-specifier");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/charset-01.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/charset-01.html
new file mode 100644
index 0000000000..7cd4916309
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/charset-01.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Root module scripts should always use UTF-8</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="module" src="../serve-with-content-type.py?fn=external-script-utf8.js&ct=text/javascript&dummy=1"></script>
+<script type="module">
+test(function() {
+ assert_equals(window.getSomeString(), "śćążź",
+ 'Should be decoded as UTF-8');
+}, 'UTF-8 module script');
+</script>
+
+<script type="module" src="../serve-with-content-type.py?fn=external-script-utf8.js&ct=text/javascript&dummy=2" charset="windows-1250"></script>
+<script type="module">
+test(function() {
+ assert_equals(window.getSomeString(), "śćążź",
+ 'Should be decoded as UTF-8');
+}, 'UTF-8 module script with wrong charset in attribute');
+</script>
+
+<script type="module" src="../serve-with-content-type.py?fn=external-script-utf8.js&ct=text/javascript%3Bcharset=windows-1250&dummy=3"></script>
+<script type="module">
+test(function() {
+ assert_equals(window.getSomeString(), "śćążź",
+ 'Should be decoded as UTF-8');
+}, 'UTF-8 module script with wrong charset in Content-Type');
+</script>
+
+<script type="module" src="../serve-with-content-type.py?fn=external-script-windows1250.js&ct=text/javascript&dummy=1"></script>
+<script type="module">
+test(function() {
+ assert_not_equals(window.getSomeString(), "śćążź",
+ 'Should be decoded as UTF-8');
+}, 'Non-UTF-8 module script');
+</script>
+
+<script type="module" src="../serve-with-content-type.py?fn=external-script-windows1250.js&ct=text/javascript&dummy=2" charset="windows-1250"></script>
+<script type="module">
+test(function() {
+ assert_not_equals(window.getSomeString(), "śćążź",
+ 'Should be decoded as UTF-8');
+}, 'Non-UTF-8 module script with charset in attribute');
+</script>
+
+<script type="module" src="../serve-with-content-type.py?fn=external-script-windows1250.js&ct=text/javascript%3Bcharset=windows-1250"></script>
+<script type="module">
+test(function() {
+ assert_not_equals(window.getSomeString(), "śćążź",
+ 'Should be decoded as UTF-8');
+}, 'Non-UTF-8 module script with charset in Content-Type');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/charset-02.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/charset-02.html
new file mode 100644
index 0000000000..c7c4517ad3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/charset-02.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Module scripts should ignore BOMs and always use UTF-8</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({allow_uncaught_exception: true});
+</script>
+<script type="module" src="../serve-with-content-type.py?fn=resources/bom-utf-8.js&ct=text/javascript"></script>
+<script type="module" src="../serve-with-content-type.py?fn=resources/bom-utf-16be.js&ct=text/javascript"></script>
+<script type="module" src="../serve-with-content-type.py?fn=resources/bom-utf-16le.js&ct=text/javascript"></script>
+<script type="module">
+test(function() {
+ assert_equals(window.executed_utf8_bom, '\u4e09\u6751\u304b\u306a\u5b50',
+ 'Should be decoded as UTF-8');
+}, 'UTF-8 module script with UTF-8 BOM');
+
+test(function() {
+ assert_equals(window.executed_utf16be_bom, undefined,
+ 'Should result in compile error because of UTF-16BE BOM');
+}, 'UTF-16 module script with UTF-16BE BOM');
+
+test(function() {
+ assert_equals(window.executed_utf16le_bom, undefined,
+ 'Should result in compile error because of UTF-16LE BOM');
+}, 'UTF-16 module script with UTF-16LE BOM');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/charset-03.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/charset-03.html
new file mode 100644
index 0000000000..666cb2e68b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/charset-03.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Imported module scripts should always use UTF-8</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script type="module" src="resources/import-utf8.js"></script>
+<script type="module">
+test(function() {
+ assert_equals(window.getSomeString(), "śćążź",
+ 'Should be decoded as UTF-8');
+}, 'UTF-8 imported module script');
+</script>
+
+<script type="module" src="resources/import-utf8-with-charset-header.js"></script>
+<script type="module">
+test(function() {
+ assert_equals(window.getSomeString(), "śćążź",
+ 'Should be decoded as UTF-8');
+}, 'UTF-8 imported module script with wrong charset in Content-Type');
+</script>
+
+<script type="module" src="resources/import-non-utf8.js"></script>
+<script type="module">
+test(function() {
+ assert_not_equals(window.getSomeString(), "śćążź",
+ 'Should be decoded as UTF-8');
+}, 'Non-UTF-8 imported module script');
+</script>
+
+<script type="module" src="resources/import-non-utf8-with-charset-header.js"></script>
+<script type="module">
+test(function() {
+ assert_not_equals(window.getSomeString(), "śćążź",
+ 'Should be decoded as UTF-8');
+}, 'Non-UTF-8 imported module script with charset in Content-Type');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-1.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-1.html
new file mode 100644
index 0000000000..50933da2c1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-1.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Choice of parse errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+ window.addEventListener("onunhandledrejection", unreachable);
+
+ const test_load = async_test(
+ "Parse errors in different files should be reported " +
+ "depending on different roots");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 4);
+
+ // Two different parse errors from different module scripts
+ // should be reported for each <script> element.
+ assert_equals(log[0].constructor, SyntaxError);
+ assert_equals(log[1], 1);
+
+ assert_equals(log[2].constructor, SyntaxError);
+ assert_equals(log[3], 2);
+
+ assert_not_equals(log[0], log[2],
+ 'two different parse errors should be reported');
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./choice-of-error-1a.js"
+ onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./choice-of-error-1b.js"
+ onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-1a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-1a.js
new file mode 100644
index 0000000000..f479e5e1fa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-1a.js
@@ -0,0 +1,2 @@
+import './choice-of-error-1b.js';
+import './syntaxerror.js?1c';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-1b.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-1b.js
new file mode 100644
index 0000000000..257f4a4678
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-1b.js
@@ -0,0 +1,2 @@
+import './choice-of-error-1a.js';
+import './syntaxerror.js?1d';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-2.html
new file mode 100644
index 0000000000..51adb09d11
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-2.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Choice of instantiation errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+ window.addEventListener("onunhandledrejection", unreachable);
+
+ const test_load = async_test(
+ "Instantiation errors in different files should be reported " +
+ "depending on different roots");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 4);
+
+ // Two different instantiation errors from different module scripts
+ // should be reported for each <script> element.
+ assert_equals(log[0].constructor, SyntaxError);
+ assert_equals(log[1], 1);
+
+ assert_equals(log[2].constructor, SyntaxError);
+ assert_equals(log[3], 2);
+
+ assert_not_equals(log[0], log[2],
+ 'two different instantiation errors should be reported');
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./choice-of-error-2a.js"
+ onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./choice-of-error-2b.js"
+ onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-2a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-2a.js
new file mode 100644
index 0000000000..2dc7aac11a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-2a.js
@@ -0,0 +1,2 @@
+import './choice-of-error-2b.js';
+import './instantiation-error-1.js?2c';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-2b.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-2b.js
new file mode 100644
index 0000000000..2adb9eea59
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-2b.js
@@ -0,0 +1,2 @@
+import './choice-of-error-2a.js';
+import './instantiation-error-1.js?2d';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-3.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-3.html
new file mode 100644
index 0000000000..bc52119bfe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-3.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>Choice of evaluation errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+ window.addEventListener("onunhandledrejection", unreachable);
+
+ const test_load = async_test(
+ "Evaluation errors are cached in intermediate module scripts");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 5);
+
+ // Evaluation errors, unlike parse/instantiation errors, are remembered
+ // and cached in module scripts between the root and the script that
+ // caused an evaluation error, and thus the same evaluation error
+ // is reported for both <script> elements.
+ assert_equals(log[0], "throw2");
+ assert_true(log[1].bar);
+ assert_equals(log[2], 1);
+
+ assert_true(log[3].bar);
+ assert_equals(log[4], 2);
+
+ assert_equals(log[1], log[3], 'evaluation errors must be the same');
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./choice-of-error-3a.js"
+ onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./choice-of-error-3b.js"
+ onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-3a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-3a.js
new file mode 100644
index 0000000000..71154674a3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-3a.js
@@ -0,0 +1,2 @@
+import './choice-of-error-3b.js';
+import './throw.js?3c';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-3b.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-3b.js
new file mode 100644
index 0000000000..2131a35eff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/choice-of-error-3b.js
@@ -0,0 +1,2 @@
+import './choice-of-error-3a.js';
+import './throw2.js?3d';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/compilation-error-1.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/compilation-error-1.html
new file mode 100644
index 0000000000..ff580d4899
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/compilation-error-1.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Handling of compilation errors, 1</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that syntax errors lead to SyntaxError events on window, " +
+ "and that exceptions are remembered.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 5);
+ assert_equals(log[0].constructor, SyntaxError);
+ assert_true(log.every(exn => exn === log[0]));
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./syntaxerror.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror-nested.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror-nested.js" onerror="unreachable()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/compilation-error-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/compilation-error-2.html
new file mode 100644
index 0000000000..131a6e439f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/compilation-error-2.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Handling of compilation errors, 2</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that syntax errors lead to SyntaxError events on window, " +
+ "and that exceptions are remembered.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 5);
+ assert_equals(log[0].constructor, SyntaxError);
+ assert_true(log.every(exn => exn === log[0]));
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./syntaxerror-nested.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror-nested.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror-nested.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror.js" onerror="unreachable()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/credentials.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/credentials.sub.html
new file mode 100644
index 0000000000..983961ae44
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/credentials.sub.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+document.cookie = 'same=1';
+
+const setCookiePromise = fetch(
+ 'http://{{domains[www2]}}:{{ports[http][0]}}/cookies/resources/set-cookie.py?name=cross&path=/html/semantics/scripting-1/the-script-element/module/',
+ {
+ mode: 'no-cors',
+ credentials: 'include',
+ });
+
+const windowLoadPromise = new Promise(resolve => {
+ window.addEventListener('load', () => {
+ resolve();
+ });
+});
+
+promise_test(t => {
+ const iframe = document.createElement('iframe');
+
+ return Promise.all([setCookiePromise, windowLoadPromise]).then(() => {
+ const messagePromise = new Promise(resolve => {
+ window.addEventListener('message', event => {
+ resolve();
+ });
+ });
+
+ iframe.src = 'resources/credentials-iframe.sub.html';
+ document.body.appendChild(iframe);
+
+ return messagePromise;
+ }).then(() => {
+ const w = iframe.contentWindow;
+
+ assert_equals(w.sameOriginNone, 'found',
+ 'Modules should be loaded with the credentials when the crossOrigin attribute is not specified and the target is same-origin');
+ assert_equals(w.sameOriginAnonymous, 'found',
+ 'Modules should be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is same-origin');
+ assert_equals(w.sameOriginUseCredentials, 'found',
+ 'Modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is same-origin');
+ assert_equals(w.crossOriginNone, 'not found',
+ 'Modules should not be loaded with the credentials when the crossOrigin attribute is not specified and the target is cross-origin');
+ assert_equals(w.crossOriginAnonymous, 'not found',
+ 'Modules should not be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is cross-origin');
+ assert_equals(w.crossOriginUseCredentials, 'found',
+ 'Modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is cross-origin');
+
+ assert_equals(w.sameOriginNoneDescendant, 'found',
+ 'Descendant modules should be loaded with the credentials when the crossOrigin attribute is not specified and the target is same-origin');
+ assert_equals(w.sameOriginAnonymousDescendant, 'found',
+ 'Descendant modules should be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is same-origin');
+ assert_equals(w.sameOriginUseCredentialsDescendant, 'found',
+ 'Descendant modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is same-origin');
+ assert_equals(w.crossOriginNoneDescendant, 'not found',
+ 'Descendant modules should not be loaded with the credentials when the crossOrigin attribute is not specified and the target is cross-origin');
+ assert_equals(w.crossOriginAnonymousDescendant, 'not found',
+ 'Descendant modules should not be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is cross-origin');
+ assert_equals(w.crossOriginUseCredentialsDescendant, 'found',
+ 'Descendant modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is cross-origin');
+});
+}, 'Modules should be loaded with or without the credentials based on the same-origin-ness and the crossOrigin attribute');
+</script>
+<body>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-common.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-common.js
new file mode 100644
index 0000000000..59bf0fd42f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-common.js
@@ -0,0 +1,8 @@
+document._log = [];
+window.addEventListener("error", function (ev) {
+ var errorSerialized = ev.lineno + "-" + ev.colno;
+ document._log.push(errorSerialized);
+});
+window.addEventListener("load", function () {
+ document._log = document._log.join(",");
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-different.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-different.sub.html
new file mode 100644
index 0000000000..8eb3292e89
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-different.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>html-script-module-crossOrigin-import-NoCORS</title>
+ <script src="crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>html-script-module-crossOrigin-import-NoCORS</h1>
+ <script type="module" onerror="document._log.push('error');">
+
+ import { foo } from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/crossorigin-scripterror.js";
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-missingheader.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-missingheader.sub.html
new file mode 100644
index 0000000000..cccb30f718
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-missingheader.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>html-script-module-crossOrigin-root-BlockedMissingHeader</title>
+ <script src="crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>html-script-module-crossOrigin-root-BlockedMissingHeader</h1>
+ <script type="module" onerror="document._log.push('error');" crossorigin>
+
+ import { foo } from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/crossorigin-scripterror.js";
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-same.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-same.sub.html
new file mode 100644
index 0000000000..84f4de1d7e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-same.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>html-script-module-crossOrigin-root-WithCORS</title>
+ <script src="crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>html-script-module-crossOrigin-root-WithCORS</h1>
+ <script type="module" crossorigin>
+
+ import { foo } from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/crossorigin-scripterror.js?pipe=header(Access-Control-Allow-Origin,*)";
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-wrongheader.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-wrongheader.sub.html
new file mode 100644
index 0000000000..5743a9e304
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-import-wrongheader.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>html-script-module-crossOrigin-root-BlockedWrongHeader</title>
+ <script src="crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>html-script-module-crossOrigin-root-BlockedWrongHeader</h1>
+ <script type="module" onerror="document._log.push('error');" crossorigin>
+
+ import { foo } from "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/crossorigin-scripterror.js?pipe=header(Access-Control-Allow-Origin,http://{{domains[www2]}}:{{ports[http][0]}})";
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-different.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-different.sub.html
new file mode 100644
index 0000000000..54d4354c53
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-different.sub.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>html-script-module-crossOrigin-root-NoCORS</title>
+ <script src="crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>html-script-module-crossOrigin-root-NoCORS</h1>
+ <script type="module" src="http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/crossorigin-scripterror.js" onerror="document._log.push('error');"></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-missingheader.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-missingheader.sub.html
new file mode 100644
index 0000000000..03943002ba
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-missingheader.sub.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>html-script-module-crossOrigin-root-BlockedMissingHeader</title>
+ <script src="crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>html-script-module-crossOrigin-root-BlockedMissingHeader</h1>
+ <script type="module" src="http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/crossorigin-scripterror.js" onerror="document._log.push('error');" crossorigin></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-same.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-same.sub.html
new file mode 100644
index 0000000000..3ec839e9a9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-same.sub.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>html-script-module-crossOrigin-root-WithCORS</title>
+ <script src="crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>html-script-module-crossOrigin-root-WithCORS</h1>
+ <script type="module" src="http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/crossorigin-scripterror.js?pipe=header(Access-Control-Allow-Origin,*)" crossorigin></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-wrongheader.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-wrongheader.sub.html
new file mode 100644
index 0000000000..c45736a896
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-root-wrongheader.sub.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>html-script-module-crossOrigin-root-BlockedWrongHeader</title>
+ <script src="crossorigin-common.js"></script>
+</head>
+<body>
+ <h1>html-script-module-crossOrigin-root-BlockedWrongHeader</h1>
+ <script type="module" src="http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/crossorigin-scripterror.js?pipe=header(Access-Control-Allow-Origin,http://{{domains[www2]}}:{{ports[http][0]}})" onerror="document._log.push('error');" crossorigin></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-scripterror.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-scripterror.js
new file mode 100644
index 0000000000..29d04ec619
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin-scripterror.js
@@ -0,0 +1,8 @@
+export var foo = {};
+
+// Push an event to the log indicating that the script was executed.
+document._log.push("running");
+
+// Deliberately trigger an error to test what details of the error
+// the (possibly) cross-origin parent can listen to.
+nonExistentMethod();
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin.html
new file mode 100644
index 0000000000..5c8d6667b0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/crossorigin.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<html>
+<head>
+ <title>html-script-module-crossOrigin</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <h1>html-script-module-crossOrigin</h1>
+ <iframe id="root-WithCORS" src="crossorigin-root-same.sub.html"></iframe>
+ <iframe id="root-NoCORS" src="crossorigin-root-different.sub.html"></iframe>
+ <iframe id="root-BlockedMissingHeader" src="crossorigin-root-missingheader.sub.html"></iframe>
+ <iframe id="root-BlockedWrongHeader" src="crossorigin-root-wrongheader.sub.html"></iframe>
+ <iframe id="import-WithCORS" src="crossorigin-import-same.sub.html"></iframe>
+ <iframe id="import-NoCORS" src="crossorigin-import-different.sub.html"></iframe>
+ <iframe id="import-BlockedMissingHeader" src="crossorigin-import-missingheader.sub.html"></iframe>
+ <iframe id="import-BlockedWrongHeader" src="crossorigin-import-wrongheader.sub.html"></iframe>
+ <script>
+
+ var tests = [
+ { "obj": async_test("Root module, Error in CORS-same-origin script"), "id": "root-WithCORS", "expected": "running,8-1" },
+ { "obj": async_test("Root module, Blocked script download, missing CORS ACAO header"), "id": "root-NoCORS", "expected": "error" },
+ { "obj": async_test("Root module, Blocked script download, crossorigin attribute with missing CORS ACAO header"), "id": "root-BlockedMissingHeader", "expected": "error" },
+ { "obj": async_test("Root module, Blocked script download, mismatched CORS ACAO header"), "id": "root-BlockedWrongHeader", "expected": "error" },
+ { "obj": async_test("Imported module, Error in CORS-same-origin script"), "id": "import-WithCORS", "expected": "running,8-1" },
+ { "obj": async_test("Imported module, Blocked script download, missing CORS ACAO header"), "id": "import-NoCORS", "expected": "error" },
+ { "obj": async_test("Imported module, Blocked script download, crossorigin attribute with missing CORS ACAO header"), "id": "import-BlockedMissingHeader", "expected": "error" },
+ { "obj": async_test("Imported module, Blocked script download, mismatched CORS ACAO header"), "id": "import-BlockedWrongHeader", "expected": "error" },
+ ];
+
+ window.addEventListener("load", function () {
+ tests.forEach(function (test) {
+ var target = document.getElementById(test.id);
+ test.obj.step(function () {
+ assert_equals(target.contentDocument._log, test.expected, "Unexpected _log value");
+ });
+ test.obj.done();
+ });
+ });
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/currentScript-null.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/currentScript-null.html
new file mode 100644
index 0000000000..146a9db60d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/currentScript-null.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="module" src="set-currentScript-on-window.js"></script>
+<script type="module">
+import { currentScriptOnImportedModule } from "./currentscript.js";
+
+test(() => {
+ assert_equals(document.currentScript, null, "document.currentScript on inline scripts should be null");
+ assert_equals(currentScriptOnImportedModule, null, "document.currentScript on imported scripts should be null");
+ assert_equals(window.currentScriptRecorded, null, "document.currentScript on external module scripts should be null");
+}, "currentScript on script type=module should be all null");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/currentscript.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/currentscript.js
new file mode 100644
index 0000000000..547359ff96
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/currentscript.js
@@ -0,0 +1 @@
+export let currentScriptOnImportedModule = window.document.currentScript;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/custom-element-exception.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/custom-element-exception.html
new file mode 100644
index 0000000000..bd77a8f1bb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/custom-element-exception.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Handling of exceptions in custom element constructors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that exceptions from the constructor of a custom element " +
+ "inside a module are propagated as expected.\n");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(1, log.length);
+ const exception = log[0];
+ assert_true(exception instanceof Error);
+ assert_equals(exception.message, "custom element error");
+ }));
+</script>
+<script type="module">
+ class XThrower extends HTMLElement {
+ constructor() {
+ super();
+ throw new Error("custom element error");
+ }
+ }
+ customElements.define("x-thrower", XThrower);
+ document.createElement("x-thrower");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access-a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access-a.js
new file mode 100644
index 0000000000..1f91f93eb1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access-a.js
@@ -0,0 +1,3 @@
+log.push("cycle-tdz-access-a");
+import { Y } from "./cycle-tdz-access.js";
+export var X = Y;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access.js
new file mode 100644
index 0000000000..9df68b3b27
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access.js
@@ -0,0 +1,3 @@
+log.push("cycle-tdz-access");
+import { X } from "./cycle-tdz-access-a.js";
+export let Y = X;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable-a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable-a.js
new file mode 100644
index 0000000000..12994f23d0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable-a.js
@@ -0,0 +1,2 @@
+export {x} from "./cycle-unresolvable.js";
+log.push("cycle-unresolvable-a");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable.js
new file mode 100644
index 0000000000..61c6d8dcb0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable.js
@@ -0,0 +1,2 @@
+export {x} from "./cycle-unresolvable-a.js";
+log.push("cycle-unresolvable");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/duplicated-imports-1.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/duplicated-imports-1.html
new file mode 100644
index 0000000000..57002a3e07
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/duplicated-imports-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Importing a module multiple times with the same specifier</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+window.log = [];
+</script>
+<script type="module">
+import { foo } from './export-something.js';
+import { set_foo } from './export-something.js';
+import default1 from './export-default.js';
+import default2 from './export-default.js';
+
+test(() => {
+ assert_array_equals(log, ['export-something', 'export-default']);
+ assert_equals(foo, 42);
+ set_foo(43);
+ assert_equals(foo, 43);
+ assert_equals(default1, "fox");
+ assert_equals(default2, "fox");
+}, 'Duplicated imports with the same specifier');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/duplicated-imports-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/duplicated-imports-2.html
new file mode 100644
index 0000000000..6a01495c33
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/duplicated-imports-2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Importing a module multiple times with the different specifier</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+window.log = [];
+</script>
+<script type="module">
+import { foo } from './export-something.js';
+import { set_foo } from '../module/export-something.js';
+import default1 from './export-default.js';
+import default2 from '../module/export-default.js';
+
+test(() => {
+ assert_array_equals(log, ['export-something', 'export-default']);
+ assert_equals(foo, 42);
+ set_foo(43);
+ assert_equals(foo, 43);
+ assert_equals(default1, "fox");
+ assert_equals(default2, "fox");
+}, 'Duplicated imports with the different specifier');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url-worker-importScripts.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url-worker-importScripts.html
new file mode 100644
index 0000000000..817cf6d5dd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url-worker-importScripts.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<title>Base URLs used in resolving specifiers in dynamic imports from importScripts()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+fetch_tests_from_worker(new Worker("./worker-importScripts.sub.js"));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url-worker.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url-worker.sub.html
new file mode 100644
index 0000000000..a12204281c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url-worker.sub.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<title>Base URLs used in resolving specifiers in dynamic imports from workers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+fetch_tests_from_worker(new Worker(
+ "../beta/redirect.py?location=http://{{host}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js"));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url.sub.html
new file mode 100644
index 0000000000..f7d4927a10
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url.sub.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Base URLs used in resolving specifiers in dynamic imports</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+self.testName = "same origin classic <script>";
+self.baseUrlSanitized = false;
+</script>
+<script src="../beta/redirect.py?location=http://{{host}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js"></script>
+
+<script>
+self.testName = "cross origin classic <script> without crossorigin attribute";
+self.baseUrlSanitized = true;
+</script>
+<script src="../beta/redirect.py?location=http://{{hosts[alt][]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js"></script>
+
+<script>
+self.testName = "cross origin classic <script> with crossorigin attribute";
+self.baseUrlSanitized = false;
+</script>
+<script src="../beta/redirect.py?location=http://{{hosts[alt][]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js%3Fpipe=header(Access-Control-Allow-Origin,*)" crossorigin></script>
+
+<script>
+self.testName = "cross origin module <script>";
+self.baseUrlSanitized = false;
+</script>
+<script src="../beta/redirect.py?location=http://{{hosts[alt][]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js%3Fpipe=header(Access-Control-Allow-Origin,*)" type="module"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/code-cache.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/code-cache.js
new file mode 100644
index 0000000000..8a8530c9b6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/code-cache.js
@@ -0,0 +1,9 @@
+promise_test(() => {
+ return (new Function('w', 'return import(w)'))("./import.js?Function")
+ .then(module => assert_equals(module.A.from, 'alpha/import.js'));
+}, 'alpha - Function');
+
+promise_test(() => {
+ return eval('import("./import.js?eval")')
+ .then(module => assert_equals(module.A.from, 'alpha/import.js'));
+}, 'alpha - eval');
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/import.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/import.js
new file mode 100644
index 0000000000..b2ac52df2a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/import.js
@@ -0,0 +1 @@
+export const A = { "from": "alpha/import.js" };
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/worker-importScripts.sub.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/worker-importScripts.sub.js
new file mode 100644
index 0000000000..904d32f9bf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/worker-importScripts.sub.js
@@ -0,0 +1,15 @@
+"use strict";
+
+importScripts("/resources/testharness.js");
+
+// CORS-same-origin
+self.testName = "same-origin importScripts()";
+self.baseUrlSanitized = false;
+importScripts("../beta/redirect.py?location=http://{{host}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js");
+
+// CORS-cross-origin
+self.testName = "cross-origin importScripts()";
+self.baseUrlSanitized = true;
+importScripts("../beta/redirect.py?location=http://{{hosts[alt][]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js");
+
+done();
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/beta/code-cache.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/beta/code-cache.js
new file mode 100644
index 0000000000..1c2a2b636a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/beta/code-cache.js
@@ -0,0 +1,9 @@
+promise_test(() => {
+ return (new Function('w', 'return import(w)'))("./import.js?Function")
+ .then(module => assert_equals(module.A.from, 'beta/import.js'));
+}, 'beta - Function');
+
+promise_test(() => {
+ return eval('import("./import.js?eval")')
+ .then(module => assert_equals(module.A.from, 'beta/import.js'));
+}, 'beta - eval');
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/beta/import.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/beta/import.js
new file mode 100644
index 0000000000..7de1c68182
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/beta/import.js
@@ -0,0 +1 @@
+export const A = { "from": "beta/import.js" };
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/beta/redirect.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/beta/redirect.py
new file mode 100644
index 0000000000..f2fd1ebd51
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/beta/redirect.py
@@ -0,0 +1,19 @@
+def main(request, response):
+ """Simple handler that causes redirection.
+
+ The request should typically have two query parameters:
+ status - The status to use for the redirection. Defaults to 302.
+ location - The resource to redirect to.
+ """
+ status = 302
+ if b"status" in request.GET:
+ try:
+ status = int(request.GET.first(b"status"))
+ except ValueError:
+ pass
+
+ response.status = status
+
+ location = request.GET.first(b"location")
+
+ response.headers.set(b"Location", location)
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url-workers.window.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url-workers.window.js
new file mode 100644
index 0000000000..70cb20fa57
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url-workers.window.js
@@ -0,0 +1,60 @@
+function objectUrlFromModule(module) {
+ const blob = new Blob([module], { type: "text/javascript" });
+ return URL.createObjectURL(blob);
+}
+
+const moduleText = `export const foo = "bar";`;
+
+async_test((t) => {
+ const moduleBlobUrl = objectUrlFromModule(moduleText);
+ t.add_cleanup(() => URL.revokeObjectURL(moduleBlobUrl));
+
+ const worker = new Worker("./resources/blob-url-worker.js");
+ worker.postMessage(moduleBlobUrl);
+
+ worker.addEventListener(
+ "message",
+ t.step_func_done((evt) => {
+ assert_true(evt.data.importSucceeded);
+ assert_equals(evt.data.module.foo, "bar");
+ })
+ );
+}, "A blob URL created in a window agent can be imported from a worker");
+
+async_test((t) => {
+ const moduleBlobUrl = objectUrlFromModule(moduleText);
+ URL.revokeObjectURL(moduleBlobUrl);
+
+ const worker = new Worker("./resources/blob-url-worker.js");
+ worker.postMessage(moduleBlobUrl);
+
+ worker.addEventListener(
+ "message",
+ t.step_func_done((evt) => {
+ assert_false(evt.data.importSucceeded);
+ assert_equals(evt.data.errorName, "TypeError");
+ })
+ );
+}, "A blob URL revoked in a window agent will not resolve in a worker");
+
+promise_test(async (t) => {
+ const moduleBlobUrl = objectUrlFromModule(moduleText);
+
+ await import(moduleBlobUrl);
+
+ URL.revokeObjectURL(moduleBlobUrl);
+
+ const worker = new Worker("./resources/blob-url-worker.js");
+ worker.postMessage(moduleBlobUrl);
+
+ await new Promise((resolve) => {
+ worker.addEventListener(
+ "message",
+ t.step_func((evt) => {
+ assert_false(evt.data.importSucceeded);
+ assert_equals(evt.data.errorName, "TypeError");
+ resolve();
+ })
+ );
+ });
+}, "A revoked blob URL will not resolve in a worker even if it's in the window's module graph");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url.any.js
new file mode 100644
index 0000000000..0719a18bf1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url.any.js
@@ -0,0 +1,66 @@
+// META: global=window,dedicatedworker,sharedworker,dedicatedworker-module,sharedworker-module
+
+function objectUrlFromModule(module) {
+ const blob = new Blob([module], { type: "text/javascript" });
+ return URL.createObjectURL(blob);
+}
+
+const moduleText = `export const foo = "bar";`;
+
+promise_test(async (t) => {
+ const moduleBlobUrl = objectUrlFromModule(moduleText);
+ t.add_cleanup(() => URL.revokeObjectURL(moduleBlobUrl));
+
+ const module = await import(moduleBlobUrl);
+ assert_equals(module.foo, "bar");
+}, "Blob URLs are supported in dynamic imports");
+
+promise_test(async (t) => {
+ const moduleBlobUrl = objectUrlFromModule(moduleText);
+ t.add_cleanup(() => URL.revokeObjectURL(moduleBlobUrl));
+
+ const module1 = await import(moduleBlobUrl);
+ const module2 = await import(moduleBlobUrl);
+ assert_equals(module1, module2);
+}, "Identical blob URLs resolve to the same module");
+
+promise_test(async (t) => {
+ const moduleBlob = new Blob([moduleText], { type: "text/javascript" });
+ const moduleBlobUrl1 = URL.createObjectURL(moduleBlob);
+ const moduleBlobUrl2 = URL.createObjectURL(moduleBlob);
+ t.add_cleanup(() => {
+ URL.revokeObjectURL(moduleBlobUrl1);
+ URL.revokeObjectURL(moduleBlobUrl2);
+ });
+
+ const module1 = await import(moduleBlobUrl1);
+ const module2 = await import(moduleBlobUrl2);
+ assert_not_equals(module1, module2);
+}, "Different blob URLs pointing to the same blob resolve to different modules");
+
+promise_test(async (t) => {
+ const moduleBlobUrl = objectUrlFromModule(moduleText);
+ URL.revokeObjectURL(moduleBlobUrl);
+
+ await promise_rejects_js(t, TypeError, import(moduleBlobUrl));
+}, "A revoked blob URL will not resolve");
+
+promise_test(async () => {
+ const moduleBlobUrl = objectUrlFromModule(moduleText);
+ const module1 = await import(moduleBlobUrl);
+
+ URL.revokeObjectURL(moduleBlobUrl);
+
+ const module2 = await import(moduleBlobUrl);
+ assert_equals(module1, module2);
+}, "A revoked blob URL will resolve if it's already in the module graph");
+
+promise_test(async () => {
+ const moduleBlobUrl = objectUrlFromModule(moduleText);
+
+ const importPromise = import(moduleBlobUrl);
+ URL.revokeObjectURL(moduleBlobUrl);
+
+ const module = await importPromise;
+ assert_equals(module.foo, "bar");
+}, "Revoking a blob URL immediately after calling import will not fail");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/code-cache-base-url.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/code-cache-base-url.html
new file mode 100644
index 0000000000..688a6193a5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/code-cache-base-url.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!--
+Regression test for https://crbug.com/990810:
+Functions with the same source code shouldn't be cached if their referencing
+scripts and host defined options are different.
+
+Each Function in the following three scripts should have different base URLs
+respectively, but in https://crbug.com/990810 the Function in
+`gamma/code-cache.js` reuses the Function in `beta/code-cache.js`, resulting in
+wrong base URL.
+-->
+
+<script src="alpha/code-cache.js"></script>
+<script src="beta/code-cache.js"></script>
+<script src="gamma/code-cache.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/code-cache-nonce.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/code-cache-nonce.html
new file mode 100644
index 0000000000..bd920f8107
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/code-cache-nonce.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+// Regression test for https://crbug.com/979351:
+// This test loads a same script file (`resources/code-cache-nonce.js`)
+// with three different nonces (i.e. different host defined options).
+// Dynamic imports from the script therefore should have different nonces,
+// but when code caching ignores the difference in nonces, the first nonce
+// ('abc') is reused incorrectly for subsequent dynamic imports, causing
+// CSP violation (and thus dynamic import rejection).
+
+function runTest(nonce, description) {
+ // Perform a dynamic import with nonce=`nonce`
+ // from a page (`iframe`) with a matching CSP script-src 'nonce-`nonce`'.
+ // This should be successful.
+ promise_test(t => {
+ return new Promise((resolve, reject) => {
+ const iframe = document.createElement('iframe');
+ iframe.src = 'resources/code-cache-nonce-iframe.sub.html?nonce=' + nonce;
+ iframe.onload = () => {
+ // `globalThis.promise` is set by `resources/code-cache-nonce.js`.
+ // `t.step_timeout()` is workaround for https://crbug.com/1247801.
+ globalThis.promise.then(
+ v => t.step_timeout(() => resolve(v), 0),
+ v => t.step_timeout(() => reject(v), 0)
+ );
+ };
+ document.body.appendChild(iframe);
+ t.add_cleanup(() => iframe.remove());
+ });
+ }, description);
+}
+
+// As `promise_test` are serialized, each iframe is created after previous
+// iframes and scripts are completely loaded.
+runTest('abc', 'First dynamic import should use nonce=abc');
+runTest('def', 'Second dynamic import should use nonce=def');
+runTest('ghi', 'Third dynamic import should use nonce=ghi');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/delay-load-event.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/delay-load-event.html
new file mode 100644
index 0000000000..5ec6433e65
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/delay-load-event.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>Dynamic imports don't delay the load event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Dynamic imports don't #delay-the-load-event.
+// Therefore, Window load event would be fired
+// just after the dynamic import() below starts.
+window.loaded = [];
+window.addEventListener('load', () => loaded.push('Window load event'));
+promise_test(t => {
+ loaded.push('import start');
+ // This 'loading' log is added to detect the previous Chromium behavior
+ // where the Window load event is delayed until just before script
+ // element's load event.
+ t.step_timeout(() => loaded.push('loading'), 1000);
+ return import("../resources/slow-module.js?pipe=trickle(d2)")
+ .then(() => {
+ assert_array_equals(
+ loaded,
+ ['import start', 'Window load event', 'loading', 'slow'],
+ "Window load event shouldn't be delayed by dynamic imports");
+ });
+}, "Dynamic imports don't delay the load event.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials-setTimeout.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials-setTimeout.sub.html
new file mode 100644
index 0000000000..189aa819fa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials-setTimeout.sub.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/dynamic-import-credentials-helper.sub.js"></script>
+
+<script type="text/javascript">
+runTestsFromIframe('../resources/dynamic-import-credentials-setTimeout-iframe.sub.html');
+</script>
+<body>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub.html
new file mode 100644
index 0000000000..910644531c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/dynamic-import-credentials-helper.sub.js"></script>
+
+<script type="text/javascript">
+runTestsFromIframe('../resources/dynamic-import-credentials-iframe.sub.html');
+</script>
+<body>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-fetch-error.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-fetch-error.sub.html
new file mode 100644
index 0000000000..12738c7b97
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-fetch-error.sub.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<title>import(): error cases occuring during fetching</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script type="module">
+const cases = [
+ ["wrong MIME type", "../errorhandling-wrongMimetype.js?pipe=header(Content-Type,text/plain)"],
+ ["wrong MIME type of a dependency", "../errorhandling-wrongMimetype-import.js"],
+ ["404", "../resources/404-but-js.asis"],
+ ["404 of a dependency", "../resources/imports-404-but-js.js"],
+ ["500", "../resources/500-but-js.asis"],
+ ["500 of a dependency", "../resources/imports-500-but-js.js"],
+ ["cross-origin module (without CORS)", "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/imports-a.js"],
+ ["cross-origin module dependency (without CORS)", "../resources/imports-b-cross-origin.sub.js"]
+];
+
+for (const [label, specifier] of cases) {
+ promise_test(t => {
+ return promise_rejects_js(t, TypeError, import(specifier));
+ }, "import() must reject when there is a " + label);
+
+ promise_test(async t => {
+ // The use of ?x is used to separate tests so they don't interfere with each other.
+ // (However, it shouldn't matter anyway, in a spec-compliant implementation.)
+ const suffix = (specifier.includes("?") ? "&" : "?") + "x";
+ const promise1 = import(specifier + suffix);
+ const promise2 = import(specifier + suffix);
+
+ await promise_rejects_js(t, TypeError, promise1, "It must reject the first time");
+ await promise_rejects_js(t, TypeError, promise2, "It must reject the second time");
+
+ const error1 = await promise1.catch(e => e);
+ const error2 = await promise2.catch(e => e);
+
+ assert_not_equals(error1, error2, "The error objects must be different");
+ }, "import() must reject with a different error object for each import when there is a " + label);
+}
+
+promise_test(async t => {
+ const id = token();
+ const url = `./resources/status-changing-script.py?id=${id}`;
+
+ // Serve HTTP 404 for the first import().
+ await fetch(url + '&newStatus=404');
+ const promise1 = import(url);
+ await promise_rejects_js(t, TypeError, promise1,
+ "First import() must be rejected due to 404");
+
+ // Serve HTTP 200 after the first import() completes.
+ await fetch(url + '&newStatus=200');
+ const r = await fetch(url, { cache: 'no-cache' });
+ assert_equals(r.status, 200);
+
+ const promise2 = import(url);
+ await promise_rejects_js(t, TypeError, promise2,
+ "Second import() must be rejected, because the result of " +
+ "the first import() is cached in the module map");
+}, "import() fetch errors must be cached");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-script-error.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-script-error.html
new file mode 100644
index 0000000000..e9b10e3bab
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-script-error.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<title>import(): error cases caused by the imported module script</title>
+<link rel="author" title="Kouhei Ueno" href="mailto:kouhei@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script type="module">
+const cases = [
+ ["parse error", "../syntaxerror.js", SyntaxError],
+ ["bad module specifier", "does-not-start-with-dot.js", TypeError, { differentErrorObjects: true }],
+ ["bad module specifier in a dependency", "../bad-module-specifier.js", TypeError],
+ ["instantiation error", "../instantiation-error-1.js", SyntaxError, { differentErrorObjects: true }],
+ ["evaluation error", "../throw-error.js", Error]
+];
+
+for (const [label, specifier, error, { differentErrorObjects } = {}] of cases) {
+ promise_test(t => {
+ return promise_rejects_js(t, error, import(specifier));
+ }, "import() must reject when there is a " + label);
+
+ const errorObjectsLabel = differentErrorObjects ? "different error objects" : "the same error object";
+ promise_test(async t => {
+ // The use of ?x is used to separate tests so they don't interfere with each other.
+ // (However, it shouldn't matter anyway, in a spec-compliant implementation.)
+ const promise1 = import(specifier + "?x");
+ const promise2 = import(specifier + "?x");
+
+ await promise_rejects_js(t, error, promise1, "It must reject the first time");
+ await promise_rejects_js(t, error, promise2, "It must reject the second time");
+
+ const error1 = await promise1.catch(e => e);
+ const error2 = await promise2.catch(e => e);
+
+ if (differentErrorObjects) {
+ assert_not_equals(error1, error2, "The error objects must be different");
+ } else {
+ assert_equals(error1, error2, "The error objects must be equal");
+ }
+ }, `import() must reject with ${errorObjectsLabel} for each import when there is a ${label}`);
+}
+
+promise_test(t => {
+ delete window.before_throwing_error;
+ delete window.after_throwing_error;
+
+ return promise_rejects_js(t, Error, import("../throw-error.js?y")).then(() => {
+ assert_true(window.before_throwing_error,
+ "the module script should run to a point where it throws exception");
+ assert_equals(window.after_throwing_error, undefined,
+ "the module script should not run after it throws exception");
+ });
+}, "import()ing a module with an evaluation error must stop evaluation");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports.html
new file mode 100644
index 0000000000..9807e21b0d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Basic dynamic imports</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="module">
+promise_test(t => {
+ return import("./../imports-a.js").then(module => {
+ assert_true(window.evaluated_imports_a);
+ assert_equals(module.A["from"], "imports-a.js");
+ });
+}, "Dynamic imports should resolve module.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js
new file mode 100644
index 0000000000..ec7784983d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js
@@ -0,0 +1,58 @@
+"use strict";
+
+// This script triggers import(), and thus the base URL of this script
+// (either loaded by `<script>` or `importScripts()`) is used as the base URL
+// of resolving relative URL-like specifiers in `import()`.
+
+// The following fields should be set by the callers of this script
+// (unless loaded as the worker top-level script):
+// - self.testName (string)
+// - self.baseUrlSanitized (boolean)
+
+// When this script is loaded as the worker top-level script:
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope &&
+ !self.testName) {
+ importScripts("/resources/testharness.js");
+ self.testName = 'worker top-level script';
+ // Worker top-level scripts are always same-origin.
+ self.baseUrlSanitized = false;
+}
+
+{
+ // This could change by the time the test is executed, so we save it now.
+ // As this script is loaded multiple times, savedBaseUrlSanitized is scoped.
+ const savedBaseUrlSanitized = self.baseUrlSanitized;
+
+ promise_test(() => {
+ const promise = import("./import.js?pipe=header(Access-Control-Allow-Origin,*)&label=relative-" + self.testName);
+ if (savedBaseUrlSanitized) {
+ // The base URL is "about:blank" and thus import() here should fail.
+ return promise.then(module => {
+ // This code should be unreached, but assert_equals() is used here
+ // to log `module.A["from"]` in case of unexpected resolution.
+ assert_equals(module.A["from"], "(unreached)",
+ "Relative URL-like specifier resolution should fail");
+ assert_unreached();
+ },
+ () => {});
+ } else {
+ // The base URL is the response URL of this script, i.e.
+ // `.../gamma/base-url.sub.js`.
+ return promise.then(module => {
+ assert_equals(module.A["from"], "gamma/import.js");
+ });
+ }
+ },
+ "Relative URL-like from " + self.testName);
+}
+
+promise_test(() => {
+ return import("http://{{hosts[alt][]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/import.js?pipe=header(Access-Control-Allow-Origin,*)&label=absolute-" + self.testName)
+ .then(module => {
+ assert_equals(module.A["from"], "gamma/import.js");
+ })
+ },
+ "Absolute URL-like from " + self.testName);
+
+done();
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/code-cache.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/code-cache.js
new file mode 100644
index 0000000000..b470bc8ae5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/code-cache.js
@@ -0,0 +1,9 @@
+promise_test(() => {
+ return (new Function('w', 'return import(w)'))("./import.js?Function")
+ .then(module => assert_equals(module.A.from, 'gamma/import.js'));
+}, 'gamma - Function');
+
+promise_test(() => {
+ return eval('import("./import.js?eval")')
+ .then(module => assert_equals(module.A.from, 'gamma/import.js'));
+}, 'gamma - eval');
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/import.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/import.js
new file mode 100644
index 0000000000..435c1369be
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/import.js
@@ -0,0 +1 @@
+export const A = { "from": "gamma/import.js" };
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/inline-event-handler.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/inline-event-handler.html
new file mode 100644
index 0000000000..13cc8b6624
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/inline-event-handler.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id='div' onmousedown='import("./../imports-a.js").then(window.continueTest);'></div>
+<script>
+const div = document.getElementById('div');
+
+promise_test(t => {
+ const promise = new Promise(resolve => window.continueTest = resolve);
+
+ const event = new MouseEvent('mousedown', {'button': 1});
+ div.dispatchEvent(event);
+
+ return promise.then(() => {
+ assert_true(window.evaluated_imports_a);
+ div.parentNode.removeChild(div);
+ });
+}, "dynamic import should work when triggered from inline event handlers");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/basic.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/basic.any.js
new file mode 100644
index 0000000000..82cb3b215d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/basic.any.js
@@ -0,0 +1,32 @@
+// META: global=window,dedicatedworker,sharedworker
+// META: script=ticker.js
+
+promise_test(async t => {
+ const getCount = ticker(1000);
+
+ const importP = import("<invalid>");
+ await promise_rejects_js(t, TypeError, importP, 'import() should reject');
+
+ assert_less_than(getCount(), 1000);
+}, "import() should not drain the microtask queue if it fails during specifier resolution");
+
+promise_test(async t => {
+ // Use Date.now() to ensure that the module is not in the module map
+ const specifier = "./empty-module.js?" + Date.now();
+
+ await import(specifier);
+
+ const getCount = ticker(1000);
+ await import(specifier);
+ assert_less_than(getCount(), 1000);
+}, "import() should not drain the microtask queue when loading an already loaded module");
+
+promise_test(async t => {
+ // Use Date.now() to ensure that the module is not in the module map
+ const specifier = "./empty-module.js?" + Date.now();
+
+ const getCount = ticker(1e7);
+ await import(specifier);
+ assert_equals(getCount(), 1e7);
+}, "import() should drain the microtask queue when fetching a new module");
+
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/css-import-in-worker.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/css-import-in-worker.any.js
new file mode 100644
index 0000000000..bd6f5d092f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/css-import-in-worker.any.js
@@ -0,0 +1,14 @@
+// META: global=dedicatedworker,sharedworker
+// META: script=ticker.js
+
+promise_test(async t => {
+ // Use Date.now() to ensure that the module is not in the module map
+ const specifier = "./empty-module.css?" + Date.now();
+
+ const getCount = ticker(1000);
+
+ const importP = import(specifier, { assert: { type: "css" } });
+ await promise_rejects_js(t, TypeError, importP, 'import() should reject');
+
+ assert_less_than(getCount(), 1000);
+}, "import() should not drain the microtask queue if it fails because of the 'type: css' assertion in a worker");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/empty-module.css b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/empty-module.css
new file mode 100644
index 0000000000..108e7649bd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/empty-module.css
@@ -0,0 +1,4 @@
+/*
+This file is empty, because all it matters is if the
+dynamic import that loads it fails or succedes.
+*/
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/empty-module.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/empty-module.js
new file mode 100644
index 0000000000..108e7649bd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/empty-module.js
@@ -0,0 +1,4 @@
+/*
+This file is empty, because all it matters is if the
+dynamic import that loads it fails or succedes.
+*/
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/serviceworker.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/serviceworker.any.js
new file mode 100644
index 0000000000..4c75cab1b6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/serviceworker.any.js
@@ -0,0 +1,14 @@
+// META: global=serviceworker
+// META: script=ticker.js
+
+promise_test(async t => {
+ // Use Date.now() to ensure that the module is not in the module map
+ const specifier = "./empty-module.js?" + Date.now();
+
+ const getCount = ticker(1000);
+
+ const importP = import(specifier);
+ await promise_rejects_js(t, TypeError, importP, 'import() should reject');
+
+ assert_less_than(getCount(), 1000);
+}, "import() should not drain the microtask queue if it fails because it's used in a ServiceWorker");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/ticker.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/ticker.js
new file mode 100644
index 0000000000..42619b6e70
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/ticker.js
@@ -0,0 +1,13 @@
+globalThis.ticker = function ticker(max) {
+ let i = 0;
+ let stop = false;
+ Promise.resolve().then(function loop() {
+ if (stop || i >= max) return;
+ i++;
+ Promise.resolve().then(loop);
+ });
+ return () => {
+ stop = true;
+ return i;
+ };
+};
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/with-import-assertions.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/with-import-assertions.any.js
new file mode 100644
index 0000000000..f67ba9a1ae
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/with-import-assertions.any.js
@@ -0,0 +1,15 @@
+// META: global=window,dedicatedworker,sharedworker
+// META: script=ticker.js
+
+promise_test(async t => {
+ // Use Date.now() to ensure that the module is not in the module map
+ const specifier = "./empty-module.js?" + Date.now();
+
+ const getCount = ticker(1000);
+
+ const importP = import(specifier, { assert: { type: "<invalid>" } });
+ await promise_rejects_js(t, TypeError, importP, 'import() should reject');
+
+ assert_less_than(getCount(), 1000);
+}, "import() should not drain the microtask queue if it fails while validating the 'type' assertion");
+
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/worklet-ref.https.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/worklet-ref.https.html
new file mode 100644
index 0000000000..6c598aee39
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/worklet-ref.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<canvas id ="output" width="100" height="100" style="background: blue;"></canvas>
+<script>
+"use strict";
+const canvas = document.getElementById('output');
+const ctx = canvas.getContext('2d');
+
+ctx.fillStyle = 'green';
+ctx.fillRect(0, 0, 100, 100);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/worklet.https.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/worklet.https.html
new file mode 100644
index 0000000000..5cd59f86dc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/microtasks/worklet.https.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="help" href="https://html.spec.whatwg.org/#hostimportmoduledynamically(referencingscriptormodule,-specifier,-promisecapability)">
+<link rel="match" href="worklet-ref.https.html">
+<style>
+#output {
+ width: 100px;
+ height: 100px;
+ background-image: paint(rects);
+ background-color: blue;
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+<body>
+<div id="output"></div>
+
+<script id="code" type="text/worklet">
+registerPaint('rects', class {
+ async paint(ctx, geom) {
+ ctx.fillStyle = 'red';
+
+ const getCount = ticker(1000);
+
+ try {
+ // Use Date.now() to ensure that the module is not in the module map
+ await import("./empty-module.js?" + Date.now());
+ } catch (e) {
+ if (getCount() < 1000) {
+ ctx.fillStyle = 'green';
+ }
+ }
+ ctx.fillRect(0, 0, geom.width, geom.height);
+ }
+});
+</script>
+
+<script>
+"use strict";
+CSS.paintWorklet.addModule("./ticker.js").then(() =>
+ importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent)
+);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/no-active-script-classic-manual.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/no-active-script-classic-manual.html
new file mode 100644
index 0000000000..b01f595c03
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/no-active-script-classic-manual.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Dynamic import when there is no active script</title>
+<link rel="help" href="https://github.com/whatwg/html/pull/4181">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<p>Click these buttons in sequence:</p>
+
+<button id="button1">Click me 1</button>
+
+<button id="button2">Click me 2</button>
+
+<p>The result will be pass/fail per the testharness.js results</p>
+
+<!-- We set the attributes from a separate script to specifically make
+ sure that it's not that script's base URL that gets used, but instead this page's. -->
+<script src="scripts/no-active-script.js"></script>
+
+<script>
+"use strict";
+setup({ explicit_timeout: true });
+
+promise_test(t => {
+ t.add_cleanup(() => {
+ delete window.evaluated_imports_a;
+ });
+
+ const promise = new Promise((resolve, reject) => {
+ window.continueTest1 = resolve;
+ window.errorTest1 = reject;
+ });
+
+ return promise.then(module => {
+ assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+ });
+}, "onclick that directly imports should successfully import, using page's base URL");
+
+promise_test(t => {
+ t.add_cleanup(() => {
+ delete window.evaluated_imports_a;
+ });
+
+ const promise = new Promise((resolve, reject) => {
+ window.continueTest2 = resolve;
+ window.errorTest2 = reject;
+ });
+
+ return promise.then(module => {
+ assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+ });
+}, "onclick that indirectly imports after a task should successfully import, using page's base URL");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/no-active-script-module-manual.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/no-active-script-module-manual.html
new file mode 100644
index 0000000000..359b71d821
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/no-active-script-module-manual.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Dynamic import when there is no active script</title>
+<link rel="help" href="https://github.com/whatwg/html/pull/4181">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<p>Click these buttons in sequence:</p>
+
+<button id="button1">Click me 1</button>
+
+<button id="button2">Click me 2</button>
+
+<p>The result will be pass/fail per the testharness.js results</p>
+
+<!-- We set the attributes from a separate script to specifically make
+ sure that it's not that script's base URL that gets used, but instead this page's. -->
+<script src="scripts/no-active-script.js" type="module"></script>
+
+<script type="module">
+"use strict";
+setup({ explicit_timeout: true });
+
+promise_test(t => {
+ t.add_cleanup(() => {
+ delete window.evaluated_imports_a;
+ });
+
+ const promise = new Promise((resolve, reject) => {
+ window.continueTest1 = resolve;
+ window.errorTest1 = reject;
+ });
+
+ return promise.then(module => {
+ assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+ });
+}, "onclick that directly imports should successfully import, using page's base URL");
+
+promise_test(t => {
+ t.add_cleanup(() => {
+ delete window.evaluated_imports_a;
+ });
+
+ const promise = new Promise((resolve, reject) => {
+ window.continueTest2 = resolve;
+ window.errorTest2 = reject;
+ });
+
+ return promise.then(module => {
+ assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+ });
+}, "onclick that indirectly imports after a task should successfully import, using page's base URL");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external-classic.html
new file mode 100644
index 0000000000..0958cfbeba
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external-classic.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta http-equiv="content-security-policy" content="script-src 'nonce-correct'">
+<script nonce="correct" src="/resources/testharness.js"></script>
+<script nonce="correct" src="/resources/testharnessreport.js"></script>
+<script nonce="correct" src="./propagate-nonce-external.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external-module.html
new file mode 100644
index 0000000000..47c422e59c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external-module.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta http-equiv="content-security-policy" content="script-src 'nonce-correct'">
+<script nonce="correct" src="/resources/testharness.js"></script>
+<script nonce="correct" src="/resources/testharnessreport.js"></script>
+<script type="module" nonce="correct" src="./propagate-nonce-external.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external.js
new file mode 100644
index 0000000000..3b97d2f40e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external.js
@@ -0,0 +1,7 @@
+// This file is loaded both as a module and as a classic script.
+promise_test(t => {
+ return import("../imports-a.js").then(module => {
+ assert_true(window.evaluated_imports_a);
+ assert_equals(module.A["from"], "imports-a.js");
+ });
+}, "Dynamically imported module should eval when imported from script w/ a valid nonce.");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-inline-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-inline-classic.html
new file mode 100644
index 0000000000..754110cb5e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-inline-classic.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta http-equiv="content-security-policy" content="script-src 'nonce-correct'">
+<script nonce="correct" src="/resources/testharness.js"></script>
+<script nonce="correct" src="/resources/testharnessreport.js"></script>
+<script nonce="correct">
+promise_test(t => {
+ return import("./../imports-a.js").then(module => {
+ assert_true(window.evaluated_imports_a);
+ assert_equals(module.A["from"], "imports-a.js");
+ });
+}, "Dynamically imported module should eval when imported from script w/ a valid nonce.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-inline-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-inline-module.html
new file mode 100644
index 0000000000..f3322773a4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-inline-module.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta http-equiv="content-security-policy" content="script-src 'nonce-correct'">
+<script nonce="correct" src="/resources/testharness.js"></script>
+<script nonce="correct" src="/resources/testharnessreport.js"></script>
+<script type="module" nonce="correct">
+promise_test(t => {
+ return import("./../imports-a.js").then(module => {
+ assert_true(window.evaluated_imports_a);
+ assert_equals(module.A["from"], "imports-a.js");
+ });
+}, "Dynamically imported module should eval when imported from script w/ a valid nonce.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/blob-url-worker.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/blob-url-worker.js
new file mode 100644
index 0000000000..07071e05ce
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/blob-url-worker.js
@@ -0,0 +1,11 @@
+self.addEventListener("message", (evt) => {
+ const importModule = import(evt.data);
+ importModule.then(
+ (module) => {
+ self.postMessage({ importSucceeded: true, module: { ...module } });
+ },
+ (error) => {
+ self.postMessage({ importSucceeded: false, errorName: error.name });
+ }
+ );
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/code-cache-nonce-iframe.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/code-cache-nonce-iframe.sub.html
new file mode 100644
index 0000000000..56f915aac1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/code-cache-nonce-iframe.sub.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta http-equiv="content-security-policy"
+ content="script-src 'unsafe-eval' 'nonce-{{GET[nonce]}}'">
+<script src="code-cache-nonce.js" nonce="{{GET[nonce]}}"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/code-cache-nonce.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/code-cache-nonce.js
new file mode 100644
index 0000000000..e9983fb6cd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/code-cache-nonce.js
@@ -0,0 +1,4 @@
+// Note that the function source text is intentionally different from e.g.
+// ../alpha/code-cache.js to avoid caching Functions between different sets
+// of tests.
+parent.promise = (new Function('x', 'return import(x)'))('../../imports-a.js');
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/empty-iframe.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/empty-iframe.html
new file mode 100644
index 0000000000..ad5ab30eda
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/empty-iframe.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <div id="dummy"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/status-changing-script.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/status-changing-script.py
new file mode 100644
index 0000000000..a44d3dd3eb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/status-changing-script.py
@@ -0,0 +1,18 @@
+def main(request, response):
+ headers = [(b"Content-Type", b"text/javascript"),
+ (b"Cache-Control", b"private, no-store")]
+
+ id = request.GET.first(b"id")
+
+ with request.server.stash.lock:
+ status = request.server.stash.take(id)
+ if status is None:
+ status = 200
+
+ new_status = request.GET.first(b"newStatus", None)
+ if new_status is not None:
+ status = int(new_status)
+
+ request.server.stash.put(id, status)
+
+ return status, headers, b""
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/v8-code-cache-iframe.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/v8-code-cache-iframe.sub.html
new file mode 100644
index 0000000000..c3a870b3f1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/v8-code-cache-iframe.sub.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta http-equiv="content-security-policy"
+ content="script-src 'nonce-{{GET[nonce]}}'">
+<!--
+base element to make the base URLs of the Document and the script different
+-->
+<base href="../">
+<script src="resources/v8-code-cache.js?pipe=header(Cache-Control,max-age=1000)"
+ nonce="{{GET[nonce]}}" type="{{GET[type]}}"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/v8-code-cache.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/v8-code-cache.js
new file mode 100644
index 0000000000..1d3e88a51d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/resources/v8-code-cache.js
@@ -0,0 +1,74 @@
+parent.promise = import('../../imports-a.js');
+
+// Padding for triggering V8 Code Cache on Chromium.
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
+// ============================================================================
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/Function.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/Function.js
new file mode 100644
index 0000000000..447e5060b1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/Function.js
@@ -0,0 +1,2 @@
+// import()s in a dynamically created function are resolved relative to the script.
+Function(`import('../../imports-a.js?label=' + window.label).then(window.continueTest, window.errorTest)`)();
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/eval.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/eval.js
new file mode 100644
index 0000000000..100602733a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/eval.js
@@ -0,0 +1,2 @@
+// import()s in eval are resolved relative to the script.
+eval(`import('../../imports-a.js?label=' + window.label).then(window.continueTest, window.errorTest)`);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/inline-event-handlers-UA-code.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/inline-event-handlers-UA-code.js
new file mode 100644
index 0000000000..3202ce47b9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/inline-event-handlers-UA-code.js
@@ -0,0 +1,3 @@
+// import()s in an event handler are resolved relative to the document base.
+window.dummyDiv.setAttribute("onclick", `import('./imports-a.js?label=' + window.label).then(window.continueTest, window.errorTest)`);
+window.dummyDiv.click(); // different from **on**click()
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/no-active-script.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/no-active-script.js
new file mode 100644
index 0000000000..85d8ac29ec
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/no-active-script.js
@@ -0,0 +1,5 @@
+"use strict";
+
+document.querySelector("#button1").setAttribute("onclick", "import('../imports-a.js?label=button1').then(window.continueTest1, window.errorTest1)");
+
+document.querySelector("#button2").setAttribute("onclick", "Promise.resolve(`import('../imports-a.js?label=button2')`).then(eval).then(window.continueTest2, window.errorTest2);");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/reflected-inline-event-handlers.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/reflected-inline-event-handlers.js
new file mode 100644
index 0000000000..923eb7d8b6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/reflected-inline-event-handlers.js
@@ -0,0 +1,3 @@
+// import()s in an event handler are resolved relative to the document base.
+window.dummyDiv.setAttribute("onclick", `import('./imports-a.js?label=' + window.label).then(window.continueTest, window.errorTest)`);
+window.dummyDiv.onclick();
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/setTimeout.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/setTimeout.js
new file mode 100644
index 0000000000..342b342e8e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/setTimeout.js
@@ -0,0 +1,2 @@
+// import()s in a timeout handler are resolved relative to the script.
+setTimeout(`import('../../imports-a.js?label=' + window.label).then(window.continueTest, window.errorTest)`, 0);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-classic.html
new file mode 100644
index 0000000000..855705e585
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-classic.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings uses the script base URL inside a classic script that is loaded from a file</title>
+<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="dummy"></div>
+
+<base href="scripts/foo/">
+<script>
+// Tweak the base URL of the document here to distinguish:
+// - document URL
+// - document base URL = ../
+// - External scripts' base URL = ./scripts/eval.js etc.
+// - This inline script's base URL = ./scripts/foo/
+document.querySelector("base").remove();
+const base = document.createElement("base");
+base.setAttribute("href", "../");
+document.body.appendChild(base);
+
+function load(scriptSrc) {
+ const el = document.createElement("script");
+ el.src = scriptSrc;
+ document.body.appendChild(el);
+}
+
+function createTestPromise() {
+ return new Promise((resolve, reject) => {
+ window.dummyDiv.removeAttribute("onclick");
+ delete window.evaluated_imports_a;
+ delete window.label;
+
+ window.continueTest = resolve;
+ window.errorTest = reject;
+ });
+}
+
+window.dummyDiv = document.querySelector("#dummy");
+
+const evaluators = [
+ "setTimeout",
+ "eval",
+ "Function",
+ "reflected-inline-event-handlers",
+ "inline-event-handlers-UA-code"
+];
+
+for (const label of evaluators) {
+ promise_test(() => {
+ const promise = createTestPromise();
+
+ window.label = label;
+ load(`dynamic-import/scripts/${label}.js`);
+
+ return promise.then(module => {
+ assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+ });
+ }, label + " should successfully import");
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-module.html
new file mode 100644
index 0000000000..d90af9c96a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-module.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings uses the script base URL inside a module script that is loaded from a file</title>
+<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="dummy"></div>
+
+<base href="scripts/foo/">
+<script type="module">
+// Tweak the base URL of the document here to distinguish:
+// - document URL
+// - document base URL = ../
+// - External scripts' base URL = ./scripts/eval.js etc.
+// - This inline script's base URL = ./scripts/foo/
+document.querySelector("base").remove();
+const base = document.createElement("base");
+base.setAttribute("href", "../");
+document.body.appendChild(base);
+
+function load(scriptSrc) {
+ const el = document.createElement("script");
+ el.type = "module";
+ el.src = scriptSrc;
+ document.body.appendChild(el);
+}
+
+function createTestPromise() {
+ return new Promise((resolve, reject) => {
+ window.dummyDiv.removeAttribute("onclick");
+ delete window.evaluated_imports_a;
+ delete window.label;
+
+ window.continueTest = resolve;
+ window.errorTest = reject;
+ });
+}
+
+window.dummyDiv = document.querySelector("#dummy");
+
+const evaluators = [
+ "setTimeout",
+ "eval",
+ "Function",
+ "reflected-inline-event-handlers",
+ "inline-event-handlers-UA-code"
+];
+
+for (const label of evaluators) {
+ promise_test(() => {
+ const promise = createTestPromise();
+
+ window.label = label;
+ load(`dynamic-import/scripts/${label}.js`);
+
+ return promise.then(module => {
+ assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+ });
+ }, label + " should successfully import");
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-classic.html
new file mode 100644
index 0000000000..2fe1f75535
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-classic.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings uses the script base URL (= document base URL) inside an inline classic script</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<div id="dummy"></div>
+
+<base href="scripts/foo/">
+<script>
+// Tweak the base URL of the document here to distinguish:
+// - document URL
+// - document base URL = ../
+// - This inline script's base URL = ./scripts/foo/
+document.querySelector("base").remove();
+const base = document.createElement("base");
+base.setAttribute("href", "../");
+document.body.appendChild(base);
+
+function createTestPromise() {
+ return new Promise((resolve, reject) => {
+ window.continueTest = resolve;
+ window.errorTest = reject;
+ });
+}
+
+const dummyDiv = document.querySelector("#dummy");
+
+function doTest(label, evaluator, path) {
+ promise_test(t => {
+ t.add_cleanup(() => {
+ dummyDiv.removeAttribute("onclick");
+ delete window.evaluated_imports_a;
+ });
+
+ const promise = createTestPromise();
+
+ evaluator(`import('${path}/imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
+
+ return promise.then(module => {
+ assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+ });
+ }, label + " should successfully import");
+}
+
+// Inline script's base URL should be used.
+doTest("setTimeout", setTimeout, "../../..");
+doTest("eval", eval, "../../..");
+doTest("the Function constructor",
+ (x) => {
+ Function(x)();
+ },
+ "../../..");
+
+// Document's base URL should be used, as there are no active scripts.
+doTest("reflected inline event handlers",
+ (x) => {
+ dummyDiv.setAttribute("onclick", x);
+ dummyDiv.onclick();
+ },
+ ".");
+
+doTest("inline event handlers triggered via UA code",
+ (x) => {
+ dummyDiv.setAttribute("onclick", x);
+ dummyDiv.click(); // different from .**on**click()
+ },
+ ".");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-module.html
new file mode 100644
index 0000000000..1691550ecb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-module.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings uses the script base URL (= document base URL) inside an inline module script</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<div id="dummy"></div>
+
+<base href="scripts/foo/">
+<script type="module">
+// Tweak the base URL of the document here to distinguish:
+// - document URL
+// - document base URL = ../
+// - This inline script's base URL = ./scripts/foo/
+document.querySelector("base").remove();
+const base = document.createElement("base");
+base.setAttribute("href", "../");
+document.body.appendChild(base);
+
+function createTestPromise() {
+ return new Promise((resolve, reject) => {
+ window.continueTest = resolve;
+ window.errorTest = reject;
+ });
+}
+
+const dummyDiv = document.querySelector("#dummy");
+
+function doTest(label, evaluator, path) {
+ promise_test(t => {
+ t.add_cleanup(() => {
+ dummyDiv.removeAttribute("onclick");
+ delete window.evaluated_imports_a;
+ });
+
+ const promise = createTestPromise();
+
+ evaluator(`import('${path}/imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
+
+ return promise.then(module => {
+ assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+ });
+ }, label + " should successfully import");
+}
+
+// Inline script's base URL should be used.
+doTest("setTimeout", setTimeout, "../../..");
+doTest("eval", eval, "../../..");
+doTest("the Function constructor",
+ (x) => {
+ Function(x)();
+ },
+ "../../..");
+
+// Document's base URL should be used, as there are no active scripts.
+doTest("reflected inline event handlers",
+ (x) => {
+ dummyDiv.setAttribute("onclick", x);
+ dummyDiv.onclick();
+ },
+ ".");
+
+doTest("inline event handlers triggered via UA code",
+ (x) => {
+ dummyDiv.setAttribute("onclick", x);
+ dummyDiv.click(); // different from .**on**click()
+ },
+ ".");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-classic.html
new file mode 100644
index 0000000000..34ea00abc8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-classic.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings inside a classic script</title>
+<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="dummy"></div>
+
+<script>
+function createTestPromise() {
+ return new Promise((resolve, reject) => {
+ window.continueTest = resolve;
+ window.errorTest = reject;
+ });
+}
+
+const dummyDiv = document.querySelector("#dummy");
+
+const evaluators = {
+ eval,
+ setTimeout,
+ "the Function constructor"(x) {
+ Function(x)();
+ },
+ "reflected inline event handlers"(x) {
+ dummyDiv.setAttribute("onclick", x);
+ dummyDiv.onclick();
+ },
+ "inline event handlers triggered via UA code"(x) {
+ dummyDiv.setAttribute("onclick", x);
+ dummyDiv.click(); // different from .**on**click()
+ }
+};
+
+for (const [label, evaluator] of Object.entries(evaluators)) {
+ promise_test(t => {
+ t.add_cleanup(() => {
+ dummyDiv.removeAttribute("onclick");
+ delete window.evaluated_imports_a;
+ });
+
+ const promise = createTestPromise();
+
+ evaluator(`import('../imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
+
+ return promise.then(module => {
+ assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+ });
+ }, label + " should successfully import");
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-module.html
new file mode 100644
index 0000000000..b85d446d8d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-module.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings inside a module script</title>
+<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="dummy"></div>
+
+<script type="module">
+function createTestPromise() {
+ return new Promise((resolve, reject) => {
+ window.continueTest = resolve;
+ window.errorTest = reject;
+ });
+}
+
+const dummyDiv = document.querySelector("#dummy");
+
+const evaluators = {
+ eval,
+ setTimeout,
+ "the Function constructor"(x) {
+ Function(x)();
+ },
+ "reflected inline event handlers"(x) {
+ dummyDiv.setAttribute("onclick", x);
+ dummyDiv.onclick();
+ },
+ "inline event handlers triggered via UA code"(x) {
+ dummyDiv.setAttribute("onclick", x);
+ dummyDiv.click(); // different from .**on**click()
+ }
+};
+
+for (const [label, evaluator] of Object.entries(evaluators)) {
+ promise_test(t => {
+ t.add_cleanup(() => {
+ dummyDiv.removeAttribute("onclick");
+ delete window.evaluated_imports_a;
+ });
+
+ const promise = createTestPromise();
+
+ evaluator(`import('../imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
+
+ return promise.then(module => {
+ assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+ });
+ }, label + " should successfully import");
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-classic.html
new file mode 100644
index 0000000000..b582eba8b0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-classic.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings uses the appropriate nonce inside a classic script</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<meta http-equiv="content-security-policy" content="script-src 'nonce-correct' 'unsafe-eval' 'unsafe-hashes' 'sha256-cAMzxBL19bKt4KwKGbxy/ZOFIIjH5AmRjlVbsD5pvNw=' 'sha256-3VjoJYNK/9HJMS8rrZHlqSZgUssDY+GPyc7AU8lNM3k='">
+
+<script nonce="correct" src="/resources/testharness.js"></script>
+<script nonce="correct" src="/resources/testharnessreport.js"></script>
+
+<div id="dummy"></div>
+
+<script nonce="correct">
+"use strict";
+const dummyDiv = document.querySelector("#dummy");
+
+function createTestPromise(t) {
+ t.add_cleanup(() => {
+ delete window.evaluated_imports_a;
+ delete window.unreached;
+ delete window.continueTest;
+ delete window.errorTest;
+ });
+
+ return new Promise((resolve, reject) => {
+ window.unreached = t.unreached_func("Must not reach this");
+ window.continueTest = resolve;
+ window.errorTest = reject;
+ });
+}
+
+function assertSuccessful(module) {
+ assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+}
+
+promise_test(t => {
+ const promise = createTestPromise(t);
+
+ setTimeout(`import('../imports-a.js?label=setTimeout').then(window.continueTest, window.errorTest)`, 0);
+
+ return promise.then(assertSuccessful);
+}, "setTimeout must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+ const promise = createTestPromise(t);
+
+ eval(`import('../imports-a.js?label=direct eval').then(window.continueTest, window.errorTest)`);
+
+ return promise.then(assertSuccessful);
+}, "direct eval must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+ const promise = createTestPromise(t);
+
+ const evalAlias = eval;
+ evalAlias(`import('../imports-a.js?label=indirect eval').then(window.continueTest, window.errorTest)`);
+
+ return promise.then(assertSuccessful);
+}, "indirect eval must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+ const promise = createTestPromise(t);
+
+ Function(`import('../imports-a.js?label=the Function constructor').then(window.continueTest, window.errorTest)`)();
+
+ return promise.then(assertSuccessful);
+}, "the Function constructor must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+ t.add_cleanup(() => {
+ dummyDiv.removeAttribute("onclick");
+ });
+
+ const promise = createTestPromise(t);
+
+ // This only works because of the 'unsafe-hashes' and the hash in the CSP policy
+ dummyDiv.setAttribute(
+ "onclick",
+ `import('../imports-a.js?label=reflected inline event handlers').then(window.continueTest, window.errorTest)`
+ );
+ dummyDiv.onclick();
+
+ return promise_rejects_js(t, TypeError, promise);
+}, "reflected inline event handlers must not inherit the nonce from the triggering script, thus fail");
+
+promise_test(t => {
+ t.add_cleanup(() => {
+ dummyDiv.removeAttribute("onclick");
+ });
+
+ const promise = createTestPromise(t);
+
+ // This only works because of the 'unsafe-hashes' and the hash in the CSP policy
+ dummyDiv.setAttribute(
+ "onclick",
+ `import('../imports-a.js?label=inline event handlers triggered via UA code').then(window.continueTest, window.errorTest)`
+ );
+ assert_equals(typeof dummyDiv.onclick, "function", "the browser must be able to parse a string containing the import() syntax into a function");
+ dummyDiv.click(); // different from **on**click()
+
+ return promise_rejects_js(t, TypeError, promise);
+}, "inline event handlers triggered via UA code must not inherit the nonce from the triggering script, thus fail");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-module.html
new file mode 100644
index 0000000000..4fa1cc5877
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-module.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings uses the appropriate nonce inside a module script</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<meta http-equiv="content-security-policy" content="script-src 'nonce-correct' 'unsafe-eval' 'unsafe-hashes' 'sha256-cAMzxBL19bKt4KwKGbxy/ZOFIIjH5AmRjlVbsD5pvNw=' 'sha256-3VjoJYNK/9HJMS8rrZHlqSZgUssDY+GPyc7AU8lNM3k='">
+
+<script nonce="correct" src="/resources/testharness.js"></script>
+<script nonce="correct" src="/resources/testharnessreport.js"></script>
+
+<div id="dummy"></div>
+
+<script type="module" nonce="correct">
+const dummyDiv = document.querySelector("#dummy");
+
+function createTestPromise(t) {
+ t.add_cleanup(() => {
+ delete window.evaluated_imports_a;
+ delete window.unreached;
+ delete window.continueTest;
+ delete window.errorTest;
+ });
+
+ return new Promise((resolve, reject) => {
+ window.unreached = t.unreached_func("Must not reach this");
+ window.continueTest = resolve;
+ window.errorTest = reject;
+ });
+}
+
+function assertSuccessful(module) {
+ assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+}
+
+promise_test(t => {
+ const promise = createTestPromise(t);
+
+ setTimeout(`import('../imports-a.js?label=setTimeout').then(window.continueTest, window.errorTest)`, 0);
+
+ return promise.then(assertSuccessful);
+}, "setTimeout must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+ const promise = createTestPromise(t);
+
+ eval(`import('../imports-a.js?label=direct eval').then(window.continueTest, window.errorTest)`);
+
+ return promise.then(assertSuccessful);
+}, "direct eval must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+ const promise = createTestPromise(t);
+
+ const evalAlias = eval;
+ evalAlias(`import('../imports-a.js?label=indirect eval').then(window.continueTest, window.errorTest)`);
+
+ return promise.then(assertSuccessful);
+}, "indirect eval must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+ const promise = createTestPromise(t);
+
+ Function(`import('../imports-a.js?label=the Function constructor').then(window.continueTest, window.errorTest)`)();
+
+ return promise.then(assertSuccessful);
+}, "the Function constructor must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+ t.add_cleanup(() => {
+ dummyDiv.removeAttribute("onclick");
+ });
+
+ const promise = createTestPromise(t);
+
+ // This only works because of the 'unsafe-hashes' and the hash in the CSP policy
+ dummyDiv.setAttribute(
+ "onclick",
+ `import('../imports-a.js?label=reflected inline event handlers').then(window.continueTest, window.errorTest)`
+ );
+ dummyDiv.onclick();
+
+ return promise_rejects_js(t, TypeError, promise);
+}, "reflected inline event handlers must not inherit the nonce from the triggering script, thus fail");
+
+promise_test(t => {
+ t.add_cleanup(() => {
+ dummyDiv.removeAttribute("onclick");
+ });
+
+ const promise = createTestPromise(t);
+
+ // This only works because of the 'unsafe-hashes' and the hash in the CSP policy
+ dummyDiv.setAttribute(
+ "onclick",
+ `import('../imports-a.js?label=inline event handlers triggered via UA code').then(window.continueTest, window.errorTest)`
+ );
+ assert_equals(typeof dummyDiv.onclick, 'function', "the browser must be able to parse a string containing the import() syntax into a function");
+ dummyDiv.click(); // different from **on**click()
+
+ return promise_rejects_js(t, TypeError, promise);
+}, "inline event handlers triggered via UA code must not inherit the nonce from the triggering script, thus fail");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-of-promise-result.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-of-promise-result.html
new file mode 100644
index 0000000000..5514049c78
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-of-promise-result.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings inside a classic script</title>
+<link rel="help" href="https://github.com/whatwg/html/pull/3163">
+<link rel="help" href="https://github.com/tc39/ecma262/issues/871#issuecomment-292493142">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<base href="scripts/foo/">
+<script>
+"use strict";
+
+// This test is based on the current specification, but all browser
+// implementations aren't conformant. See
+// https://bugs.chromium.org/p/chromium/issues/detail?id=1245063
+// https://github.com/heycam/webidl/pull/902
+
+// Tweak the base URL of the document here to distinguish:
+// - document URL
+// - document base URL = ../
+// - This inline script's base URL = ./scripts/foo/
+document.querySelector("base").remove();
+const base = document.createElement("base");
+base.setAttribute("href", "../");
+document.body.appendChild(base);
+
+self.ran = false;
+
+// The active script for the dynamic import is this inline script
+// (see https://html.spec.whatwg.org/C/#hostmakejobcallback).
+promise_test(t => {
+ t.add_cleanup(() => {
+ self.ran = false;
+ })
+
+ return Promise.resolve(`import("../../../imports-a.js?1").then(() => { self.ran = true; })`)
+ .then(eval)
+ .then(() => {
+ assert_true(self.ran);
+ });
+}, "Evaled the script via eval, successful import");
+
+promise_test(t => {
+ t.add_cleanup(() => {
+ self.ran = false;
+ })
+
+ return Promise.resolve(`import("bad-specifier?1").catch(() => { self.ran = true; })`)
+ .then(eval)
+ .then(() => {
+ assert_true(self.ran);
+ });
+}, "Evaled the script via eval, failed import");
+
+promise_test(t => {
+ t.add_cleanup(() => {
+ self.ran = false;
+ })
+
+ return Promise.resolve(`return import("../../../imports-a.js?2").then(() => { self.ran = true; })`)
+ .then(Function)
+ .then(Function.prototype.call.bind(Function.prototype.call))
+ .then(() => {
+ assert_true(self.ran);
+ });
+}, "Evaled the script via Function, successful import");
+
+promise_test(t => {
+ t.add_cleanup(() => {
+ self.ran = false;
+ })
+
+ return Promise.resolve(`return import("bad-specifier?2").catch(() => { self.ran = true; })`)
+ .then(Function)
+ .then(Function.prototype.call.bind(Function.prototype.call))
+ .then(() => {
+ assert_true(self.ran);
+ });
+}, "Evaled the script via Function, failed import");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-other-document.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-other-document.html
new file mode 100644
index 0000000000..3b1d98f6b1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-other-document.html
@@ -0,0 +1,79 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Check import() works when active script is in another document</title>
+<link rel="author" title="Jon Coppeard" href="mailto:jcoppeard@mozilla.com">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<iframe id="frame" src="resources/empty-iframe.html"></iframe>
+
+<script>
+
+function startTest() {
+ const otherWindow = document.getElementById("frame").contentWindow;
+ const otherDiv = otherWindow.document.getElementById("dummy");
+
+ function createTestPromise() {
+ return new Promise((resolve, reject) => {
+ otherWindow.continueTest = resolve;
+ otherWindow.errorTest = reject;
+ });
+ }
+
+ const evaluators = {
+ eval: otherWindow.eval,
+ setTimeout: otherWindow.setTimeout,
+ "the Function constructor"(x) {
+ otherWindow.Function(x)();
+ },
+ };
+
+ for (const [label, evaluator] of Object.entries(evaluators)) {
+ promise_test(t => {
+ t.add_cleanup(() => {
+ otherDiv.removeAttribute("onclick");
+ delete otherWindow.evaluated_imports_a;
+ });
+
+ const promise = createTestPromise();
+
+ evaluator(`import('../imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
+
+ return promise.then(module => {
+ assert_true(otherWindow.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+ });
+ }, label + " should successfully import");
+ };
+
+ const eventHandlerEvaluators = {
+ "reflected inline event handlers"(x) {
+ otherDiv.setAttribute("onclick", x);
+ otherDiv.onclick();
+ },
+ "inline event handlers triggered by JS"(x) {
+ otherDiv.setAttribute("onclick", x);
+ otherDiv.click(); // different from .**on**click()
+ }
+ };
+
+ for (const [label, evaluator] of Object.entries(eventHandlerEvaluators)) {
+ promise_test(t => {
+ t.add_cleanup(() => {
+ otherDiv.removeAttribute("onclick");
+ delete otherWindow.evaluated_imports_a;
+ });
+
+ const promise = createTestPromise();
+
+ evaluator(`import('../../imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
+
+ return promise.then(module => {
+ assert_true(otherWindow.evaluated_imports_a, "The module must have been evaluated");
+ assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+ });
+ }, label + " should successfully import");
+ };
+}
+</script>
+<body onLoad="startTest()"></body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/v8-code-cache.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/v8-code-cache.html
new file mode 100644
index 0000000000..77de19e74b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/dynamic-import/v8-code-cache.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+// Regression test for https://crbug.com/1244145:
+// This test loads a same script file (`resources/v8-code-cache.js`)
+// multiple times to trigger V8 Code Cache.
+// Host defined options (including base URLs and nonces) are lost when the
+// script is compiled using the cached metadata, and thus causing
+// dynamic import failures due to wrong base URLs and wrong nonces.
+
+function runTest(type, nonce, description) {
+ promise_test(t => {
+ return new Promise((resolve, reject) => {
+ const iframe = document.createElement('iframe');
+ iframe.src = 'resources/v8-code-cache-iframe.sub.html?nonce=' + nonce + '&type=' + type;
+ iframe.onload = () => {
+ // `window.promise` is set by `resources/v8-code-cache.js`.
+ window.promise.then(resolve, reject);
+ };
+ document.body.appendChild(iframe);
+ t.add_cleanup(() => iframe.remove());
+ });
+ }, type + ': ' + description);
+}
+
+// As `promise_test` are serialized, each iframe is created after previous
+// iframes and scripts are completely loaded.
+for (const type of ['text/javascript', 'module']) {
+ // Cache the script in V8 Code Cache by running multiple times.
+ runTest(type, 'abc', 'Run #1');
+ runTest(type, 'abc', 'Run #2');
+ runTest(type, 'abc', 'Run #3');
+ runTest(type, 'abc', 'Run #4');
+ // Changing the nonce seems to disable compilation cache, trigger compilation
+ // using V8 Code Cache and thus expose the bug.
+ runTest(type, 'def', 'Run #5');
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-and-slow-dependency.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-and-slow-dependency.html
new file mode 100644
index 0000000000..f336276f3f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-and-slow-dependency.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Module importing syntax error script and slow script should not crash UA</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="module">
+setup({allow_uncaught_exception: true});
+window.log = [];
+window.loaded = false;
+</script>
+<script type="module">
+import "./syntaxerror.js";
+import "./resources/delayed-modulescript.py";
+
+window.loaded = true;
+</script>
+<script type="module">
+test(() => {
+ assert_false(loaded);
+ }, "module graph with a syntax error should not evaulate but should not crash UA.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-1.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-1.html
new file mode 100644
index 0000000000..2480a60d6d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Handling of different types of errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+ window.addEventListener("onunhandledrejection", unreachable);
+
+ const test_load = async_test(
+ "network error has higher priority than parse error");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 3);
+
+ // A parse error is reported for the first top-level
+ // <script> element for syntaxerror.js.
+ assert_equals(log[0].constructor, SyntaxError);
+ assert_equals(log[1], 1);
+
+ // onerror is called (with no errors reported) due to a network error
+ // for the second top-level <script>.
+ assert_equals(log[2], 2);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./syntaxerror.js"
+ onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./error-type-1.js"
+ onerror="log.push(2)" onload="unreachable()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-1.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-1.js
new file mode 100644
index 0000000000..4882d3f2a5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-1.js
@@ -0,0 +1,2 @@
+import './syntaxerror.js';
+import './404.js';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-2.html
new file mode 100644
index 0000000000..673bf28ca2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-2.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Handling of different types of errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+ window.addEventListener("onunhandledrejection", unreachable);
+
+ const test_load = async_test(
+ "parse error has higher priority than instantiation error");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 4);
+
+ // An instantiation error is reported for the first top-level
+ // <script> element for instantiation-error-1.js.
+ assert_equals(log[0].constructor, SyntaxError);
+ assert_equals(log[1], 1);
+
+ // A parse error is reported for the second top-level <script>.
+ assert_equals(log[2].constructor, SyntaxError);
+ assert_equals(log[3], 2);
+ assert_not_equals(log[0], log[2]);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./instantiation-error-1.js"
+ onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./error-type-2.js"
+ onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-2.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-2.js
new file mode 100644
index 0000000000..6b11397300
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-2.js
@@ -0,0 +1,2 @@
+import './instantiation-error-1.js';
+import './syntaxerror.js';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-3.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-3.html
new file mode 100644
index 0000000000..8a16266f4c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-3.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Handling of different types of errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+ window.addEventListener("onunhandledrejection", unreachable);
+
+ const test_load = async_test(
+ "instantiation error has higher priority than evaluation error");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 5);
+
+ // An evaluation error is reported for the first top-level
+ // <script> element for throw.js.
+ assert_equals(log[0], 'throw');
+ assert_true(log[1].foo);
+ assert_equals(log[2], 1);
+
+ // An instantiation error is reported for the second top-level <script>.
+ assert_equals(log[3].constructor, SyntaxError);
+ assert_equals(log[4], 2);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./throw.js"
+ onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./error-type-3.js"
+ onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-3.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-3.js
new file mode 100644
index 0000000000..542be52846
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/error-type-3.js
@@ -0,0 +1,2 @@
+import './throw.js';
+import './instantiation-error-1.js';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-common.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-common.js
new file mode 100644
index 0000000000..4eb5597a58
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-common.js
@@ -0,0 +1,10 @@
+function errorHandler(ev)
+{
+ document._errorReported.push("error");
+}
+
+document._errorReported = [];
+window.addEventListener("error", errorHandler);
+window.addEventListener("load", function () {
+ document._errorReported = document._errorReported.join(",");
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependent.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependent.html
new file mode 100644
index 0000000000..3a00f62f00
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependent.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html>
+<head>
+ <title>html-script-module-errorHandling-parseError-Dependent</title>
+ <script src="errorhandling-parseerror-common.js"></script>
+</head>
+<body>
+ <script type="module" onerror="errorHandler(event);">
+
+ // No parse errors in the root module, just in the dependent module
+ import test from "./errorhandling-parseerror-dependent.js";
+ document._errorReported = "shouldn't have run dependent module";
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependent.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependent.js
new file mode 100644
index 0000000000..71872c47cf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependent.js
@@ -0,0 +1,2 @@
+// Parse error in a dependent module
+1A
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependentmultiple.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependentmultiple.html
new file mode 100644
index 0000000000..7775aeabb4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependentmultiple.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+<head>
+ <title>html-script-module-errorHandling-parseError-DependentMultiple</title>
+ <script src="errorhandling-parseerror-common.js"></script>
+</head>
+<body>
+ <script type="module" onerror="errorHandler(event)">
+
+ // No parse errors in the root module, just in the dependent module
+ import test from "./errorhandling-parseerror-dependentmultiple.js";
+ document._errorReported = "shouldn't have run dependent module";
+
+ </script>
+ <script type="module" onerror="errorHandler(event)">
+
+ // With the broken dependent module already acquired, try to import it
+ // again from another root. This root should be unwound appropriately.
+ import test from "./errorhandling-parseerror-dependentmultiple.js";
+ document._errorReported = "really shouldn't have run dependent module";
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependentmultiple.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependentmultiple.js
new file mode 100644
index 0000000000..71872c47cf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-dependentmultiple.js
@@ -0,0 +1,2 @@
+// Parse error in a dependent module
+1A
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-root.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-root.html
new file mode 100644
index 0000000000..012f3e9b8c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-parseerror-root.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+<head>
+ <title>html-script-module-errorHandling-parseError-Root</title>
+ <script src="errorhandling-parseerror-common.js"></script>
+</head>
+<body>
+ <script type="module">
+
+ // Parse error in a root module
+ 1A
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-wrongMimetype-import.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-wrongMimetype-import.js
new file mode 100644
index 0000000000..286e1a4229
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-wrongMimetype-import.js
@@ -0,0 +1,8 @@
+import foo from "./errorhandling-wrongMimetype.js?pipe=header(Content-Type,text/plain)";
+
+// We don't expect this code to run, the import above should fail!
+// If we do run though, don't trigger an error that the testharness
+// might misinterpret as the import itself failing to load.
+
+var A = null;
+export { A };
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-wrongMimetype.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-wrongMimetype.js
new file mode 100644
index 0000000000..373a532445
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling-wrongMimetype.js
@@ -0,0 +1,7 @@
+// This is a plain JavaScript file, but since it will only be accessed with
+// a Content-Type of text/plain and nosniff, it will be seen as invalid.
+// The file itself will have no errors/effects, so if it does actually run,
+// no error will be detected, and the test will fail.
+
+var foo = null;
+export foo; \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling.html
new file mode 100644
index 0000000000..cf47465b49
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/errorhandling.html
@@ -0,0 +1,60 @@
+<!doctype html>
+<html>
+<head>
+ <title>html-script-module-errorHandling</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style>
+
+ iframe
+ { display: none; }
+
+ </style>
+</head>
+<body>
+ <h1>html-script-module-errorHandling</h1>
+ <iframe id="iframe_parseError_Root" src="errorhandling-parseerror-root.html"></iframe>
+ <iframe id="iframe_parseError_Dependent" src="errorhandling-parseerror-dependent.html"></iframe>
+ <iframe id="iframe_parseError_DependentMultiple" src="errorhandling-parseerror-dependentmultiple.html"></iframe>
+ <script>
+
+ var tests = [
+ { "id": "iframe_parseError_Root", "expected": "error" },
+ { "id": "iframe_parseError_Dependent", "expected": "error" },
+ { "id": "iframe_parseError_DependentMultiple", "expected": "error,error" },
+ ];
+ tests.forEach(function (testObj) {
+ var testHandle = async_test("IFrame test: '" + testObj.id + "'");
+ var testTarget = document.getElementById(testObj.id);
+ testTarget.addEventListener("load", testHandle.step_func(function () {
+ assert_equals(testTarget.contentDocument._errorReported, testObj.expected, "Unexpected _errorReported value");
+ testHandle.done();
+ }));
+ });
+
+ var test_wrongMimetype_root = async_test("External root module with non-script mimetype");
+ var script_wrongMimetype_root = document.createElement("script");
+ script_wrongMimetype_root.type = "module";
+ script_wrongMimetype_root.src = "errorhandling-wrongMimetype.js?pipe=header(Content-Type,text/plain)";
+ script_wrongMimetype_root.addEventListener("error", test_wrongMimetype_root.step_func(function () {
+ test_wrongMimetype_root.done();
+ }));
+ script_wrongMimetype_root.addEventListener("load", test_wrongMimetype_root.step_func(function () {
+ assert_unreached("This script should not have loaded!");
+ }));
+ document.body.appendChild(script_wrongMimetype_root);
+
+ var test_wrongMimetype_import = async_test("Module with imported non-script mimetype");
+ var script_wrongMimetype_import = document.createElement("script");
+ script_wrongMimetype_import.type = "module";
+ script_wrongMimetype_import.src = "errorhandling-wrongMimetype-import.js";
+ script_wrongMimetype_import.addEventListener("error", test_wrongMimetype_import.step_func(function () {
+ test_wrongMimetype_import.done();
+ }));
+ script_wrongMimetype_import.addEventListener("load", test_wrongMimetype_import.step_func(function () {
+ assert_unreached("This script should not have loaded!");
+ }));
+ document.body.appendChild(script_wrongMimetype_import);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-1.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-1.html
new file mode 100644
index 0000000000..3f2bb35f4e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-1.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Handling of evaluation errors, 1</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+ window.addEventListener("onunhandledrejection", unreachable);
+
+ const test_load = async_test(
+ "Test that exceptions during evaluation lead to error events on " +
+ "window, and that exceptions are remembered.\n");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ const exn = log[1];
+ assert_array_equals(log,
+ ["throw", exn, "load", exn, "load", exn, "load", exn, "load"]);
+ assert_true(exn.foo);
+ }));
+
+ function logLoad() { log.push("load") }
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="throw.js" onerror="unreachable()"
+ onload="logLoad()"></script>
+<script type="module" src="throw.js" onerror="unreachable()"
+ onload="logLoad()"></script>
+<script type="module" src="throw.js" async onerror="unreachable()"
+ onload="logLoad()"></script>
+<script type="module" src="throw.js" nomodule onerror="unreachable()"
+ onload="logLoad()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-2.html
new file mode 100644
index 0000000000..4f2b3c5a74
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-2.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Handling of evaluation errors, 2</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+ window.addEventListener("onunhandledrejection", unreachable);
+
+ const test_load = async_test(
+ "Test that ill-founded cyclic dependencies cause ReferenceError " +
+ "during evaluation, which leads to error events on window, and that " +
+ "exceptions are remembered.\n");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ const exn = log[1];
+ assert_array_equals(log,
+ ["cycle-tdz-access-a", exn, "load", exn, "load", exn, "load"]);
+ assert_equals(exn.constructor, ReferenceError);
+ }));
+
+ function logLoad() { log.push("load"); }
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="cycle-tdz-access.js" async onerror="unreachable()"
+ onload="logLoad()"></script>
+<script type="module" src="cycle-tdz-access.js" nomodule onerror="unreachable()"
+ onload="logLoad()"></script>
+<script type="module" src="cycle-tdz-access.js" onerror="unreachable()"
+ onload="logLoad()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-3.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-3.html
new file mode 100644
index 0000000000..9bfb5df2cf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-3.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Handling of evaluation errors, 3</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+ window.addEventListener("onunhandledrejection", unreachable);
+
+ const test_load = async_test(
+ "Test that exceptions during evaluation lead to error events on " +
+ "window, and that exceptions are remembered.\n");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ const exn = log[1];
+ assert_array_equals(log,
+ ["throw", exn, "load", exn, "load", exn, "load", exn, "load",
+ exn, "load"]);
+ assert_true(exn.foo);
+ }));
+
+ function logLoad() { log.push("load"); }
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./throw.js" onerror="unreachable()"
+ onload="logLoad()"></script>
+<script type="module" src="./throw.js" onerror="unreachable()"
+ onload="logLoad()"></script>
+<script type="module" src="./throw-nested.js" onerror="unreachable()"
+ onload="logLoad()"></script>
+<script type="module" src="./throw.js" onerror="unreachable()"
+ onload="logLoad()"></script>
+<script type="module" src="./throw-nested.js" onerror="unreachable()"
+ onload="logLoad()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-4.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-4.html
new file mode 100644
index 0000000000..0b4b7d1662
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/evaluation-error-4.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Handling of evaluation errors, 4</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+ window.addEventListener("onunhandledrejection", unreachable);
+
+ const test_load = async_test(
+ "Test that exceptions during evaluation lead to error events on " +
+ "window, and that exceptions are remembered.\n");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ const exn = log[1];
+ assert_array_equals(log,
+ ["throw", exn, "load", exn, "load", exn, "load", exn, "load",
+ exn, "load"]);
+ assert_true(exn.foo);
+ }));
+
+ function logLoad() { log.push("load"); }
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./throw-nested.js" onerror="unreachable()"
+ onload="logLoad()"></script>
+<script type="module" src="./throw-nested.js" onerror="unreachable()"
+ onload="logLoad()"></script>
+<script type="module" src="./throw.js" onerror="unreachable()"
+ onload="logLoad()"></script>
+<script type="module" src="./throw-nested.js" onerror="unreachable()"
+ onload="logLoad()"></script>
+<script type="module" src="./throw.js" onerror="unreachable()"
+ onload="logLoad()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicordered2.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicordered2.js
new file mode 100644
index 0000000000..d7115a2ac6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicordered2.js
@@ -0,0 +1,3 @@
+test_dynamicOrdered.step(function() {
+ assert_execCount(1, 2, "External script element (#1) should have fired second");
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicordered3.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicordered3.js
new file mode 100644
index 0000000000..c04e101bb8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicordered3.js
@@ -0,0 +1,3 @@
+test_dynamicOrdered.step(function() {
+ assert_execCount(1, 3, "External script element (#2) should have fired third");
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicordered4.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicordered4.js
new file mode 100644
index 0000000000..958736a3d8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicordered4.js
@@ -0,0 +1,3 @@
+test_dynamicOrdered.step(function() {
+ assert_execCount(1, 4, "External script element (#3) should have fired fourth");
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicunordered1.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicunordered1.js
new file mode 100644
index 0000000000..a54cb7a303
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicunordered1.js
@@ -0,0 +1,3 @@
+test_dynamicUnordered1.step(function() {
+ test_dynamicUnordered1.done();
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicunordered2.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicunordered2.js
new file mode 100644
index 0000000000..df0cd85421
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-dynamicunordered2.js
@@ -0,0 +1,3 @@
+test_dynamicUnordered2.step(function() {
+ test_dynamicUnordered2.done();
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedordered2.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedordered2.js
new file mode 100644
index 0000000000..fca73bd9db
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedordered2.js
@@ -0,0 +1,3 @@
+test_parsedOrdered.step(function() {
+ assert_execCount(0, 2, "External deferred (#1) script element should have fired second");
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedordered4.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedordered4.js
new file mode 100644
index 0000000000..6435333bb9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedordered4.js
@@ -0,0 +1,3 @@
+test_parsedOrdered.step(function() {
+ assert_execCount(0, 4, "External deferred (#2) script element should have fired fourth");
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedunordered1.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedunordered1.js
new file mode 100644
index 0000000000..ea0bb1b486
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedunordered1.js
@@ -0,0 +1,3 @@
+test_parsedUnordered1.step(function() {
+ test_parsedUnordered1.done();
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedunordered2.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedunordered2.js
new file mode 100644
index 0000000000..ce219ee0ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder-parsedunordered2.js
@@ -0,0 +1,3 @@
+test_parsedUnordered2.step(function() {
+ test_parsedUnordered2.done();
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder.html
new file mode 100644
index 0000000000..6a7513dc13
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/execorder.html
@@ -0,0 +1,106 @@
+<!doctype html>
+<html>
+<head>
+ <title>html-script-module-execOrder</title>
+ <meta name=timeout content=long>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+
+ var execCounts = [
+ 0, // test_parsedOrdered
+ 0, // test_dynamicOrdered
+ ];
+ function assert_execCount(set, expected, description)
+ {
+ if (!execCounts[set])
+ {
+ execCounts[set] = 0;
+ }
+ assert_equals(++execCounts[set], expected, description);
+ }
+
+ function create_script(src, opts)
+ {
+ var element = document.createElement("script");
+ element.src = src;
+ element.async = (opts.asyncOrdered ? false : true);
+ element.type = (opts.module ? "module" : "text/javascript");
+ document.body.appendChild(element);
+ }
+
+ </script>
+</head>
+<body>
+ <h1>html-script-module-execOrder</h1>
+ <script>
+
+ /////
+ // Start test_parsedUnordered*
+ /////
+ var test_parsedUnordered1 = async_test("Unordered module script execution (parsed, unordered #1)");
+ var test_parsedUnordered2 = async_test("Unordered module script execution (parsed, unordered #2)");
+ </script>
+ <script type="module" src="execorder-parsedunordered1.js"></script>
+ <script type="module" src="execorder-parsedunordered2.js"></script>
+ <script>
+ /////
+ // End test_parsedUnordered*
+ /////
+
+ /////
+ // Start test_dynamicUnordered*
+ /////
+ var test_dynamicUnordered1 = async_test("Unordered module script execution (dynamic, unordered #1)");
+ var test_dynamicUnordered2 = async_test("Unordered module script execution (dynamic, unordered #2)");
+ create_script("execorder-dynamicunordered1.js", { module: true });
+ create_script("execorder-dynamicunordered2.js", { module: true });
+ /////
+ // End test_dynamicUnordered*
+ /////
+
+ /////
+ // Begin test_parsedOrdered
+ /////
+ var test_parsedOrdered = async_test("Interlaced module/non-module script execution (parsed, async-ordered)");
+ window.addEventListener("load", test_parsedOrdered.step_func(function() {
+ assert_execCount(0, 5, "onload should have fired fifth");
+ test_parsedOrdered.done();
+ }));
+ </script>
+ <script src="execorder-parsedordered2.js" defer></script>
+ <script type="module">
+ test_parsedOrdered.step(function() {
+ assert_execCount(0, 3, "Inline module-typed script element should have fired third");
+ });
+ </script>
+ <script src="execorder-parsedordered4.js" defer></script>
+ <script>
+ test_parsedOrdered.step(function() {
+ assert_execCount(0, 1, "Inline untyped script element should have fired first");
+ });
+ /////
+ // End test_parsedOrdered
+ /////
+
+ /////
+ // Start test_dynamicOrdered
+ /////
+ var test_dynamicOrdered = async_test("Interlaced module/non-module script execution (dynamic, async-ordered)");
+ window.addEventListener("load", test_dynamicOrdered.step_func(function() {
+ assert_execCount(1, 5, "onload should have fired fifth (last)");
+ test_dynamicOrdered.done();
+ }));
+ create_script("execorder-dynamicordered2.js", { asyncOrdered: true, module: false });
+ create_script("execorder-dynamicordered3.js", { asyncOrdered: true, module: true });
+ create_script("execorder-dynamicordered4.js", { asyncOrdered: true, module: false });
+ test_dynamicOrdered.step(function() {
+ assert_execCount(1, 1, "Inline untyped script element should have fired first");
+ });
+ /////
+ // End test_dynamicOrdered
+ /////
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/export-default.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/export-default.js
new file mode 100644
index 0000000000..283830ab28
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/export-default.js
@@ -0,0 +1,2 @@
+log.push("export-default");
+export default "fox";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/export-something-nested.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/export-something-nested.js
new file mode 100644
index 0000000000..ca806eb8b1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/export-something-nested.js
@@ -0,0 +1,2 @@
+log.push("export-something-nested");
+export * from "./export-something.js";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/export-something.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/export-something.js
new file mode 100644
index 0000000000..cf2c3a99fe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/export-something.js
@@ -0,0 +1,3 @@
+log.push("export-something");
+export let foo = 42;
+export function set_foo(x) { foo = x };
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/fetch-error-1.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/fetch-error-1.html
new file mode 100644
index 0000000000..170bb665ff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/fetch-error-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Handling of fetch errors, 1</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ const test_load = async_test(
+ "Test that failure to fetch root leads to error event on script.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(log, ["script"]);
+ }));
+</script>
+<script type="module" src="./no-such-file.js" onerror="log.push('script')"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/fetch-error-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/fetch-error-2.html
new file mode 100644
index 0000000000..9386ce603a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/fetch-error-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Handling of fetch errors, 2</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ const test_load = async_test(
+ "Test that failure to fetch dependency leads to error event on script.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(log, ["script"]);
+ }));
+</script>
+<script type="module" src="./fetch-error-2.js" onerror="log.push('script')"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/fetch-error-2.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/fetch-error-2.js
new file mode 100644
index 0000000000..20c0ea6402
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/fetch-error-2.js
@@ -0,0 +1,2 @@
+import "./no-such-file.js"
+import "./this.js";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-dependent.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-dependent.js
new file mode 100644
index 0000000000..cfaeabc47e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-dependent.js
@@ -0,0 +1 @@
+export let importMetaOnDependentModule = import.meta;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-object.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-object.any.js
new file mode 100644
index 0000000000..494e168102
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-object.any.js
@@ -0,0 +1,20 @@
+// META: global=dedicatedworker-module,sharedworker-module,serviceworker-module
+
+test(() => {
+ assert_equals(typeof import.meta, "object");
+ assert_not_equals(import.meta, null);
+}, "import.meta is an object");
+
+test(() => {
+ import.meta.newProperty = 1;
+ assert_true(Object.isExtensible(import.meta));
+}, "import.meta is extensible");
+
+test(() => {
+ for (const name of Reflect.ownKeys(import.meta)) {
+ const desc = Object.getOwnPropertyDescriptor(import.meta, name);
+ assert_equals(desc.writable, true);
+ assert_equals(desc.enumerable, true);
+ assert_equals(desc.configurable, true);
+ }
+}, "import.meta's properties are writable, configurable, and enumerable");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-importmap.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-importmap.html
new file mode 100644
index 0000000000..214b9bb59c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-importmap.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!--
+ More extensive tests of import maps and import.meta.resolve() will be
+ located in the import maps test suite. This contains some basic tests plus
+ tests some tricky parts of the import.meta.resolve() algorithm around string
+ conversion which are only testable with import maps.
+-->
+
+<script type="importmap">
+{
+ "imports": {
+ "bare": "https://example.com/",
+ "https://example.com/rewrite": "https://example.com/rewritten",
+
+ "1": "https://example.com/PASS-1",
+ "null": "https://example.com/PASS-null",
+ "undefined": "https://example.com/PASS-undefined",
+ "[object Object]": "https://example.com/PASS-object",
+
+ "./start": "./resources/export-1.mjs",
+ "./resources/export-1.mjs": "./resources/export-2.mjs"
+ }
+}
+</script>
+
+<script type="module">
+test(() => {
+ assert_equals(import.meta.resolve("bare"), "https://example.com/");
+}, "import.meta.resolve() given an import mapped bare specifier");
+
+test(() => {
+ assert_equals(import.meta.resolve("https://example.com/rewrite"), "https://example.com/rewritten");
+}, "import.meta.resolve() given an import mapped URL-like specifier");
+
+test(() => {
+ assert_equals(import.meta.resolve(), "https://example.com/PASS-undefined", "no-arg case");
+
+ assert_equals(import.meta.resolve(1), "https://example.com/PASS-1");
+ assert_equals(import.meta.resolve(null), "https://example.com/PASS-null");
+ assert_equals(import.meta.resolve(undefined), "https://example.com/PASS-undefined");
+
+ // Only toString() methods are consulted by ToString, not valueOf() ones.
+ // So this becomes "[object Object]".
+ assert_equals(import.meta.resolve({ valueOf() { return "./x"; } }), "https://example.com/PASS-object");
+}, "Testing the ToString() step of import.meta.resolve() via import maps");
+
+promise_test(async () => {
+ const one = (await import("./start")).default;
+ assert_equals(one, 1);
+
+ const two = (await import(import.meta.resolve("./start"))).default;
+ assert_equals(two, 2);
+}, "import(import.meta.resolve(x)) can be different from import(x)");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-multiple-scripts.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-multiple-scripts.html
new file mode 100644
index 0000000000..d2e0f185e0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-multiple-scripts.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe src="resources/store-import-meta.html"></iframe>
+
+<script type="module">
+import * as otherImportMeta from "./resources/export-import-meta.mjs";
+setup({ explicit_done: true });
+
+window.onload = () => {
+ test(() => {
+ assert_not_equals(frames[0].importMetaURL, import.meta.url,
+ "Precondition check: we've set things up so that the other script has a different import.meta.url");
+
+ const expected = (new URL("resources/x", location.href)).href;
+ assert_equals(frames[0].importMetaResolve("./x"), expected);
+ }, "import.meta.resolve resolves URLs relative to the import.meta.url, not relative to the active script when it is called: another global's inline script");
+
+ test(() => {
+ const otherFrameImportMetaResolve = frames[0].importMetaResolve;
+
+ document.querySelector("iframe").remove();
+
+ const expected = (new URL("resources/x", location.href)).href;
+ assert_equals(otherFrameImportMetaResolve("./x"), expected);
+ }, "import.meta.resolve still works if its global has been destroyed (by detaching the iframe)");
+
+ test(() => {
+ assert_not_equals(otherImportMeta.url, import.meta.url,
+ "Precondition check: we've set things up so that the other script has a different import.meta.url");
+
+ const expected = (new URL("resources/x", location.href)).href;
+ assert_equals(otherImportMeta.resolve("./x"), expected);
+ }, "import.meta.resolve resolves URLs relative to the import.meta.url, not relative to the active script when it is called: another module script");
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve.any.js
new file mode 100644
index 0000000000..5b8a84efaf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve.any.js
@@ -0,0 +1,77 @@
+// META: global=dedicatedworker-module,sharedworker-module,serviceworker-module
+
+import { importMetaOnRootModule, importMetaOnDependentModule }
+ from "./import-meta-root.js";
+
+test(() => {
+ assert_equals(typeof import.meta.resolve, "function");
+ assert_equals(import.meta.resolve.name, "resolve");
+ assert_equals(import.meta.resolve.length, 1);
+ assert_equals(Object.getPrototypeOf(import.meta.resolve), Function.prototype);
+}, "import.meta.resolve is a function with the right properties");
+
+test(() => {
+ assert_false(isConstructor(import.meta.resolve));
+
+ assert_throws_js(TypeError, () => new import.meta.resolve("./x"));
+}, "import.meta.resolve is not a constructor");
+
+test(() => {
+ // See also tests in ./import-meta-resolve-importmap.html.
+
+ assert_equals(import.meta.resolve({ toString() { return "./x"; } }), resolveURL("x"));
+ assert_throws_js(TypeError, () => import.meta.resolve(Symbol("./x")),
+ "symbol");
+ assert_throws_js(TypeError, () => import.meta.resolve(),
+ "no argument (which is treated like \"undefined\")");
+}, "import.meta.resolve ToString()s its argument");
+
+test(() => {
+ assert_equals(import.meta.resolve("./x"), resolveURL("x"),
+ "current module import.meta");
+ assert_equals(importMetaOnRootModule.resolve("./x"), resolveURL("x"),
+ "sibling module import.meta");
+ assert_equals(importMetaOnDependentModule.resolve("./x"), resolveURL("x"),
+ "dependency module import.meta");
+}, "Relative URL-like specifier resolution");
+
+test(() => {
+ assert_equals(import.meta.resolve("https://example.com/"), "https://example.com/",
+ "current module import.meta");
+ assert_equals(importMetaOnRootModule.resolve("https://example.com/"), "https://example.com/",
+ "sibling module import.meta");
+ assert_equals(importMetaOnDependentModule.resolve("https://example.com/"), "https://example.com/",
+ "dependency module import.meta");
+}, "Absolute URL-like specifier resolution");
+
+test(() => {
+ const invalidSpecifiers = [
+ "https://eggplant:b/c",
+ "pumpkins.js",
+ ".tomato",
+ "..zuccini.mjs",
+ ".\\yam.es"
+ ];
+
+ for (const specifier of invalidSpecifiers) {
+ assert_throws_js(TypeError, () => import.meta.resolve(specifier), specifier);
+ }
+}, "Invalid module specifiers");
+
+test(() => {
+ const { resolve } = import.meta;
+ assert_equals(resolve("https://example.com/"), "https://example.com/", "current module import.meta");
+}, "Works fine with no this value");
+
+function resolveURL(urlRelativeToThisTest) {
+ return (new URL(urlRelativeToThisTest, location.href)).href;
+}
+
+function isConstructor(o) {
+ try {
+ new (new Proxy(o, { construct: () => ({}) }));
+ return true;
+ } catch {
+ return false;
+ }
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-root.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-root.js
new file mode 100644
index 0000000000..62ec082a8e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-root.js
@@ -0,0 +1,2 @@
+export let importMetaOnRootModule = import.meta;
+export { importMetaOnDependentModule } from "./import-meta-dependent.js";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.any.js
new file mode 100644
index 0000000000..61d96f35af
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.any.js
@@ -0,0 +1,38 @@
+// META: global=dedicatedworker-module,sharedworker-module,serviceworker-module
+
+import { importMetaOnRootModule, importMetaOnDependentModule }
+ from "./import-meta-root.js";
+
+const base = location.href.slice(0, location.href.lastIndexOf('/'));
+
+test(() => {
+ assert_equals(importMetaOnRootModule.url,
+ base + "/import-meta-root.js");
+}, "import.meta.url in a root external script");
+
+test(() => {
+ assert_equals(importMetaOnDependentModule.url,
+ base + "/import-meta-dependent.js");
+}, "import.meta.url in a dependent external script");
+
+
+import { importMetaOnRootModule as hashedImportMetaOnRootModule1,
+ importMetaOnDependentModule as hashedImportMetaOnDependentModule1 }
+ from "./import-meta-root.js#1";
+
+import { importMetaOnRootModule as hashedImportMetaOnRootModule2,
+ importMetaOnDependentModule as hashedImportMetaOnDependentModule2 }
+ from "./import-meta-root.js#2";
+
+test(() => {
+ assert_equals(hashedImportMetaOnRootModule1.url,
+ base + "/import-meta-root.js#1");
+ assert_equals(hashedImportMetaOnRootModule2.url,
+ base + "/import-meta-root.js#2");
+
+ // Must not be affected
+ assert_equals(hashedImportMetaOnDependentModule1.url,
+ base + "/import-meta-dependent.js");
+ assert_equals(hashedImportMetaOnDependentModule2.url,
+ base + "/import-meta-dependent.js");
+}, "import.meta.url when importing the module with different fragments");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.html
new file mode 100644
index 0000000000..284a15f2b2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="module" src="import-meta-url.any.js"></script>
+
+<script type="module">
+const base = location.href.slice(0, location.href.lastIndexOf('/'));
+
+test(() => {
+ assert_equals(import.meta.url, location.href);
+}, "import.meta.url in a root inline script");
+
+for (const workerType of ['DedicatedWorker', 'SharedWorker']) {
+ promise_test(async t => {
+ const worker_request_url =
+ new URL(`postmessage-worker.js?${workerType}`, location);
+ let w;
+ let port;
+ if (workerType === 'DedicatedWorker') {
+ w = new Worker(worker_request_url.href, {type: 'module'});
+ port = w;
+ } else {
+ w = new SharedWorker(worker_request_url.href, {type: 'module'});
+ port = w.port;
+ w.port.start();
+ }
+ w.onerror = t.unreached_func('Worker error');
+ const url = await new Promise(resolve => {
+ port.onmessage = evt => resolve(evt.data);
+ });
+ assert_equals(url, worker_request_url.href);
+ }, `import.meta.url at top-level module ${workerType}`);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/postmessage-worker.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/postmessage-worker.js
new file mode 100644
index 0000000000..3618137aef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/postmessage-worker.js
@@ -0,0 +1,12 @@
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ postMessage(import.meta.url);
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = function(e) {
+ const port = e.ports[0];
+ port.start();
+ port.postMessage(import.meta.url);
+ };
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-1.mjs b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-1.mjs
new file mode 100644
index 0000000000..aef22247d7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-1.mjs
@@ -0,0 +1 @@
+export default 1;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-2.mjs b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-2.mjs
new file mode 100644
index 0000000000..842e368a0a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-2.mjs
@@ -0,0 +1 @@
+export default 2;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-import-meta.mjs b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-import-meta.mjs
new file mode 100644
index 0000000000..488ca74c93
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-import-meta.mjs
@@ -0,0 +1,2 @@
+export const url = import.meta.url;
+export const resolve = import.meta.resolve;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/store-import-meta.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/store-import-meta.html
new file mode 100644
index 0000000000..c9751da408
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-meta/resources/store-import-meta.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<script type="module">
+window.importMetaURL = import.meta.url;
+window.importMetaResolve = import.meta.resolve;
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-something-namespace.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-something-namespace.js
new file mode 100644
index 0000000000..32d90287d7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-something-namespace.js
@@ -0,0 +1,5 @@
+log.push("import-something-namespace");
+log.push(m.foo);
+m.set_foo(43);
+log.push(m.foo);
+import * as m from "./export-something.js";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-subgraph-404.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-subgraph-404.html
new file mode 100644
index 0000000000..4911a071a0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/import-subgraph-404.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="module">
+import { delayedLoaded } from "./resources/delayed-modulescript.py";
+import { A } from "./404.js";
+window.loadSuccess = delayedLoaded;
+</script>
+<script type="module">
+test(function () {
+ assert_equals(window.loadSuccess, undefined,
+ "module tree w/ its sub graph 404 should fail to load without crashing");
+}, "Import a module graph w/ sub-graph 404.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-a.js
new file mode 100644
index 0000000000..44d1ac96c2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-a.js
@@ -0,0 +1,3 @@
+var A = { "from": "imports-a.js" };
+window.evaluated_imports_a = true;
+export { A };
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-b.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-b.js
new file mode 100644
index 0000000000..ae194e4d8a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-b.js
@@ -0,0 +1 @@
+export var B = { "from": "imports-b.js" };
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-cycle-a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-cycle-a.js
new file mode 100644
index 0000000000..8bd8526f76
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-cycle-a.js
@@ -0,0 +1,2 @@
+import { CycleB } from "./imports-cycle-b.js";
+export var CycleA = "CycleA";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-cycle-b.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-cycle-b.js
new file mode 100644
index 0000000000..218f350c39
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-cycle-b.js
@@ -0,0 +1,2 @@
+import { CycleA } from "./imports-cycle-a.js";
+export var CycleB = "CycleB";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-cycle.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-cycle.js
new file mode 100644
index 0000000000..88a77a4d67
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-cycle.js
@@ -0,0 +1,6 @@
+import { CycleA } from "./imports-cycle-a.js";
+
+test_importCycle.step(function () {
+ assert_equals(CycleA, "CycleA");
+ test_importCycle.done();
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-inc-a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-inc-a.js
new file mode 100644
index 0000000000..8cb2298e8a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-inc-a.js
@@ -0,0 +1,2 @@
+import { A } from "./imports-a.js";
+export { A as INC_A };
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-inc-ab.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-inc-ab.js
new file mode 100644
index 0000000000..b7d84005c5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-inc-ab.js
@@ -0,0 +1,5 @@
+import { A } from "./imports-a.js";
+export { A as INC_AB_A };
+
+import { B } from "./imports-b.js";
+export { B as INC_AB_B };
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-inc-b.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-inc-b.js
new file mode 100644
index 0000000000..243b84fdd0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-inc-b.js
@@ -0,0 +1,2 @@
+import { B } from "./imports-b.js";
+export { B as INC_B }; \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-self-inner.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-self-inner.js
new file mode 100644
index 0000000000..40eca1c8df
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-self-inner.js
@@ -0,0 +1,2 @@
+import { SelfInner as SelfInnerA } from "./imports-self-inner.js";
+export var SelfInner = "SelfInner";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-self.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-self.js
new file mode 100644
index 0000000000..05fa60e2dc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports-self.js
@@ -0,0 +1,6 @@
+import { SelfInner } from "./imports-self-inner.js";
+
+test_importSelf.step(function () {
+ assert_equals(SelfInner, "SelfInner");
+ test_importSelf.done();
+});
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports.html
new file mode 100644
index 0000000000..ca6900744d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/imports.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>html-script-module-imports</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <h1>html-script-module-imports</h1>
+
+ <script type="module">
+
+ import { A } from "./imports-a.js";
+
+ test(function () {
+ assert_equals(A.from, "imports-a.js", "Unexpected A");
+ }, "Import a simple module");
+
+ </script>
+ <script type="module">
+
+ import { B as B_RENAMED } from "./imports-b.js";
+
+ test(function () {
+ assert_equals(B_RENAMED.from, "imports-b.js", "Unexpected B_RENAMED");
+
+ try
+ {
+ B;
+ assert_unreached("Unexpectedly defined B");
+ }
+ catch (ex)
+ {}
+ }, "Import a simple module, renamed");
+
+ </script>
+ <script type="module">
+
+ import { INC_A } from "./imports-inc-a.js";
+ import { INC_B } from "./imports-inc-b.js";
+ import { INC_AB_A, INC_AB_B } from "./imports-inc-ab.js";
+
+ test(function () {
+ assert_equals(INC_A.from, "imports-a.js", "Unexpected INC_A");
+ assert_equals(INC_B.from, "imports-b.js", "Unexpected INC_A");
+ assert_equals(INC_AB_A.from, "imports-a.js", "Unexpected INC_A");
+ assert_equals(INC_AB_B.from, "imports-b.js", "Unexpected INC_A");
+ assert_equals(INC_A, INC_AB_A, "INC_A and INC_AB_A should be the same");
+ assert_equals(INC_B, INC_AB_B, "INC_B and INC_AB_B should be the same");
+ }, "Import the same module multiple times");
+
+ </script>
+
+ <script>
+ var test_importSelf = async_test("Import a module that validly imports itself");
+ </script>
+ <script type="module" src="imports-self.js"></script>
+
+ <script>
+ var test_importCycle = async_test("Import a module with a valid cyclical module dependency");
+ </script>
+ <script type="module" src="imports-cycle.js"></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inactive-context-import.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inactive-context-import.html
new file mode 100644
index 0000000000..ce88c0a152
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inactive-context-import.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Dynamic import triggered from inactive context should not crash</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="container">
+<iframe></iframe>
+</div>
+
+<script>
+test(() => {
+ const iframe = document.querySelector('iframe');
+ const otherWindow = iframe.contentWindow;
+ iframe.remove();
+
+ // Below should not crash
+ otherWindow.eval(`import('foobar');`);
+}, 'dynamic import from inactive context should not crash');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-async-execorder.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-async-execorder.html
new file mode 100644
index 0000000000..db03612e82
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-async-execorder.html
@@ -0,0 +1,29 @@
+<html>
+ <head>
+ <title>Inline async module script execution order</title>
+ <meta name=timeout content=long>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ let loaded = [];
+ let test = async_test("Inline async module script execution order");
+ window.addEventListener("load", test.step_func(function() {
+ assert_array_equals(loaded,
+ ["fast", "fast", "fast", "slow", "slow", "slow"]);
+ test.done();
+ }));
+ </script>
+ <script type="module" async src="resources/slow-module.js?pipe=trickle(d2)&unique=1"></script>
+ <script type="module" async>
+ import "./resources/slow-module.js?pipe=trickle(d2)&unique=2";
+ loaded.push("slow");
+ </script>
+ <script type="module" async src="resources/fast-module.js?unique=1"></script>
+ <script type="module" async>
+ import "./resources/fast-module.js?unique=2";
+ loaded.push("fast");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-async-onload.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-async-onload.html
new file mode 100644
index 0000000000..314abc98dc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-async-onload.html
@@ -0,0 +1,28 @@
+<html>
+ <head>
+ <title>Inline async module script without external deps onload blocking</title>
+ <meta name=timeout content=long>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ let loadFired = false;
+ let moduleRan = false
+ let test = async_test("Inline async module script vs. onload");
+ window.addEventListener("load", test.step_func(function() {
+ loadFired = true;
+ assert_true(moduleRan, "Module should have run before the load event");
+ test.step_timeout(function() {
+ test.done();
+ }, 0);
+ }));
+ </script>
+ <script type="module" async>
+ moduleRan = true;
+ test.step_func(function() {
+ assert_false(loadFired, "onload should not have fired yet");
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-defer-onload.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-defer-onload.html
new file mode 100644
index 0000000000..0a0bc36f8b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-defer-onload.html
@@ -0,0 +1,29 @@
+<html>
+ <head>
+ <title>Inline defer module script without external deps onload blocking</title>
+ <meta name=timeout content=long>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ let loadFired = false;
+ let moduleRan = false
+ let test = async_test("Inline defer module script vs. onload");
+ window.addEventListener("load", test.step_func(function() {
+ loadFired = true;
+ assert_true(moduleRan, "Module should have run before the load event");
+ test.step_timeout(function() {
+ test.done();
+ }, 0);
+ }));
+ </script>
+ <!-- defer should be equivalent to neither defer nor async specified -->
+ <script type="module" defer>
+ moduleRan = true;
+ test.step_func(function() {
+ assert_false(loadFired, "onload should not have fired yet");
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-no-async-onload.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-no-async-onload.html
new file mode 100644
index 0000000000..50f42c6a38
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/inline-no-async-onload.html
@@ -0,0 +1,28 @@
+<html>
+ <head>
+ <title>Inline module script without external deps onload blocking</title>
+ <meta name=timeout content=long>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ let loadFired = false;
+ let moduleRan = false
+ let test = async_test("Inline module script vs. onload");
+ window.addEventListener("load", test.step_func(function() {
+ loadFired = true;
+ assert_true(moduleRan, "Module should have run before the load event");
+ test.step_timeout(function() {
+ test.done();
+ }, 0);
+ }));
+ </script>
+ <script type="module">
+ moduleRan = true;
+ test.step_func(function() {
+ assert_false(loadFired, "onload should not have fired yet");
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html
new file mode 100644
index 0000000000..57b40f5baa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Handling of instantiation errors, 1</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ const test_load = async_test(
+ "Test that missing exports lead to SyntaxError events on window and " +
+ "load events on script");
+
+ window.log = [];
+ window.addEventListener("error", ev => {
+ test_load.step(() => assert_equals(ev.error.constructor, SyntaxError));
+ log.push(ev.message);
+ });
+
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ const msg = log[0];
+ assert_array_equals(log, [msg, 1, msg, 2, msg, 3, msg, 4, msg, 5]);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./missing-export.js"
+ onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./missing-export.js"
+ onerror="unreachable()" onload="log.push(2)"></script>
+<script type="module" src="./missing-export-nested.js"
+ onerror="unreachable()" onload="log.push(3)"></script>
+<script type="module" src="./missing-export.js"
+ onerror="unreachable()" onload="log.push(4)"></script>
+<script type="module" src="./missing-export-nested.js"
+ onerror="unreachable()" onload="log.push(5)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.js
new file mode 100644
index 0000000000..e317b01cc2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.js
@@ -0,0 +1 @@
+import something from "./instantiation-error-1.js";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-2.html
new file mode 100644
index 0000000000..27ba006fc7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-2.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Handling of instantiation errors, 2</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ const test_load = async_test(
+ "Test that missing exports lead to SyntaxError events on window and " +
+ "load events on script");
+
+ window.log = [];
+ window.addEventListener("error", ev => {
+ test_load.step(() => assert_equals(ev.error.constructor, SyntaxError));
+ log.push(ev.message);
+ });
+
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ const msg = log[0];
+ assert_array_equals(log, [msg, 1, msg, 2, msg, 3, msg, 4, msg, 5]);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./missing-export-nested.js"
+ onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./missing-export-nested.js"
+ onerror="unreachable()" onload="log.push(2)"></script>
+<script type="module" src="./missing-export.js"
+ onerror="unreachable()" onload="log.push(3)"></script>
+<script type="module" src="./missing-export-nested.js"
+ onerror="unreachable()" onload="log.push(4)"></script>
+<script type="module" src="./missing-export.js"
+ onerror="unreachable()" onload="log.push(5)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html
new file mode 100644
index 0000000000..32f0a4a243
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Handling of instantiation errors, 3</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ const test_load = async_test(
+ "Test that unresolvable cycles lead to SyntaxError events on window " +
+ "and load events on script");
+
+ window.log = [];
+ window.addEventListener("error", ev => {
+ test_load.step(() => assert_equals(ev.error.constructor, SyntaxError));
+ log.push(ev.message);
+ });
+
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 6, 'Log length');
+ assert_equals(log[1], 1);
+ assert_equals(log[3], 2);
+ assert_equals(log[5], 3);
+ assert_not_equals(log[0], log[2],
+ 'Instantiation error objects for different root scripts');
+ assert_equals(log[0], log[4],
+ 'Instantiation error objects for the same root script');
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./cycle-unresolvable.js"
+ onerror="unreachable()" onload="log.push(1)" nomodule></script>
+<script type="module" src="./cycle-unresolvable-a.js"
+ onerror="unreachable()" onload="log.push(2)"></script>
+<script type="module" src="./cycle-unresolvable.js"
+ onerror="unreachable()" onload="log.push(3)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4.html
new file mode 100644
index 0000000000..8d3f23819a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Handling of instantiation errors, 4</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+ const test_load = async_test(
+ "Test that loading a graph in which a module is already " +
+ "errored results in an error.");
+
+ window.addEventListener("error", ev => {
+ test_load.step(() => assert_equals(ev.error.constructor, SyntaxError));
+ log.push(ev.message);
+ });
+
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 4, 'Log length');
+ assert_equals(log[1], 1);
+ assert_equals(log[3], 2);
+ assert_not_equals(log[0], log[2],
+ 'Instantiation error objects for different root scripts');
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./instantiation-error-4a.js"
+ onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./instantiation-error-4d.js"
+ onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4a.js
new file mode 100644
index 0000000000..6fed27f1c7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4a.js
@@ -0,0 +1,2 @@
+import "./instantiation-error-4b.js";
+log.push("instantiation-error-4a");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4b.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4b.js
new file mode 100644
index 0000000000..4b702cae67
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4b.js
@@ -0,0 +1,3 @@
+import "./instantiation-error-4c.js";
+import "./instantiation-error-4d.js";
+log.push("instantiation-error-4b");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4c.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4c.js
new file mode 100644
index 0000000000..ef699f6ca3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4c.js
@@ -0,0 +1,2 @@
+import {something} from "./instantiation-error-4c.js";
+log.push("instantiation-error-4c");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4d.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4d.js
new file mode 100644
index 0000000000..ac04ccb9b3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-4d.js
@@ -0,0 +1,2 @@
+import {something} from "./instantiation-error-4d.js";
+log.push("instantiation-error-4d");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5.html
new file mode 100644
index 0000000000..20df0bb5c9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Handling of instantiation errors, 5</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ const test_load = async_test(
+ "Test that loading a graph in which a module is already " +
+ "errored results an error.");
+
+ window.log = [];
+ window.addEventListener("error", ev => {
+ test_load.step(() => assert_equals(ev.error.constructor, SyntaxError));
+ log.push(ev.message);
+ });
+
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 4, 'Log length');
+ assert_equals(log[1], 1);
+ assert_equals(log[3], 2);
+ assert_not_equals(log[0], log[2],
+ 'Instantiation error objects for different root scripts');
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./instantiation-error-5a.js"
+ onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./instantiation-error-5d.js"
+ onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5a.js
new file mode 100644
index 0000000000..b2e6b106b2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5a.js
@@ -0,0 +1,2 @@
+import "./instantiation-error-5b.js";
+log.push("instantiation-error-5a");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5b.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5b.js
new file mode 100644
index 0000000000..2d37ae8fff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5b.js
@@ -0,0 +1,3 @@
+import "./instantiation-error-5c.js";
+import "./instantiation-error-5d.js";
+log.push("instantiation-error-5b");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5c.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5c.js
new file mode 100644
index 0000000000..ba221b6fc0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5c.js
@@ -0,0 +1,2 @@
+import {something} from "./instantiation-error-5c.js";
+log.push("instantiation-error-5c");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5d.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5d.js
new file mode 100644
index 0000000000..9775e04f82
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5d.js
@@ -0,0 +1,3 @@
+import "./instantiation-error-5e.js";
+import "./instantiation-error-5a.js";
+log.push("instantiation-error-5d");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5e.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5e.js
new file mode 100644
index 0000000000..8bd3b3c3bf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-5e.js
@@ -0,0 +1,2 @@
+import {something} from "./instantiation-error-5e.js";
+log.push("instantiation-error-5e");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6.html
new file mode 100644
index 0000000000..8d3ce121ee
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Handling of instantiation errors, 6</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that ambiguous star exports lead to an instantiation error " +
+ "and that the correct module is blamed.");
+ // Concretely, instantiation-error-6a.js fails to instantiate because it
+ // requests a name from instantion-error-6b.js that is ambiguous there.
+ // instantiation-error-6b.js itself, however, is fine, and it instantiates
+ // and evaluates successfully.
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ const exn = log[0];
+ assert_array_equals(log, [
+ exn, 1,
+ "instantiation-error-6c",
+ "instantiation-error-6d",
+ "instantiation-error-6b", 2
+ ]);
+ assert_equals(exn.constructor, SyntaxError);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./instantiation-error-6a.js"
+ onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./instantiation-error-6b.js"
+ onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6a.js
new file mode 100644
index 0000000000..4db49c6c46
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6a.js
@@ -0,0 +1,2 @@
+import {foo} from "./instantiation-error-6b.js";
+log.push("instantiation-error-6a");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6b.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6b.js
new file mode 100644
index 0000000000..35272fe550
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6b.js
@@ -0,0 +1,3 @@
+export * from "./instantiation-error-6c.js";
+export * from "./instantiation-error-6d.js";
+log.push("instantiation-error-6b");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6c.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6c.js
new file mode 100644
index 0000000000..69d616b4ba
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6c.js
@@ -0,0 +1,2 @@
+export let foo = "c";
+log.push("instantiation-error-6c");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6d.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6d.js
new file mode 100644
index 0000000000..d1336a57a2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-6d.js
@@ -0,0 +1,2 @@
+export let foo = "d";
+log.push("instantiation-error-6d");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7.html
new file mode 100644
index 0000000000..57f1f87216
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Handling of instantiation errors, 7</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that ambiguous star exports lead to an instantiation error, " +
+ "even when discovered through a star export, and that the correct " +
+ "module is blamed.");
+ // This is a variation of instantiation-error-6.html (see the explanation
+ // there).
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ const exn = log[0];
+ assert_array_equals(log, [
+ exn, 1,
+ "instantiation-error-7d",
+ "instantiation-error-7e",
+ "instantiation-error-7c",
+ "instantiation-error-7f",
+ "instantiation-error-7b", 2
+ ]);
+ assert_equals(exn.constructor, SyntaxError);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./instantiation-error-7a.js"
+ onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./instantiation-error-7b.js"
+ onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7a.js
new file mode 100644
index 0000000000..d27a44865c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7a.js
@@ -0,0 +1,2 @@
+import {foo} from "./instantiation-error-7b.js";
+log.push("instantiation-error-7a");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7b.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7b.js
new file mode 100644
index 0000000000..8c05d3b727
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7b.js
@@ -0,0 +1,3 @@
+export * from "./instantiation-error-7c.js";
+export * from "./instantiation-error-7f.js";
+log.push("instantiation-error-7b");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7c.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7c.js
new file mode 100644
index 0000000000..fff1368034
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7c.js
@@ -0,0 +1,3 @@
+export * from "./instantiation-error-7d.js";
+export * from "./instantiation-error-7e.js";
+log.push("instantiation-error-7c");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7d.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7d.js
new file mode 100644
index 0000000000..fa5e7651f4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7d.js
@@ -0,0 +1,2 @@
+export let foo = "d";
+log.push("instantiation-error-7d");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7e.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7e.js
new file mode 100644
index 0000000000..6547c3fe6a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7e.js
@@ -0,0 +1,2 @@
+export let foo = "e";
+log.push("instantiation-error-7e");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7f.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7f.js
new file mode 100644
index 0000000000..7f9ec5d12e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-7f.js
@@ -0,0 +1,2 @@
+export let foo = "f";
+log.push("instantiation-error-7f");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-8.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-8.html
new file mode 100644
index 0000000000..080b170233
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-8.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Handling of instantiation errors, 8</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!-- The below module tree should fail to instantiate, since it references undefined identifier. -->
+<script type="module" src="instantiation-error-1.js"></script>
+<script>
+setup({allow_uncaught_exception: true});
+
+promise_test(t => {
+ return new Promise(resolve => {
+ window.addEventListener("error", e => {
+ assert_equals(e.error.constructor, SyntaxError);
+ resolve();
+ }, { once: true });
+ }).then(() => new Promise(resolve => {
+ window.addEventListener("error", e => {
+ assert_equals(e.error.constructor, SyntaxError);
+ resolve();
+ }, { once: true });
+ // Load another module tree w/ previously instantiate-failed tree as its sub-tree.
+ document.head.appendChild(Object.assign(
+ document.createElement('script'),
+ { type: 'module', innerText: 'import "./instantiation-error-1.js"'}));
+ }));
+}, "Instantiate attempt on a tree w/ previously instantiate-failed tree as a sub-tree shouldn't crash.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-matches-inner.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-matches-inner.js
new file mode 100644
index 0000000000..369b3e7827
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-matches-inner.js
@@ -0,0 +1 @@
+window.matchesLog.push("integrity-matches-inner");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-matches.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-matches.js
new file mode 100644
index 0000000000..d8c4219e90
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-matches.js
@@ -0,0 +1,2 @@
+import "./integrity-matches-inner.js";
+window.matchesLog.push("integrity-matches");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-mismatches-inner.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-mismatches-inner.js
new file mode 100644
index 0000000000..8182d4de16
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-mismatches-inner.js
@@ -0,0 +1 @@
+window.mismatchesLog.push("integrity-mismatches-inner");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-mismatches.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-mismatches.js
new file mode 100644
index 0000000000..2d47344a5a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity-mismatches.js
@@ -0,0 +1,2 @@
+import "./integrity-mismatches-inner.js";
+window.mismatchesLog.push("integrity-mismatches");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity.html
new file mode 100644
index 0000000000..c79843624f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/integrity.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>&lt;script> integrity=""</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+window.inlineRan = false;
+
+window.matchesLog = [];
+window.matchesEvents = [];
+
+window.mismatchesLog = [];
+window.mismatchesEvents = [];
+</script>
+
+<script type="module" integrity="sha384-garbage">
+window.inlineRan = true;
+</script>
+
+<script type="module" src="integrity-matches.js" integrity="sha384-1/XwTy38IAlmvk1O674Efus1/REqfuX6x0V/B2/GX5R3lNbRjhrIwlWyEDPyOwpN" onload="window.matchesEvents.push('load');" onerror="window.matchesEvents.push('error')"></script>
+<script type="module" src="integrity-mismatches.js" integrity="sha384-doesnotmatch" onload="window.mismatchesEvents.push('load');" onerror="window.mismatchesEvents.push('error')"></script>
+
+<script type="module">
+test(() => {
+ assert_true(window.inlineRan);
+}, "The integrity attribute must have no affect on inline module scripts");
+
+test(() => {
+ assert_array_equals(window.matchesLog, ["integrity-matches-inner", "integrity-matches"], "The module and its dependency must have executed");
+ assert_array_equals(window.matchesEvents, ["load"], "The load event must have fired");
+}, "The integrity attribute must be verified on the top-level of a module and allow it to execute when it matches");
+
+test(() => {
+ assert_array_equals(window.mismatchesLog, [], "The module and its dependency must not have executed");
+ assert_array_equals(window.mismatchesEvents, ["error"], "The error event must have fired");
+}, "The integrity attribute must be verified on the top-level of a module and not allow it to execute when there's a mismatch");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/late-namespace-request.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/late-namespace-request.html
new file mode 100644
index 0000000000..00269efdf9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/late-namespace-request.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Late namespace request</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ const test_load = async_test(
+ "Test the situation where a module is instantiated without the " +
+ "need for a namespace object, but later on a different module " +
+ "requests the namespace.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(log,
+ ["export-something",
+ "import-something-namespace", 42, 43]);
+ }));
+</script>
+<script type="module" src="export-something.js"></script>
+<script type="module" src="import-something-namespace.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/late-star-export-request.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/late-star-export-request.html
new file mode 100644
index 0000000000..d40bb0aca7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/late-star-export-request.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>Late star-export request</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ const test_load = async_test(
+ "Test the situation where a module is instantiated without a use of " +
+ "its star-exports, but later on a different module requests them.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(log, [
+ "export-something", "export-something-nested",
+ "import-something-namespace", 42, 43]);
+ }));
+</script>
+<script type="module" src="export-something-nested.js"></script>
+<script type="module">
+ log.push("import-something-namespace");
+ log.push(foo);
+ set_foo(43);
+ log.push(foo);
+ import {foo, set_foo} from "./export-something-nested.js";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/load-error-events-inline.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/load-error-events-inline.html
new file mode 100644
index 0000000000..58397dd07d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/load-error-events-inline.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<head>
+<title>load/error events for inline module scripts</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/load-error-events-helpers.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+</head>
+<script>
+"use strict";
+
+var test1_load = event_test('src, 200, parser-inserted, defer, no async', false, false);
+var test4_load = event_test('src, 200, parser-inserted, no defer, async', false, false);
+
+var test3_dynamic_load = event_test('src, 200, not parser-inserted, no defer, no async, no non-blocking', false, false);
+var test4_dynamic_load = event_test('src, 200, not parser-inserted, no defer, async', false, false);
+
+var test1_error = event_test('src, 404, parser-inserted, defer, no async', false, true);
+var test4_error = event_test('src, 404, parser-inserted, no defer, async', false, true);
+
+var test3_dynamic_error = event_test('src, 404, not parser-inserted, no defer, no async, no non-blocking', false, true);
+var test4_dynamic_error = event_test('src, 404, not parser-inserted, no defer, async', false, true);
+
+var script3_dynamic_load = document.createElement('script');
+script3_dynamic_load.setAttribute('type', 'module');
+script3_dynamic_load.onload = () => onLoad(test3_dynamic_load);
+script3_dynamic_load.onerror = () => onError(test3_dynamic_load);
+script3_dynamic_load.async = false;
+script3_dynamic_load.appendChild(document.createTextNode('onExecute(test3_dynamic_load);'));
+document.head.appendChild(script3_dynamic_load);
+
+var script3_dynamic_error = document.createElement('script');
+script3_dynamic_error.setAttribute('type', 'module');
+script3_dynamic_error.onload = () => onLoad(test3_dynamic_error);
+script3_dynamic_error.onerror = () => onError(test3_dynamic_error);
+script3_dynamic_error.async = false;
+script3_dynamic_error.appendChild(document.createTextNode('import "./not_found.js";'));
+document.head.appendChild(script3_dynamic_error);
+
+var script4_dynamic_load = document.createElement('script');
+script4_dynamic_load.setAttribute('type', 'module');
+script4_dynamic_load.onload = () => onLoad(test4_dynamic_load);
+script4_dynamic_load.onerror = () => onError(test4_dynamic_load);
+script4_dynamic_load.async = true;
+script4_dynamic_load.appendChild(document.createTextNode('onExecute(test4_dynamic_load);'));
+document.head.appendChild(script4_dynamic_load);
+
+var script4_dynamic_error = document.createElement('script');
+script4_dynamic_error.setAttribute('type', 'module');
+script4_dynamic_error.onload = () => onLoad(test4_dynamic_error);
+script4_dynamic_error.onerror = () => onError(test4_dynamic_error);
+script4_dynamic_error.async = true;
+script4_dynamic_error.appendChild(document.createTextNode('import "./not_found.js";'));
+document.head.appendChild(script4_dynamic_error);
+</script>
+
+<script onload="onLoad(test1_load);" onerror="onError(test1_load);" type="module">"use strict";onExecute(test1_load);</script>
+<script onload="onLoad(test4_load);" onerror="onError(test4_load);" type="module" async>"use strict";onExecute(test4_load);</script>
+<script onload="onLoad(test1_error);" onerror="onError(test1_error);" type="module">"use strict";import "./not_found.js";</script>
+<script onload="onLoad(test4_error);" onerror="onError(test4_error);" type="module" async>"use strict";import "./not_found.js";</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/load-error-events.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/load-error-events.html
new file mode 100644
index 0000000000..d9bf05226c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/load-error-events.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<head>
+<title>load/error events for external module scripts</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/load-error-events-helpers.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+</head>
+<script>
+"use strict";
+
+var test1_load = event_test('src, 200, parser-inserted, defer, no async', true, false);
+var test4_load = event_test('src, 200, parser-inserted, no defer, async', true, false);
+
+var test3_dynamic_load = event_test('src, 200, not parser-inserted, no defer, no async, no non-blocking', true, false);
+var test4_dynamic_load = event_test('src, 200, not parser-inserted, no defer, async', true, false);
+
+var test1_error = event_test('src, 404, parser-inserted, defer, no async', false, true);
+var test4_error = event_test('src, 404, parser-inserted, no defer, async', false, true);
+
+var test3_dynamic_error = event_test('src, 404, not parser-inserted, no defer, no async, no non-blocking', false, true);
+var test4_dynamic_error = event_test('src, 404, not parser-inserted, no defer, async', false, true);
+
+var script3_dynamic_load = document.createElement('script');
+script3_dynamic_load.setAttribute('type', 'module');
+script3_dynamic_load.onload = () => onLoad(test3_dynamic_load);
+script3_dynamic_load.onerror = () => onError(test3_dynamic_load);
+script3_dynamic_load.async = false;
+script3_dynamic_load.src = "../resources/load-error-events.py?test=test3_dynamic_load";
+document.head.appendChild(script3_dynamic_load);
+
+var script3_dynamic_error = document.createElement('script');
+script3_dynamic_error.setAttribute('type', 'module');
+script3_dynamic_error.onload = () => onLoad(test3_dynamic_error);
+script3_dynamic_error.onerror = () => onError(test3_dynamic_error);
+script3_dynamic_error.async = false;
+script3_dynamic_error.src = "../resources/load-error-events.py?test=test3_dynamic_error";
+document.head.appendChild(script3_dynamic_error);
+
+var script4_dynamic_load = document.createElement('script');
+script4_dynamic_load.setAttribute('type', 'module');
+script4_dynamic_load.onload = () => onLoad(test4_dynamic_load);
+script4_dynamic_load.onerror = () => onError(test4_dynamic_load);
+script4_dynamic_load.async = true;
+script4_dynamic_load.src = "../resources/load-error-events.py?test=test4_dynamic_load";
+document.head.appendChild(script4_dynamic_load);
+
+var script4_dynamic_error = document.createElement('script');
+script4_dynamic_error.setAttribute('type', 'module');
+script4_dynamic_error.onload = () => onLoad(test4_dynamic_error);
+script4_dynamic_error.onerror = () => onError(test4_dynamic_error);
+script4_dynamic_error.async = true;
+script4_dynamic_error.src = "../resources/load-error-events.py?test=test4_dynamic_error";
+document.head.appendChild(script4_dynamic_error);
+</script>
+
+<script src="../resources/load-error-events.py?test=test1_load" onload="onLoad(test1_load);" onerror="onError(test1_load);" type="module"></script>
+<script src="../resources/load-error-events.py?test=test4_load" onload="onLoad(test4_load);" onerror="onError(test4_load);" type="module" async></script>
+<script src="../resources/load-error-events.py?test=test1_error" onload="onLoad(test1_error);" onerror="onError(test1_error);" type="module"></script>
+<script src="../resources/load-error-events.py?test=test4_error" onload="onLoad(test4_error);" onerror="onError(test4_error);" type="module" async></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/missing-export-nested.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/missing-export-nested.js
new file mode 100644
index 0000000000..860d2bf341
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/missing-export-nested.js
@@ -0,0 +1,2 @@
+import "./missing-export.js";
+log.push("nested-missing-export");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/missing-export.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/missing-export.js
new file mode 100644
index 0000000000..e6f5746eb7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/missing-export.js
@@ -0,0 +1,2 @@
+import something from "./missing-export.js";
+log.push("missing-export");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-import-referrer.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-import-referrer.html
new file mode 100644
index 0000000000..7348b88075
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-import-referrer.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Referrer for module imports</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>setup({ explicit_done: true })</script>
+</head>
+<body>
+<script type="module">
+
+import { referrerExternalStatic, referrerExternalDynamic } from "./module-import-referrer.js";
+
+// "name" parameter is necessary for bypassing the module map.
+import { referrer as referrerInlineStatic } from "./resources/referrer-checker.py?name=internal-static"
+const { referrer: referrerInlineDynamic } = await import("./resources/referrer-checker.py?name=internal-dynamic");
+
+const scriptURL = new URL("module-import-referrer.js", location.href)
+
+test(t => {
+ assert_equals(
+ referrerInlineStatic, location.href,
+ "Referrer should be the document URL");
+}, "Static imports from inline modules in the HTML document");
+
+test(t => {
+ assert_equals(
+ referrerInlineDynamic, location.href,
+ "Referrer should be the document URL");
+}, "Dynamic imports from inline modules in the HTML document");
+
+test(t => {
+ assert_equals(
+ referrerExternalStatic, scriptURL.href,
+ "Referrer should be the importer module URL");
+}, "Static imports from external modules");
+
+test(t => {
+ assert_equals(
+ referrerExternalDynamic, scriptURL.href,
+ "Referrer should be the document URL");
+}, "Dynamic imports from external modules");
+
+done();
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-import-referrer.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-import-referrer.js
new file mode 100644
index 0000000000..8710f5c621
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-import-referrer.js
@@ -0,0 +1,2 @@
+export { referrer as referrerExternalStatic } from "./resources/referrer-checker.py?name=external-static"
+export const { referrer: referrerExternalDynamic } = await import("./resources/referrer-checker.py?name=external-dynamic");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-in-xhtml.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-in-xhtml.xhtml
new file mode 100644
index 0000000000..1655e61a7c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-in-xhtml.xhtml
@@ -0,0 +1,20 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+window.evaluated_module_script = true;
+</script>
+<script>
+ var test = async_test("module script in XHTML documents should be evaluated.");
+ window.addEventListener("load", () => {
+ test.step(() => { assert_true(window.evaluated_module_script); });
+ test.done();
+ });
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-vs-script-1.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-vs-script-1.html
new file mode 100644
index 0000000000..ae82e1348a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-vs-script-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Once as module script, once as classic script</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ const test_load = async_test(
+ "Test that evaluating something as classic script does not prevent " +
+ "it from being evaluated as module script.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(log, [window, undefined]);
+ }));
+</script>
+<script type="module" src="this.js"></script>
+<script src="this.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-vs-script-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-vs-script-2.html
new file mode 100644
index 0000000000..fb512b4b30
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-vs-script-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Once as classic script, once as module script</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ const test_load = async_test(
+ "Test that evaluating something as classic script does not prevent " +
+ "it from being evaluated as module script.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(log, [window, undefined]);
+ }));
+</script>
+<script src="this.js"></script>
+<script type="module" src="this.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-a.js
new file mode 100644
index 0000000000..a127aeb559
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-a.js
@@ -0,0 +1 @@
+import { b } from "./nested-imports-b.js";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-b.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-b.js
new file mode 100644
index 0000000000..18a5af40cc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-b.js
@@ -0,0 +1,2 @@
+import { c } from "./nested-imports-c.js";
+export const b = "b";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-c.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-c.js
new file mode 100644
index 0000000000..ec44596aea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-c.js
@@ -0,0 +1,2 @@
+import { d } from "./nested-imports-d.js";
+export const c = "c";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-d.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-d.js
new file mode 100644
index 0000000000..cee87849c6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-d.js
@@ -0,0 +1,3 @@
+import { e } from "./nested-imports-e.js";
+import "./resources/delayed-modulescript.py";
+export const d = "d";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-e.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-e.js
new file mode 100644
index 0000000000..ec6f0360a6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-e.js
@@ -0,0 +1,2 @@
+import { f } from "./nested-imports-f.js";
+export const e = "e";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-f.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-f.js
new file mode 100644
index 0000000000..0591e0b316
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-f.js
@@ -0,0 +1,2 @@
+import { g } from "./nested-imports-g.js";
+export const f = "f";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-g.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-g.js
new file mode 100644
index 0000000000..86cbe7d3f8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-g.js
@@ -0,0 +1,2 @@
+import { h } from "./nested-imports-h.js";
+export const g = "g";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-h.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-h.js
new file mode 100644
index 0000000000..a161291259
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports-h.js
@@ -0,0 +1,2 @@
+import { c } from "./nested-imports-c.js";
+export const h = "h";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports.html
new file mode 100644
index 0000000000..23bb595d0e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-imports.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Test imports under more than 3 levels in different modules</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({ allow_uncaught_exception: true });
+
+ window.log = [];
+
+ const test_load = async_test("should load all modules successfully");
+ window.addEventListener(
+ "load",
+ test_load.step_func_done((ev) => {
+ assert_equals(log.length, 2);
+
+ assert_equals(log[0], 1);
+ assert_equals(log[1], 2);
+ })
+ );
+
+ function unreachable() {
+ log.push("unexpected");
+ }
+</script>
+<script type="module" src="./nested-imports-a.js" onerror="unreachable()"
+ onload="log.push(1)"></script>
+<script type="module" src="./nested-imports-e.js" onerror="unreachable()"
+ onload="log.push(2)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-missing-export.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-missing-export.js
new file mode 100644
index 0000000000..3801ae847a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nested-missing-export.js
@@ -0,0 +1,2 @@
+import "./missing-export.js";
+log.push("missing-export-nested");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nomodule-attribute.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nomodule-attribute.html
new file mode 100644
index 0000000000..656c99b292
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/nomodule-attribute.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>The 'nomodule' attribute</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ const test_load = async_test(
+ "Test that 'nomodule' has the desired effect on classic scripts, but " +
+ "no effect on module scripts.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(log, [undefined]);
+ }));
+
+</script>
+<script type="module" src="this.js" nomodule></script>
+<script src="this.js" nomodule></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-no-referrer.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-no-referrer.sub.html
new file mode 100644
index 0000000000..b8866f9511
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-no-referrer.sub.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Referrer with the no-referrer policy</title>
+<meta name="referrer" content="no-referrer">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+
+// "name" parameter is necessary for bypassing the module map.
+
+import { referrer as referrerSame } from "./resources/referrer-checker.py?name=same";
+
+import { referrer as referrerRemote } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/referrer-checker.py?name=remote";
+
+import { referrer as referrerSameSame } from "./resources/import-referrer-checker.sub.js?name=same_same";
+
+import { referrer as referrerSameRemote } from "./resources/import-remote-origin-referrer-checker.sub.js?name=same_remote";
+
+import { referrer as referrerRemoteRemote } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js?name=remote_remote";
+
+import { referrer as referrerRemoteSame } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js?name=remote_same";
+
+test(t => {
+ assert_equals(
+ referrerSame, "",
+ "Referrer should not be sent for the same-origin top-level script.");
+}, "Importing a same-origin top-level script with the no-referrer policy.");
+
+test(t => {
+ assert_equals(
+ referrerRemote, "",
+ "Referrer should not be sent for the remote-origin top-level script.");
+}, "Importing a remote-origin top-level script with the no-referrer policy.");
+
+test(t => {
+ assert_equals(
+ referrerSameSame, "",
+ "Referrer should not be sent for the same-origin descendant script.");
+}, "Importing a same-origin descendant script from a same-origin top-level " +
+ "script with the no-referrer policy.");
+
+test(t => {
+ assert_equals(
+ referrerSameRemote, "",
+ "Referrer should not be sent for the remote-origin descendant script.");
+}, "Importing a remote-origin descendant script from a same-origin top-level " +
+ "script with the no-referrer policy.");
+
+test(t => {
+ assert_equals(
+ referrerRemoteRemote, "",
+ "Referrer should not be sent for the remote-origin descendant script.");
+}, "Importing a remote-origin descendant script from a remote-origin " +
+ "top-level script with the no-referrer policy.");
+
+test(t => {
+ assert_equals(
+ referrerRemoteSame, "",
+ "Referrer should not be sent for the same-origin descendant script.");
+}, "Importing a same-origin descendant script from a remote-origin " +
+ "top-level script with the no-referrer policy.");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-origin-when-cross-origin.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-origin-when-cross-origin.sub.html
new file mode 100644
index 0000000000..fd4c1ea496
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-origin-when-cross-origin.sub.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Referrer with the origin-when-cross-origin policy</title>
+<meta name="referrer" content="origin-when-cross-origin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+
+// "name" parameter is necessary for bypassing the module map.
+
+import { referrer as referrerSame } from "./resources/referrer-checker.py?name=same";
+
+import { referrer as referrerRemote } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/referrer-checker.py?name=remote";
+
+import { referrer as referrerSameSame } from "./resources/import-referrer-checker.sub.js?name=same_same";
+
+import { referrer as referrerSameRemote } from "./resources/import-remote-origin-referrer-checker.sub.js?name=same_remote";
+
+import { referrer as referrerRemoteRemote } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js?name=remote_remote";
+
+import { referrer as referrerRemoteSame } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js?name=remote_same";
+
+const origin = (new URL(location.href)).origin + "/";
+const remoteOrigin = new URL("http://{{domains[www1]}}:{{ports[http][0]}}/").origin + "/";
+
+test(t => {
+ assert_equals(
+ referrerSame, location.href,
+ "Full referrer should be sent for the same-origin top-level script.");
+}, "Importing a same-origin top-level script with the " +
+ "origin-when-cross-origin policy.");
+
+test(t => {
+ assert_equals(
+ referrerRemote, origin,
+ "Referrer should be stripped to the origin when importing " +
+ "remote-origin top-level script.");
+}, "Importing a remote-origin top-level script with the " +
+ "origin-when-cross-origin policy.");
+
+test(t => {
+ const scriptURL =
+ new URL("resources/import-referrer-checker.sub.js", location.href)
+ assert_equals(
+ referrerSameSame, scriptURL + "?name=same_same",
+ "Full referrer should be sent for same-origin descendant script" +
+ "imported by same-origin top-level script.");
+}, "Importing a same-origin descendant script from a same-origin top-level " +
+ "script with the origin-when-cross-origin policy.");
+
+test(t => {
+ assert_equals(
+ referrerSameRemote, origin,
+ "Referrer should be stripped to the origin for the remote-origin " +
+ "descendant script imported from same-origin top-level script.");
+}, "Importing a remote-origin descendant script from a same-origin top-level " +
+ "script with the origin-when-cross-origin policy.");
+
+test(t => {
+ const scriptURL = new URL(
+ "html/semantics/scripting-1/the-script-element/module/resources/" +
+ "import-referrer-checker.sub.js",
+ remoteOrigin);
+ assert_equals(referrerRemoteRemote, scriptURL + "?name=remote_remote",
+ "Full referrer should be sent for the remote-origin descendant script " +
+ "imported from a remote-origin top-level script.");
+}, "Importing a remote-origin descendant script from a remote-origin " +
+ "top-level script with the origin-when-cross-origin policy.");
+
+test(t => {
+ assert_equals(referrerRemoteSame, remoteOrigin,
+ "Referrer should be stripped to the origin for the same-origin " +
+ "descendant script imported by remote-origin top-level script.");
+}, "Importing a same-origin descendant script from a remote-origin " +
+ "top-level script with the origin-when-cross-origin policy.");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-origin.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-origin.sub.html
new file mode 100644
index 0000000000..a554fb4b0c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-origin.sub.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Referrer with the origin policy</title>
+<meta name="referrer" content="origin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+
+// "name" parameter is necessary for bypassing the module map.
+
+import { referrer as referrerSame } from "./resources/referrer-checker.py?name=same";
+
+import { referrer as referrerRemote } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/referrer-checker.py?name=remote";
+
+import { referrer as referrerSameSame } from "./resources/import-referrer-checker.sub.js?name=same_same";
+
+import { referrer as referrerSameRemote } from "./resources/import-remote-origin-referrer-checker.sub.js?name=same_remote";
+
+import { referrer as referrerRemoteRemote } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js?name=remote_remote";
+
+import { referrer as referrerRemoteSame } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js?name=remote_same";
+
+const origin = (new URL(location.href)).origin + "/";
+const remoteOrigin = "http://{{domains[www1]}}:{{ports[http][0]}}/";
+
+test(t => {
+ assert_equals(
+ referrerSame, origin,
+ "Referrer should be sent for the same-origin top-level script.");
+}, "Importing a same-origin top-level script with the origin policy.");
+
+test(t => {
+ assert_equals(
+ referrerRemote, origin,
+ "Referrer should be sent for the remote-origin top-level script.");
+}, "Importing a remote-origin top-level script with the origin policy.");
+
+test(t => {
+ assert_equals(
+ referrerSameSame, origin,
+ "Referrer should be sent for the same-origin descendant script.");
+}, "Importing a same-origin descendant script from a same-origin top-level " +
+ "script with the origin policy.");
+
+test(t => {
+ assert_equals(
+ referrerSameRemote, origin,
+ "Referrer should be sent for the remote-origin descendant script.");
+}, "Importing a remote-origin descendant script from a same-origin top-level " +
+ "script with the origin policy.");
+
+test(t => {
+ assert_equals(
+ referrerRemoteRemote, remoteOrigin,
+ "Referrer should be sent for the remote-origin descendant script.");
+}, "Importing a remote-origin descendant script from a remote-origin " +
+ "top-level script with the origin policy.");
+
+test(t => {
+ assert_equals(
+ referrerRemoteSame, remoteOrigin,
+ "Referrer should be sent for the same-origin descendant script.");
+}, "Importing a same-origin descendant script from a remote-origin " +
+ "top-level script with the origin policy.");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-policy-for-descendants.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-policy-for-descendants.sub.html
new file mode 100644
index 0000000000..4a7005da84
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-policy-for-descendants.sub.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Module script descendants use the referrer policy on their ancestor, if one exists</title>
+<link rel=help href="https://github.com/whatwg/html/pull/9210">
+<meta name="referrer" content="no-referrer">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+
+// "name" parameter is necessary for bypassing the module map.
+//
+// The case where the parent module script is sent with no referrer policy at
+// all is covered in `referrer-no-referrer.sub.html` (and others like it). In
+// that case, the parent module inherits its referencing document's referrer
+// policy for use in descendant imports.
+
+import { referrer as parentWithNoReferrerWhenDowngrade } from
+ "./resources/import-referrer-checker.sub.js?name=same_same&pipe=header(Referrer-Policy,no-referrer-when-downgrade)";
+
+import { referrer as parentWithOrigin} from
+ "./resources/import-referrer-checker.sub.js?name=same_remote&pipe=header(Referrer-Policy,origin)";
+
+import { referrer as parentWithSameOrigin} from
+ "./resources/import-referrer-checker.sub.js?name=same_remote_so&pipe=header(Referrer-Policy,same-origin)";
+
+import { referrer as parentWithOriginWhenCrossOriginRemoteDescendant} from
+ "./resources/import-remote-origin-referrer-checker.sub.js?name=same_remote_owco&pipe=header(Referrer-Policy,origin-when-cross-origin)";
+
+import { referrer as remoteParentWithOriginWhenCrossOriginSameOriginDescendant} from
+ "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js?name=remote_same&pipe=header(Referrer-Policy,origin-when-cross-origin)";
+
+import { referrer as remoteParentWithSameOriginWhenCrossOriginSameOriginDescendant} from
+ "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js?name=remote_same_so&pipe=header(Referrer-Policy,same-origin)";
+
+import { referrer as remoteParentWithOriginWhenCrossOriginRemoteDescendant} from
+ "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js?name=remote_remote&pipe=header(Referrer-Policy,origin-when-cross-origin)";
+
+import { referrer as remoteParentWithSameOriginWhenCrossOriginRemoteDescendant} from
+ "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js?name=remote_remote_so&pipe=header(Referrer-Policy,same-origin)";
+
+import { referrer as parentWithInvalidPolicy } from
+ "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js?name=remote_same_invalid&pipe=header(Referrer-Policy,invalid-policy)";
+
+test(t => {
+ const expected_url =
+ new URL("html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js?name=same_same&pipe=header(Referrer-Policy,no-referrer-when-downgrade)",
+ window.origin);
+ assert_equals(
+ parentWithNoReferrerWhenDowngrade, expected_url.toString(),
+ "Descendant referrer should be the parent's full URL.");
+}, "Parent module delivered with `no-referrer-when-downgrade` policy importing a same-origin descendant script.");
+
+test(t => {
+ assert_equals(
+ parentWithOrigin, window.origin + "/",
+ "Descendant referrer should be the parent's origin.");
+}, "Parent module delivered with `origin` policy importing a same-origin descendant script.");
+
+test(t => {
+ const expected_url =
+ new URL("html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js?name=same_remote_so&pipe=header(Referrer-Policy,same-origin)",
+ window.origin);
+ assert_equals(
+ parentWithSameOrigin, expected_url.toString(),
+ "Descendant referrer should be the parent's full URL.");
+}, "Parent module delivered with `same-origin` policy importing a same-origin descendant script.");
+
+test(t => {
+ assert_equals(
+ parentWithOriginWhenCrossOriginRemoteDescendant, window.origin + "/",
+ "Remote descendant referrer should be the parent's origin.");
+}, "Parent module delivered with `origin-when-cross-origin` policy importing a cross-origin descendant script.");
+
+test(t => {
+ const expected_url =
+ new URL("/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js?name=remote_same&pipe=header(Referrer-Policy,origin-when-cross-origin)",
+ "http://{{domains[www1]}}:{{ports[http][0]}}/");
+ assert_equals(
+ remoteParentWithOriginWhenCrossOriginSameOriginDescendant, expected_url.toString(),
+ "Same-origin descendant referrer should be the parent's full URL.");
+}, "Remote parent module delivered with `origin-when-cross-origin` policy importing a same-origin-to-parent-module descendant script.");
+
+test(t => {
+ const expected_url =
+ new URL("/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js?name=remote_same_so&pipe=header(Referrer-Policy,same-origin)",
+ "http://{{domains[www1]}}:{{ports[http][0]}}/");
+ assert_equals(
+ remoteParentWithSameOriginWhenCrossOriginSameOriginDescendant, expected_url.toString(),
+ "Same-origin descendant referrer should be the parent's full URL.");
+}, "Remote parent module delivered with `same-origin` policy importing a same-origin-to-parent-module descendant script.");
+
+test(t => {
+ assert_equals(
+ remoteParentWithOriginWhenCrossOriginRemoteDescendant, "http://{{domains[www1]}}:{{ports[http][0]}}/",
+ "Remote-origin descendant referrer should be the parent's origin.");
+}, "Remote parent module delivered with `origin-when-cross-origin` policy importing a cross-origin-to-parent-module descendant script.");
+
+test(t => {
+ assert_equals(
+ remoteParentWithSameOriginWhenCrossOriginRemoteDescendant, "");
+}, "Remote parent module delivered with `same-origin` policy importing a cross-origin-to-parent-module descendant script.");
+
+// This tests the following spec line:
+//
+// "If referrerPolicy is not the empty string, set options's referrer policy to
+// referrerPolicy."
+//
+// In other words, invalid referrer policies are ignored, so the referrer policy
+// from the referencing document is inherited as usual, which is `no-referrer`
+// in this case.
+test(t => {
+ assert_equals(
+ parentWithInvalidPolicy, "",
+ "Descendant referrer should be empty.");
+}, "Parent module delivered with invalid policy importing a same-origin descendant script.");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-same-origin.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-same-origin.sub.html
new file mode 100644
index 0000000000..7d6d515a47
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-same-origin.sub.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Referrer with the same-origin policy</title>
+<meta name="referrer" content="same-origin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+
+// "name" parameter is necessary for bypassing the module map.
+
+import { referrer as referrerSame } from "./resources/referrer-checker.py?name=same";
+
+import { referrer as referrerRemote } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/referrer-checker.py?name=remote";
+
+import { referrer as referrerSameSame } from "./resources/import-referrer-checker.sub.js?name=same_same";
+
+import { referrer as referrerSameRemote } from "./resources/import-remote-origin-referrer-checker.sub.js?name=same_remote";
+
+import { referrer as referrerRemoteRemote } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js?name=remote_remote";
+
+import { referrer as referrerRemoteSame } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js?name=remote_same";
+
+const remoteOrigin = "http://{{domains[www1]}}:{{ports[http][0]}}/";
+
+test(t => {
+ assert_equals(
+ referrerSame, location.href,
+ "Referrer should be sent for the same-origin top-level script.");
+}, "Importing a same-origin top-level script with the same-origin policy.");
+
+test(t => {
+ assert_equals(
+ referrerRemote, "",
+ "Referrer should not be sent for the remote-origin top-level script.");
+}, "Importing a remote-origin top-level script with the same-origin policy.");
+
+test(t => {
+ const path =
+ new URL("resources/import-referrer-checker.sub.js", location.href);
+ assert_equals(
+ referrerSameSame, path + `?name=same_same`,
+ "Referrer should be sent for the same-origin descendant script.");
+}, "Importing a same-origin descendant script from a same-origin top-level " +
+ "script with the same-origin policy.");
+
+test(t => {
+ assert_equals(
+ referrerSameRemote, "",
+ "Referrer should not be sent for the remote-origin descendant script.");
+}, "Importing a remote-origin descendant script from a same-origin top-level " +
+ "script with the same-origin policy.");
+
+test(t => {
+ const scriptURL = new URL(
+ "html/semantics/scripting-1/the-script-element/module/resources/" +
+ "import-referrer-checker.sub.js", remoteOrigin);
+ assert_equals(
+ referrerRemoteRemote, scriptURL + "?name=remote_remote",
+ "Referrer should be sent for the remote-origin descendant script " +
+ "when it is imported from a top-level script in the same remote-origin.");
+}, "Importing a remote-origin descendant script from a remote-origin " +
+ "top-level script with the same-origin policy.");
+
+test(t => {
+ assert_equals(
+ referrerRemoteSame, "",
+ "Referrer should not be sent for the same-origin descendant script " +
+ "when it is imported from a top-level remote-origin script.");
+}, "Importing a same-origin descendant script from a remote-origin " +
+ "top-level script with the same-origin policy.");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-strict-policies.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-strict-policies.sub.html
new file mode 100644
index 0000000000..1984d875b3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-strict-policies.sub.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Referrer with the strict-origin referrer policy</title>
+<meta name="referrer" content="strict-origin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+
+// "name" parameter is necessary for bypassing the module map in descendant import.
+
+import { referrer as insecureImport } from "./resources/import-referrer-checker-insecure.sub.js?name=insecure_import";
+import { referrer as secureImport } from "https://{{domains[www]}}:{{ports[https][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker-insecure.sub.js?name=secure_import";
+
+const origin = (new URL(location.href)).origin + "/";
+
+test(t => {
+ assert_equals(
+ insecureImport, origin,
+ "A document with the strict-origin referrer policy served over HTTP, " +
+ "imports an module script over HTTP, that imports a descendant script " +
+ "over HTTP. The request for the descendant script is sent with a " +
+ "`Referer` header with the page's origin");
+
+ assert_equals(
+ secureImport, "",
+ "A document with the strict-origin referrer policy served over HTTP, " +
+ "imports an module script over HTTPS, that imports a descendant script " +
+ "over HTTP. The request for the descendant script is sent with no " +
+ "`Referer` header");
+}, "The strict-* referrer policies compare the trustworthiness of a " +
+ "request's referrer string against its URL");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-unsafe-url.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-unsafe-url.sub.html
new file mode 100644
index 0000000000..443731c1b8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/referrer-unsafe-url.sub.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Referrer with the unsafe-url policy</title>
+<meta name="referrer" content="unsafe-url">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+
+// "name" parameter is necessary for bypassing the module map.
+
+import { referrer as referrerSame } from "./resources/referrer-checker.py?name=same";
+
+import { referrer as referrerRemote } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/referrer-checker.py?name=remote";
+
+import { referrer as referrerSameSame } from "./resources/import-referrer-checker.sub.js?name=same_same";
+
+import { referrer as referrerSameRemote } from "./resources/import-remote-origin-referrer-checker.sub.js?name=same_remote";
+
+import { referrer as referrerRemoteRemote } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js?name=remote_remote";
+
+import { referrer as referrerRemoteSame } from "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js?name=remote_same";
+
+test(t => {
+ assert_equals(
+ referrerSame, location.href,
+ "Referrer should be sent for the same-origin top-level script.");
+}, "Importing a same-origin top-level script with the unsafe-url policy.");
+
+test(t => {
+ assert_equals(
+ referrerRemote, location.href,
+ "Referrer should be sent for the remote-origin top-level script.");
+}, "Importing a remote-origin top-level script with the unsafe-url policy.");
+
+test(t => {
+ const scriptURL =
+ new URL("resources/import-referrer-checker.sub.js", location.href)
+ assert_equals(
+ referrerSameSame, scriptURL + "?name=same_same",
+ "Referrer should be sent for the same-origin descendant script.");
+}, "Importing a same-origin descendant script from a same-origin top-level " +
+ "script with the unsafe-url policy.");
+
+test(t => {
+ const scriptURL =
+ new URL("resources/import-remote-origin-referrer-checker.sub.js",
+ location.href)
+ assert_equals(
+ referrerSameRemote, scriptURL + "?name=same_remote",
+ "Referrer should be sent for the remote-origin descendant script.");
+}, "Importing a remote-origin descendant script from a same-origin top-level " +
+ "script with the unsafe-url policy.");
+
+test(t => {
+ const scriptURL =
+ "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/" +
+ "scripting-1/the-script-element/module/resources/" +
+ "import-referrer-checker.sub.js";
+ assert_equals(
+ referrerRemoteRemote, scriptURL + "?name=remote_remote",
+ "Referrer should be sent for the remote-origin descendant script.");
+}, "Importing a remote-origin descendant script from a remote-origin " +
+ "top-level script with the unsafe-url policy.");
+
+test(t => {
+ const scriptURL =
+ "http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/" +
+ "scripting-1/the-script-element/module/resources/" +
+ "import-same-origin-referrer-checker-from-remote-origin.sub.js";
+ assert_equals(
+ referrerRemoteSame, scriptURL + "?name=remote_same",
+ "Referrer should be sent for the same-origin descendant script.");
+}, "Importing a same-origin descendant script from a remote-origin " +
+ "top-level script with the unsafe-url policy.");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/404-but-js.asis b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/404-but-js.asis
new file mode 100644
index 0000000000..0fe1379e56
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/404-but-js.asis
@@ -0,0 +1,4 @@
+HTTP/1.1 404 Not Found
+Content-Type: text/javascript
+
+window.ran404 = true;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/500-but-js.asis b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/500-but-js.asis
new file mode 100644
index 0000000000..c81202f7ef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/500-but-js.asis
@@ -0,0 +1,4 @@
+HTTP/1.1 500 Not Found
+Content-Type: text/javascript
+
+window.ran500 = true;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py
new file mode 100644
index 0000000000..90551e92c3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py
@@ -0,0 +1,20 @@
+def main(request, response):
+ headers = [
+ (b"Content-Type", b"text/javascript"),
+ (b"Access-Control-Allow-Origin", request.GET.first(b"origin")),
+ (b"Access-Control-Allow-Credentials", b"true")
+ ]
+ identifier = request.GET.first(b"id")
+ cookie_name = request.GET.first(b"cookieName")
+ cookie = request.cookies.first(cookie_name, None)
+ if identifier is None or cookie_name is None:
+ return headers, b""
+
+ if cookie is None:
+ result = b"not found"
+ elif cookie.value == b"1":
+ result = b"found"
+ else:
+ result = b"different value: " + cookie.value
+
+ return headers, b"window." + identifier + b" = '" + result + b"';"
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/credentials-iframe.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/credentials-iframe.sub.html
new file mode 100644
index 0000000000..dbc14dffec
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/credentials-iframe.sub.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<script type="module"
+ src="check-cookie.py?id=sameOriginNone&cookieName=same&origin=http://{{host}}:{{ports[http][0]}}">
+</script>
+<script type="module"
+ src="check-cookie.py?id=sameOriginAnonymous&cookieName=same&origin=http://{{host}}:{{ports[http][0]}}"
+ crossOrigin="anonymous">
+</script>
+<script type="module"
+ src="check-cookie.py?id=sameOriginUseCredentials&cookieName=same&origin=http://{{host}}:{{ports[http][0]}}"
+ crossOrigin="use-credentials">
+</script>
+<script type="module"
+ src="http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py?id=crossOriginNone&cookieName=cross&origin=http://{{host}}:{{ports[http][0]}}">
+</script>
+<script type="module"
+ src="http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py?id=crossOriginAnonymous&cookieName=cross&origin=http://{{host}}:{{ports[http][0]}}"
+ crossOrigin="anonymous">
+</script>
+<script type="module"
+ src="http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py?id=crossOriginUseCredentials&cookieName=cross&origin=http://{{host}}:{{ports[http][0]}}"
+ crossOrigin="use-credentials">
+</script>
+
+<script type="module">
+import "./check-cookie.py?id=sameOriginNoneDescendant&cookieName=same&origin=http://{{host}}:{{ports[http][0]}}";
+</script>
+<script type="module" crossOrigin="anonymous">
+import "./check-cookie.py?id=sameOriginAnonymousDescendant&cookieName=same&origin=http://{{host}}:{{ports[http][0]}}";
+</script>
+<script type="module" crossOrigin="use-credentials">
+import "./check-cookie.py?id=sameOriginUseCredentialsDescendant&cookieName=same&origin=http://{{host}}:{{ports[http][0]}}";
+</script>
+<script type="module">
+import "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py?id=crossOriginNoneDescendant&cookieName=cross&origin=http://{{host}}:{{ports[http][0]}}";
+</script>
+<script type="module" crossOrigin="anonymous">
+import "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py?id=crossOriginAnonymousDescendant&cookieName=cross&origin=http://{{host}}:{{ports[http][0]}}";
+</script>
+<script type="module" crossOrigin="use-credentials">
+import "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py?id=crossOriginUseCredentialsDescendant&cookieName=cross&origin=http://{{host}}:{{ports[http][0]}}";
+</script>
+
+<script type="text/javascript">
+window.addEventListener('load', event => {
+ window.parent.postMessage({}, '*');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/delayed-modulescript.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/delayed-modulescript.py
new file mode 100644
index 0000000000..52dbfba445
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/delayed-modulescript.py
@@ -0,0 +1,7 @@
+import time
+
+def main(request, response):
+ delay = float(request.GET.first(b"ms", 500))
+ time.sleep(delay / 1E3)
+
+ return [(b"Content-type", b"text/javascript")], u"export let delayedLoaded = true;"
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-helper.sub.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-helper.sub.js
new file mode 100644
index 0000000000..7d9b024e75
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-helper.sub.js
@@ -0,0 +1,67 @@
+// runTestsFromIframe() is used in the top-level HTML to set cookies and then
+// start actual tests in iframe.
+function runTestsFromIframe(iframe_url) {
+ const setSameOriginCookiePromise = fetch(
+ '/cookies/resources/set-cookie.py?name=same&path=/html/semantics/scripting-1/the-script-element/module/',
+ {
+ mode: 'no-cors',
+ credentials: 'include',
+ });
+ const setCrossOriginCookiePromise = fetch(
+ 'http://{{domains[www2]}}:{{ports[http][0]}}/cookies/resources/set-cookie.py?name=cross&path=/html/semantics/scripting-1/the-script-element/module/',
+ {
+ mode: 'no-cors',
+ credentials: 'include',
+ });
+ const windowLoadPromise = new Promise(resolve => {
+ window.addEventListener('load', () => {
+ resolve();
+ });
+ });
+
+ const iframe = document.createElement('iframe');
+ Promise.all([setSameOriginCookiePromise,
+ setCrossOriginCookiePromise,
+ windowLoadPromise]).then(() => {
+ iframe.src = iframe_url;
+ document.body.appendChild(iframe);
+ fetch_tests_from_window(iframe.contentWindow);
+ });
+}
+
+// The functions below are used from tests within the iframe.
+
+let testNumber = 0;
+
+// importFunc and setTimeoutFunc is used to make the active script at the time
+// of import() to be the script elements that call `runTest()`,
+// NOT this script defining runTest().
+
+function runTest(importFunc, origin, expected, source) {
+ let url;
+ let description;
+ if (origin === 'same') {
+ url = "./check-cookie.py";
+ description = "Same-origin dynamic import from " + source;
+ } else {
+ url = "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py";
+ description = "Cross-origin dynamic import from " + source;
+ }
+ promise_test(() => {
+ const id = "test" + testNumber;
+ testNumber += 1;
+ return importFunc(url + "?id=" + id + "&cookieName=" + origin + "&origin=" + location.origin)
+ .then(() => {
+ assert_equals(window[id], expected, "cookie");
+ });
+ }, description);
+}
+
+function setTimeoutWrapper(setTimeoutFunc) {
+ return url => {
+ return new Promise(resolve => {
+ window.resolve = resolve;
+ setTimeoutFunc(`import("${url}").then(window.resolve)`);
+ });
+ };
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-iframe.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-iframe.sub.html
new file mode 100644
index 0000000000..88204ef00b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-iframe.sub.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="dynamic-import-credentials-helper.sub.js"></script>
+
+<!--
+The active script at the time of import() is the script elements below, and
+thus the credentials mode of the fetch options of the script elements below
+are used for dynamic import requests.
+-->
+
+<script>
+runTest(url => import(url),
+ "same", "found", "classic script (crossOrigin not specified)");
+runTest(url => import(url),
+ "cross", "not found", "classic script (crossOrigin not specified)");
+</script>
+
+<script crossOrigin="anonymous">
+runTest(url => import(url), "same", "found",
+ "classic script (crossOrigin=anonymous)");
+runTest(url => import(url), "cross", "not found",
+ "classic script (crossOrigin=anonymous)");
+</script>
+
+<script crossOrigin="use-credentials">
+runTest(url => import(url),
+ "same", "found", "classic script (crossOrigin=use-credentials)");
+runTest(url => import(url),
+ "cross", "found", "classic script (crossOrigin=use-credentials)");
+</script>
+
+<script type="module">
+runTest(url => import(url),
+ "same", "found", "module script (crossOrigin not specified)");
+runTest(url => import(url),
+ "cross", "not found", "module script (crossOrigin not specified)");
+</script>
+
+<script type="module" crossOrigin="anonymous">
+runTest(url => import(url), "same", "found",
+ "module script (crossOrigin=anonymous)");
+runTest(url => import(url), "cross", "not found",
+ "module script (crossOrigin=anonymous)");
+</script>
+
+<script type="module" crossOrigin="use-credentials">
+runTest(url => import(url),
+ "same", "found", "module script (crossOrigin=use-credentials)");
+runTest(url => import(url),
+ "cross", "found", "module script (crossOrigin=use-credentials)");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-setTimeout-iframe.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-setTimeout-iframe.sub.html
new file mode 100644
index 0000000000..ffba141527
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-setTimeout-iframe.sub.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="dynamic-import-credentials-helper.sub.js"></script>
+
+<!--
+The active script at the time of import() is the classic script created by
+https://html.spec.whatwg.org/multipage/C/#timer-initialisation-steps
+and the active script at the time of setTimeout() is the script elements below,
+thus the credentials mode of the fetch options of the script elements below
+are used for dynamic import requests.
+
+setTimeout() calls below can't be wrapped (e.g. by step_timeout())
+because wrapping setTimeout() would set active scripts differently.
+-->
+
+<script>
+runTest(setTimeoutWrapper(x => setTimeout(x, 0)),
+ "same", "found", "setTimeout(string) from classic script (crossOrigin not specified)");
+runTest(setTimeoutWrapper(x => setTimeout(x, 0)),
+ "cross", "not found", "setTimeout(string) from classic script (crossOrigin not specified)");
+</script>
+
+<script crossOrigin="anonymous">
+runTest(setTimeoutWrapper(x => setTimeout(x, 0)),
+ "same", "found", "setTimeout(string) from classic script (crossOrigin=anonymous)");
+runTest(setTimeoutWrapper(x => setTimeout(x, 0)),
+ "cross", "not found", "setTimeout(string) from classic script (crossOrigin=anonymous)");
+</script>
+
+<script crossOrigin="use-credentials">
+runTest(setTimeoutWrapper(x => setTimeout(x, 0)),
+ "same", "found", "setTimeout(string) from classic script (crossOrigin=use-credentials)");
+runTest(setTimeoutWrapper(x => setTimeout(x, 0)),
+ "cross", "found", "setTimeout(string) from classic script (crossOrigin=use-credentials)");
+</script>
+
+<script type="module">
+runTest(setTimeoutWrapper(x => setTimeout(x, 0)),
+ "same", "found", "setTimeout(string) from module script (crossOrigin not specified)");
+runTest(setTimeoutWrapper(x => setTimeout(x, 0)),
+ "cross", "not found", "setTimeout(string) from module script (crossOrigin not specified)");
+</script>
+
+<script type="module" crossOrigin="anonymous">
+runTest(setTimeoutWrapper(x => setTimeout(x, 0)),
+ "same", "found", "setTimeout(string) from module script (crossOrigin=anonymous)");
+runTest(setTimeoutWrapper(x => setTimeout(x, 0)),
+ "cross", "not found", "setTimeout(string) from module script (crossOrigin=anonymous)");
+</script>
+
+<script type="module" crossOrigin="use-credentials">
+runTest(setTimeoutWrapper(x => setTimeout(x, 0)),
+ "same", "found", "setTimeout(string) from module script (crossOrigin=use-credentials)");
+runTest(setTimeoutWrapper(x => setTimeout(x, 0)),
+ "cross", "found", "setTimeout(string) from module script (crossOrigin=use-credentials)");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/fast-module.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/fast-module.js
new file mode 100644
index 0000000000..3a76cf71f6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/fast-module.js
@@ -0,0 +1 @@
+loaded.push("fast");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-non-utf8-with-charset-header.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-non-utf8-with-charset-header.js
new file mode 100644
index 0000000000..6fc4ad395c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-non-utf8-with-charset-header.js
@@ -0,0 +1 @@
+import "../../serve-with-content-type.py?fn=external-script-windows1250.js&ct=text/javascript%3Bcharset=windows-1250&dummy=6";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-non-utf8.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-non-utf8.js
new file mode 100644
index 0000000000..3ae805d78d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-non-utf8.js
@@ -0,0 +1 @@
+import "../../serve-with-content-type.py?fn=external-script-windows1250.js&ct=text/javascript&dummy=5";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker-insecure.sub.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker-insecure.sub.js
new file mode 100644
index 0000000000..2d6fd96712
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker-insecure.sub.js
@@ -0,0 +1 @@
+export { referrer } from 'http://{{host}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/referrer-checker.py?name={{GET[name]}}';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker-insecure.sub.js.headers b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker-insecure.sub.js.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker-insecure.sub.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js
new file mode 100644
index 0000000000..2c7dce9dff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js
@@ -0,0 +1,2 @@
+import { referrer as referrerImport } from './referrer-checker.py?name={{GET[name]}}';
+export const referrer = referrerImport;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js.headers b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-referrer-checker.sub.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-remote-origin-referrer-checker.sub.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-remote-origin-referrer-checker.sub.js
new file mode 100644
index 0000000000..45a2520b68
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-remote-origin-referrer-checker.sub.js
@@ -0,0 +1,2 @@
+import { referrer as referrerImport } from 'http://{{domains[www1]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/referrer-checker.py?name={{GET[name]}}';
+export const referrer = referrerImport;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js
new file mode 100644
index 0000000000..5a53bcd4d5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js
@@ -0,0 +1,2 @@
+import { referrer as referrerImport } from 'http://{{host}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/referrer-checker.py?name={{GET[name]}}';
+export const referrer = referrerImport;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js.headers b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-same-origin-referrer-checker-from-remote-origin.sub.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-utf8-with-charset-header.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-utf8-with-charset-header.js
new file mode 100644
index 0000000000..c2ccab7c62
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-utf8-with-charset-header.js
@@ -0,0 +1 @@
+import "../../serve-with-content-type.py?fn=external-script-utf8.js&ct=text/javascript%3Bcharset=windows-1250&dummy=6";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-utf8.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-utf8.js
new file mode 100644
index 0000000000..5708a26e07
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/import-utf8.js
@@ -0,0 +1 @@
+import "../../serve-with-content-type.py?fn=external-script-utf8.js&ct=text/javascript&dummy=5";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/imports-404-but-js.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/imports-404-but-js.js
new file mode 100644
index 0000000000..d62e4f05be
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/imports-404-but-js.js
@@ -0,0 +1 @@
+import "./404-but-js.asis";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/imports-500-but-js.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/imports-500-but-js.js
new file mode 100644
index 0000000000..d62e4f05be
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/imports-500-but-js.js
@@ -0,0 +1 @@
+import "./404-but-js.asis";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/imports-b-cross-origin.sub.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/imports-b-cross-origin.sub.js
new file mode 100644
index 0000000000..6db57b5017
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/imports-b-cross-origin.sub.js
@@ -0,0 +1 @@
+import "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/imports-b.js";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/referrer-checker.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/referrer-checker.py
new file mode 100644
index 0000000000..413f48d381
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/referrer-checker.py
@@ -0,0 +1,6 @@
+def main(request, response):
+ referrer = request.headers.get(b"referer", b"")
+ response_headers = [(b"Content-Type", b"text/javascript"),
+ (b"Access-Control-Allow-Origin", b"*")]
+ return (200, response_headers,
+ b"export const referrer = '" + referrer + b"';")
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/slow-module.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/slow-module.js
new file mode 100644
index 0000000000..4623ef7360
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/resources/slow-module.js
@@ -0,0 +1,3 @@
+// This module is imported with pipe=trickle(d2) to make it load more slowly
+// than fast-module.js
+loaded.push("slow");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/script-for-event.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/script-for-event.html
new file mode 100644
index 0000000000..e3b8e15b41
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/script-for-event.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<title>Module scripts with for and event attributes</title>
+<link rel="author" title="Matheus Kerschbaum" href="mailto:matjk7@gmail.com">
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var expected = [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+];
+var run = expected.map(function() { return false });
+</script>
+<script for="w&#x130;ndow" event="onload" type="module">
+run[0] = true;
+</script>
+<script for="window" event="onload x" type="module">
+run[1] = true;
+</script>
+<script for="window" event="onload(x" type="module">
+run[2] = true;
+</script>
+<script for="window" event="onload(x)" type="module">
+run[3] = true;
+</script>
+<script for="window" event="onclick" type="module">
+run[4] = true;
+</script>
+<script for="" event="onload" type="module">
+run[5] = true;
+</script>
+<script for="window" event="" type="module">
+run[6] = true;
+</script>
+<script for="" event="" type="module">
+run[7] = true;
+</script>
+<script for="&#xa0;window" event="onload" type="module">
+run[8] = true;
+</script>
+<script for="window&#xa0;" event="onload" type="module">
+run[9] = true;
+</script>
+<script for="window" event="&#xa0;onload" type="module">
+run[10] = true;
+</script>
+<script for="window" event="onload&#xa0;" type="module">
+run[11] = true;
+</script>
+<script for=" window " event=" onload " type="module">
+run[12] = true;
+</script>
+<script for=" window " event=" onload() " type="module">
+run[13] = true;
+</script>
+<script for="object" event="handler" type="module">
+run[14] = true;
+</script>
+<script event="handler" type="module">
+run[15] = true;
+</script>
+<script for="object" type="module">
+run[16] = true;
+</script>
+<script type="module">
+test(function() {
+ for (var i = 0; i < run.length; ++i) {
+ test(function() {
+ var script = document.querySelectorAll("script[for], script[event]")[i];
+ assert_equals(run[i], expected[i],
+ "script for=" + format_value(script.getAttribute("for")) +
+ " event=" + format_value(script.getAttribute("event")));
+ }, "Script " + i);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/set-currentScript-on-window.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/set-currentScript-on-window.js
new file mode 100644
index 0000000000..6863075bd9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/set-currentScript-on-window.js
@@ -0,0 +1 @@
+window.currentScriptRecorded = document.currentScript;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/single-evaluation-1.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/single-evaluation-1.html
new file mode 100644
index 0000000000..cc4e2d69b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/single-evaluation-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Single evaluation, 1</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ const test_load = async_test(
+ "Test that a module is evaluated only once, and that 'this' is " +
+ "undefined (because of strict mode).");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(log, [undefined, "this-nested"]);
+ }));
+</script>
+<script type="module" src="this.js"></script>
+<script type="module" src="this.js"></script>
+<script type="module" src="this-nested.js"></script>
+<script type="module" src="this.js"></script>
+<script type="module" src="this-nested.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/single-evaluation-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/single-evaluation-2.html
new file mode 100644
index 0000000000..790e2fa9c6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/single-evaluation-2.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Single evaluation, 2</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.log = [];
+
+ const test_load = async_test(
+ "Test that a module is evaluated only once, and that 'this' is " +
+ "undefined (because of strict mode).");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_array_equals(log, [undefined, "this-nested"]);
+ }));
+</script>
+<script type="module" src="this-nested.js"></script>
+<script type="module" src="this-nested.js"></script>
+<script type="module" src="this.js"></script>
+<script type="module" src="this-nested.js"></script>
+<script type="module" src="this.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/slow-cycle.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/slow-cycle.html
new file mode 100644
index 0000000000..3a42cf9e30
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/slow-cycle.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Cyclic graph with slow imports</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="module">
+import { loaded } from "./slow-module-graph-a.js";
+
+test(() => {
+ assert_true(loaded);
+}, "module graph with cycles load even if part of the graph loads slow");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/slow-module-graph-a.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/slow-module-graph-a.js
new file mode 100644
index 0000000000..48701aa1d0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/slow-module-graph-a.js
@@ -0,0 +1,3 @@
+import "./slow-module-graph-b.js";
+import "./resources/delayed-modulescript.py"
+export let loaded = true;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/slow-module-graph-b.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/slow-module-graph-b.js
new file mode 100644
index 0000000000..53a8f2026e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/slow-module-graph-b.js
@@ -0,0 +1 @@
+import "./slow-module-graph-a.js";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/specifier-error.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/specifier-error.html
new file mode 100644
index 0000000000..d07005caaa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/specifier-error.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>Handling of invalid specifiers</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+
+ window.addEventListener("error", ev => log.push(ev.error));
+
+ const test_load = async_test(
+ "Test that invalid module specifier leads to TypeError on window.");
+ window.addEventListener("load", test_load.step_func_done(ev => {
+ assert_equals(log.length, 1);
+ assert_equals(log[0].constructor, TypeError);
+ }));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./bad-module-specifier.js" onerror="unreachable()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/syntaxerror-nested.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/syntaxerror-nested.js
new file mode 100644
index 0000000000..de1b053c5a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/syntaxerror-nested.js
@@ -0,0 +1,2 @@
+import "./syntaxerror.js";
+log.push("nested-syntaxerror");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/syntaxerror.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/syntaxerror.js
new file mode 100644
index 0000000000..31a9e2cbdf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/syntaxerror.js
@@ -0,0 +1,2 @@
+log.push("syntaxerror");
+%!#$@#$@#$@
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/this-nested.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/this-nested.js
new file mode 100644
index 0000000000..f204812fd1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/this-nested.js
@@ -0,0 +1,2 @@
+import "./this.js";
+log.push("this-nested");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/this.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/this.js
new file mode 100644
index 0000000000..996a439df0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/this.js
@@ -0,0 +1 @@
+log.push(this);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw-error.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw-error.js
new file mode 100644
index 0000000000..9769c84b23
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw-error.js
@@ -0,0 +1,3 @@
+window.before_throwing_error = true;
+throw new Error;
+window.after_throwing_error = true;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw-nested.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw-nested.js
new file mode 100644
index 0000000000..f1801ea366
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw-nested.js
@@ -0,0 +1,2 @@
+import "./throw.js";
+log.push("throw-nested");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw.js
new file mode 100644
index 0000000000..cef7918216
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw.js
@@ -0,0 +1,2 @@
+log.push("throw");
+throw {foo: true}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw2.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw2.js
new file mode 100644
index 0000000000..2931eec500
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/throw2.js
@@ -0,0 +1,2 @@
+log.push("throw2");
+throw {bar: true}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked.any.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked.any.js
new file mode 100644
index 0000000000..820f995aaf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked.any.js
@@ -0,0 +1,11 @@
+promise_test(async t => {
+ const { checkMicrotask } = await import("./sibling-imports-not-blocked__microtask__parent.js");
+
+ assert_equals(checkMicrotask, "PASS");
+}, "Async modules only scheduling microtasks don't block execution of sibling modules");
+
+promise_test(async t => {
+ const { checkTask } = await import("./sibling-imports-not-blocked__task__parent.js");
+
+ assert_equals(checkTask, "PASS");
+}, "Async modules scheduling tasks don't block execution of sibling modules");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__microtask__check-get-sync.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__microtask__check-get-sync.js
new file mode 100644
index 0000000000..063697310a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__microtask__check-get-sync.js
@@ -0,0 +1 @@
+export const { checkMicrotask } = globalThis;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__microtask__check-set-async.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__microtask__check-set-async.js
new file mode 100644
index 0000000000..98930fed0b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__microtask__check-set-async.js
@@ -0,0 +1,3 @@
+globalThis.checkMicrotask = "PASS";
+await 0;
+globalThis.checkMicrotask = "FAIL";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__microtask__parent.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__microtask__parent.js
new file mode 100644
index 0000000000..a28fcef9cb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__microtask__parent.js
@@ -0,0 +1,2 @@
+import "./sibling-imports-not-blocked__microtask__check-set-async.js";
+export { checkMicrotask } from "./sibling-imports-not-blocked__microtask__check-get-sync.js";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__task__check-get-sync.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__task__check-get-sync.js
new file mode 100644
index 0000000000..833acdd7ec
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__task__check-get-sync.js
@@ -0,0 +1 @@
+export const { checkTask } = globalThis;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__task__check-set-async.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__task__check-set-async.js
new file mode 100644
index 0000000000..0973b3c65a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__task__check-set-async.js
@@ -0,0 +1,3 @@
+globalThis.checkTask = "PASS";
+await new Promise(r => setTimeout(r, 0));
+globalThis.checkTask = "FAIL";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__task__parent.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__task__parent.js
new file mode 100644
index 0000000000..56ee5134d7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/top-level-await/sibling-imports-not-blocked__task__parent.js
@@ -0,0 +1,2 @@
+import "./sibling-imports-not-blocked__task__check-set-async.js";
+export { checkTask } from "./sibling-imports-not-blocked__task__check-get-sync.js";
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/type.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/type.html
new file mode 100644
index 0000000000..5817ae4d43
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/type.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Type attribute of module scripts</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+window.t1 = async_test('type="module"');
+window.t2 = async_test('type="MODULE"');
+window.t3 = async_test('type="Module"');
+window.t4 = async_test('type="module "');
+window.t5 = async_test('type=" module"');
+</script>
+<script type="module">window.t1.done();</script>
+<script type="MODULE">window.t2.done();</script>
+<script type="Module">window.t3.done();</script>
+<script type="module ">window.t4.unreached_func('Unexpectedly evaluated');</script>
+<script type=" module">window.t5.unreached_func('Unexpectedly evaluated');</script>
+<script type="module">
+window.t1.unreached_func('Unexpectedly not evaluated')();
+window.t2.unreached_func('Unexpectedly not evaluated')();
+window.t3.unreached_func('Unexpectedly not evaluated')();
+window.t4.done();
+window.t5.done();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents-during-evaluation.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents-during-evaluation.html
new file mode 100644
index 0000000000..e4cd887c61
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents-during-evaluation.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Moving script elements between documents during evaluation</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script id="outerScript">
+"use strict";
+
+async_test(t => {
+ const outerScript = document.querySelector('#outerScript');
+ assert_equals(document.currentScript, outerScript);
+
+ const innerScript = document.createElement('script');
+ window.innerScript = innerScript;
+
+ window.innerScriptEvaluated = false;
+ window.anotherDocument = null;
+
+ innerScript.innerText = `
+ window.innerScriptEvaluated = true;
+ const innerScript = window.innerScript;
+ assert_equals(document.currentScript, innerScript,
+ '[1] Before move: currentScript of source Document');
+ assert_equals(innerScript.ownerDocument, document,
+ '[1] Before move: ownerDocument');
+
+ window.anotherDocument = document.implementation.createHTMLDocument();
+ window.anotherDocument.body.appendChild(innerScript);
+
+ assert_equals(innerScript.ownerDocument, anotherDocument,
+ '[2] Just after move: ownerDocument');
+ assert_equals(document.currentScript, innerScript,
+ '[2] Just after move: currentScript of source Document');
+ assert_equals(anotherDocument.currentScript, null,
+ '[2] Just after move: currentScript of destination Document');
+ `;
+
+ document.body.appendChild(innerScript);
+ assert_true(window.innerScriptEvaluated,
+ 'Inner script should be evaluated synchronously');
+
+ assert_equals(document.currentScript, outerScript,
+ '[3] After inner script: currentScript of source Document');
+ assert_equals(window.anotherDocument.currentScript, null,
+ '[3] After inner script: currentScript of destination Document');
+
+ t.step_timeout(() => {
+ assert_equals(document.currentScript, null,
+ '[4] After outer script: currentScript of source Document');
+ assert_equals(anotherDocument.currentScript, null,
+ '[4] After outer script: currentScript of destination Document');
+ t.done();
+ }, 0);
+}, 'Script moved between documents during evaluation');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/README.md b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/README.md
new file mode 100644
index 0000000000..f95e0a63a3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/README.md
@@ -0,0 +1,16 @@
+The tests in this directory are for script elements moved between documents.
+
+Use
+
+```
+$ tools/generate.py
+```
+
+to generate test HTML files (except for tests in subdirectories).
+
+Background:
+
+- https://www.w3.org/Bugs/Public/show_bug.cgi?id=11323
+- https://github.com/whatwg/html/issues/2137
+- https://github.com/whatwg/html/issues/2469
+- https://github.com/whatwg/html/pull/2673
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-fetch-error-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-fetch-error-external-classic.html
new file mode 100644
index 0000000000..2b84ae8088
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-fetch-error-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "createHTMLDocument", "fetch-error", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-fetch-error-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-fetch-error-external-module.html
new file mode 100644
index 0000000000..09ee1490c1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-fetch-error-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "createHTMLDocument", "fetch-error", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-parse-error-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-parse-error-external-classic.html
new file mode 100644
index 0000000000..477abd32cf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-parse-error-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "createHTMLDocument", "parse-error", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-parse-error-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-parse-error-external-module.html
new file mode 100644
index 0000000000..9da2276408
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-parse-error-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "createHTMLDocument", "parse-error", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-success-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-success-external-classic.html
new file mode 100644
index 0000000000..bc4deb6f17
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-success-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "createHTMLDocument", "success", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-success-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-success-external-module.html
new file mode 100644
index 0000000000..ff76954f9d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-createHTMLDocument-success-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "createHTMLDocument", "success", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-fetch-error-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-fetch-error-external-classic.html
new file mode 100644
index 0000000000..768120e9d8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-fetch-error-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "iframe", "fetch-error", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-fetch-error-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-fetch-error-external-module.html
new file mode 100644
index 0000000000..50dd80662d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-fetch-error-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "iframe", "fetch-error", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-parse-error-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-parse-error-external-classic.html
new file mode 100644
index 0000000000..981a068230
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-parse-error-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "iframe", "parse-error", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-parse-error-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-parse-error-external-module.html
new file mode 100644
index 0000000000..6debb4189e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-parse-error-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "iframe", "parse-error", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-parse-error-inline-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-parse-error-inline-classic.html
new file mode 100644
index 0000000000..53389f20db
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-parse-error-inline-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "iframe", "parse-error", "inline", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-success-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-success-external-classic.html
new file mode 100644
index 0000000000..9c4a12226a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-success-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "iframe", "success", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-success-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-success-external-module.html
new file mode 100644
index 0000000000..0a0490cfc1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-success-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "iframe", "success", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-success-inline-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-success-inline-classic.html
new file mode 100644
index 0000000000..932825709f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/after-prepare-iframe-success-inline-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("after-prepare", "iframe", "success", "inline", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-fetch-error-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-fetch-error-external-classic.html
new file mode 100644
index 0000000000..444382ac20
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-fetch-error-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "createHTMLDocument", "fetch-error", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-fetch-error-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-fetch-error-external-module.html
new file mode 100644
index 0000000000..e1a1f7c08f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-fetch-error-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "createHTMLDocument", "fetch-error", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-external-classic.html
new file mode 100644
index 0000000000..6bb5ebddbd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "createHTMLDocument", "parse-error", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-external-module.html
new file mode 100644
index 0000000000..10a6549f62
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "createHTMLDocument", "parse-error", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-inline-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-inline-classic.html
new file mode 100644
index 0000000000..28bd935995
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-inline-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "createHTMLDocument", "parse-error", "inline", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-inline-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-inline-module.html
new file mode 100644
index 0000000000..e665a75629
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-parse-error-inline-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "createHTMLDocument", "parse-error", "inline", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-empty-src-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-empty-src-classic.html
new file mode 100644
index 0000000000..8ee0dd1de1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-empty-src-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "createHTMLDocument", "success", "empty-src", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-empty-src-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-empty-src-module.html
new file mode 100644
index 0000000000..4791149c57
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-empty-src-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "createHTMLDocument", "success", "empty-src", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-external-classic.html
new file mode 100644
index 0000000000..3a3aceaf26
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "createHTMLDocument", "success", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-external-module.html
new file mode 100644
index 0000000000..c15b4fc77f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "createHTMLDocument", "success", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-inline-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-inline-classic.html
new file mode 100644
index 0000000000..576f4d4684
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-inline-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "createHTMLDocument", "success", "inline", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-inline-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-inline-module.html
new file mode 100644
index 0000000000..c84d61c89a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-createHTMLDocument-success-inline-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "createHTMLDocument", "success", "inline", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-fetch-error-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-fetch-error-external-classic.html
new file mode 100644
index 0000000000..febf6fcc55
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-fetch-error-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "iframe", "fetch-error", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-fetch-error-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-fetch-error-external-module.html
new file mode 100644
index 0000000000..f936260b8c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-fetch-error-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "iframe", "fetch-error", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-external-classic.html
new file mode 100644
index 0000000000..870c900abf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "iframe", "parse-error", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-external-module.html
new file mode 100644
index 0000000000..fb44a89df1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "iframe", "parse-error", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-inline-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-inline-classic.html
new file mode 100644
index 0000000000..986e4fa396
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-inline-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "iframe", "parse-error", "inline", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-inline-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-inline-module.html
new file mode 100644
index 0000000000..3a3fc38479
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-parse-error-inline-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "iframe", "parse-error", "inline", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-empty-src-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-empty-src-classic.html
new file mode 100644
index 0000000000..4f03a94358
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-empty-src-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "iframe", "success", "empty-src", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-empty-src-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-empty-src-module.html
new file mode 100644
index 0000000000..a7bd42fd32
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-empty-src-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "iframe", "success", "empty-src", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-external-classic.html
new file mode 100644
index 0000000000..08a8ac4afa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "iframe", "success", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-external-module.html
new file mode 100644
index 0000000000..b8c3f79fea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "iframe", "success", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-inline-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-inline-classic.html
new file mode 100644
index 0000000000..b639f6109d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-inline-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "iframe", "success", "inline", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-inline-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-inline-module.html
new file mode 100644
index 0000000000..616e46310f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/before-prepare-iframe-success-inline-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("before-prepare", "iframe", "success", "inline", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-fetch-error-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-fetch-error-external-classic.html
new file mode 100644
index 0000000000..745c62d898
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-fetch-error-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "createHTMLDocument", "fetch-error", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-fetch-error-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-fetch-error-external-module.html
new file mode 100644
index 0000000000..f6353a05fc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-fetch-error-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "createHTMLDocument", "fetch-error", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-parse-error-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-parse-error-external-classic.html
new file mode 100644
index 0000000000..21099c3ff9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-parse-error-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "createHTMLDocument", "parse-error", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-parse-error-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-parse-error-external-module.html
new file mode 100644
index 0000000000..2eb153acfe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-parse-error-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "createHTMLDocument", "parse-error", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-success-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-success-external-classic.html
new file mode 100644
index 0000000000..88821826ed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-success-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "createHTMLDocument", "success", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-success-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-success-external-module.html
new file mode 100644
index 0000000000..f2a9d95741
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-createHTMLDocument-success-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "createHTMLDocument", "success", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-fetch-error-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-fetch-error-external-classic.html
new file mode 100644
index 0000000000..d96bfe0d50
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-fetch-error-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "iframe", "fetch-error", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-fetch-error-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-fetch-error-external-module.html
new file mode 100644
index 0000000000..7e71bfbe15
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-fetch-error-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "iframe", "fetch-error", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-parse-error-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-parse-error-external-classic.html
new file mode 100644
index 0000000000..757c879897
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-parse-error-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "iframe", "parse-error", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-parse-error-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-parse-error-external-module.html
new file mode 100644
index 0000000000..6ec6c5970e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-parse-error-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "iframe", "parse-error", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-parse-error-inline-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-parse-error-inline-classic.html
new file mode 100644
index 0000000000..c0b0f9d404
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-parse-error-inline-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "iframe", "parse-error", "inline", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-external-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-external-classic.html
new file mode 100644
index 0000000000..7955dbce62
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-external-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "iframe", "success", "external", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-external-module.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-external-module.html
new file mode 100644
index 0000000000..af17eb01f7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-external-module.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "iframe", "success", "external", "module");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-inline-classic.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-inline-classic.html
new file mode 100644
index 0000000000..8a44e2feaf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-inline-classic.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("move-back", "iframe", "success", "inline", "classic");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/README.md b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/README.md
new file mode 100644
index 0000000000..dcf5597e2d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/README.md
@@ -0,0 +1,6 @@
+The tests in this directory checks side effects (other than script
+evaluation/event firing, which is covered by the tests in the parent directory)
+caused by scripts moved between Documents.
+
+The tests assume that script loading is not canceled when moved between
+documents (which is not explicitly specified as of Jan 2022).
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-1.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-1.html
new file mode 100644
index 0000000000..c5812765eb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helper.js"></script>
+
+<body>
+<script>
+runDelayEventTest('Script elements (parser-blocking)');
+</script>
+
+<script id="to-be-moved" src="../../resources/throw.js?pipe=trickle(d3)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-2.html
new file mode 100644
index 0000000000..916673894d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helper.js"></script>
+
+<body>
+<script>
+runDelayEventTest('Script elements (async)');
+
+const script = document.createElement('script');
+script.setAttribute('id', 'to-be-moved');
+script.setAttribute('src', '../../resources/throw.js?pipe=trickle(d3)');
+document.body.appendChild(script);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-iframe.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-iframe.html
new file mode 100644
index 0000000000..efec7657c3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-iframe.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<body onload="parent.onloadIframe()">
+<script src="../../resources/throw.js?pipe=trickle(d2)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/helper.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/helper.js
new file mode 100644
index 0000000000..cb5f45eb0d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/helper.js
@@ -0,0 +1,31 @@
+function runDelayEventTest(description) {
+ const t_original = async_test(description +
+ ' still delay the load event in the original Document after move');
+ const t_new = async_test(description +
+ ' does not delay the load event in the new Document after move');
+ const iframe = document.createElement('iframe');
+ iframe.setAttribute('src', 'delay-load-event-iframe.html');
+ const start_time = performance.now();
+ document.body.appendChild(iframe);
+
+ window.onload = t_original.step_func_done(() => {
+ // The `#to-be-moved` script should delay the load event until it is loaded
+ // (i.e. 3 seconds), not just until it is moved out to another Document
+ // (i.e. 1 second). Here we expect the delay should be at least 2 seconds,
+ // as the latency can be slightly less than 3 seconds due to preloading.
+ assert_greater_than(performance.now() - start_time, 2000,
+ 'Load event should be delayed until script is loaded');
+ });
+
+ window.onloadIframe = t_new.step_func_done(() => {
+ // The iframe's load event is fired after 2 seconds of its subresource
+ // loading, and shouldn't wait for the `#to-be-moved` script.
+ assert_less_than(performance.now() - start_time, 3000,
+ 'Load event should not be delayed until moved script is loaded');
+ });
+
+ t_original.step_timeout(() => {
+ const script = document.querySelector('#to-be-moved');
+ iframe.contentDocument.body.appendChild(script);
+ }, 1000);
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/in-order.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/in-order.html
new file mode 100644
index 0000000000..6a3e2b54a7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/in-order.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/C/#list-of-scripts-that-will-execute-in-order-as-soon-as-possible">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helper.js"></script>
+<body>
+<script>
+const t = async_test('Script elements (in-order) still block subsequent in-order scripts in the original Document after moved to another Document');
+const start_time = performance.now();
+const iframe = document.createElement('iframe');
+document.body.appendChild(iframe);
+
+const onScript2Evaluated = t.step_func_done(() => {
+ // `script1` should remain the
+ // #list-of-scripts-that-will-execute-in-order-as-soon-as-possible of the
+ // original Document and thus blocks `script2` evaluation until it is loaded.
+ assert_greater_than(performance.now() - start_time, 2000,
+ 'In-order scripts should block subsequent in-order scripts');
+});
+
+const script1 = document.createElement('script');
+script1.async = false;
+script1.setAttribute('src', '../../resources/throw.js?pipe=trickle(d2)');
+document.body.appendChild(script1);
+
+const script2 = document.createElement('script');
+script2.async = false;
+script2.setAttribute('src', 'data:text/javascript,onScript2Evaluated()');
+document.body.appendChild(script2);
+
+t.step_timeout(() => {
+ iframe.contentDocument.body.appendChild(script1);
+}, 1000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/parser-blocking.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/parser-blocking.html
new file mode 100644
index 0000000000..9edde13736
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/parser-blocking.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/C/#pending-parsing-blocking-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helper.js"></script>
+<body>
+<script>
+const t = async_test('Script elements (parser-blocking) still block the parser in the original Document after moved to another Document');
+const start_time = performance.now();
+const iframe = document.createElement('iframe');
+document.body.appendChild(iframe);
+
+t.step_timeout(() => {
+ const script = document.querySelector('#to-be-moved');
+ iframe.contentDocument.body.appendChild(script);
+}, 1000);
+
+let syncScriptEvaluated = false;
+
+const onSyncScript = t.step_func(() => {
+ syncScriptEvaluated = true;
+
+ // The `#to-be-moved` script should block the parser and thus the sync
+ // script after `#to-be-moved` should be delayed until `#to-be-moved` is
+ // loaded (i.e. 3 seconds).
+ // Here we expect the delay should be at least 2 seconds,
+ // as the latency can be slightly less than 3 seconds due to preloading.
+ assert_greater_than(performance.now() - start_time, 2000,
+ 'Parser should be blocked until script is loaded');
+});
+
+document.addEventListener('DOMContentLoaded', t.step_func_done(() => {
+ assert_true(syncScriptEvaluated,
+ 'sync script should be evaluated before DOMContentLoaded');
+ assert_greater_than(performance.now() - start_time, 2000,
+ 'DOMContentLoaded event should be delayed until script is loaded');
+}));
+</script>
+<script id="to-be-moved" src="../../resources/throw.js?pipe=trickle(d3)"></script>
+<script src="data:text/javascript,onSyncScript()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-helper.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-helper.js
new file mode 100644
index 0000000000..de4af6ac10
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-helper.js
@@ -0,0 +1,214 @@
+"use strict";
+
+function createDocument(documentType, result, inlineOrExternal, type, hasBlockingStylesheet) {
+ return new Promise((resolve, reject) => {
+ const iframe = document.createElement("iframe");
+ iframe.src =
+ "resources/moving-between-documents-iframe.py" +
+ "?result=" + result +
+ "&inlineOrExternal=" + inlineOrExternal +
+ "&type=" + type +
+ "&hasBlockingStylesheet=" + hasBlockingStylesheet +
+ "&cache=" + Math.random();
+ // As blocking stylesheets delays Document load events, we use
+ // DOMContentLoaded here.
+ // After that point, we expect iframe.contentDocument exists
+ // while still waiting for blocking stylesheet loading.
+ document.body.appendChild(iframe);
+
+ window.addEventListener('message', (event) => {
+ if (documentType === "iframe") {
+ resolve([iframe.contentWindow, iframe.contentDocument]);
+ } else if (documentType === "createHTMLDocument") {
+ resolve([
+ iframe.contentWindow,
+ iframe.contentDocument.implementation.createHTMLDocument("")]);
+ } else {
+ reject(new Error("Invalid document type: " + documentType));
+ }
+ }, {once: true});
+ });
+}
+
+window.didExecute = undefined;
+
+// For a script, there are three associated Documents that can
+// potentially different:
+//
+// [1] script's parser document
+// https://html.spec.whatwg.org/C/#parser-document
+//
+// [2] script's preparation-time document
+// https://html.spec.whatwg.org/C/#preparation-time-document
+// == script's node document at the beginning of #prepare-a-script
+//
+// [3] script's node document at the beginning of
+// #execute-the-script-block
+//
+// This helper is for tests where [1]/[2]/[3] are different.
+
+// In the spec, scripts are executed only if [1]/[2]/[3] are all the same
+// (or [1] is null and [2]==[3]).
+//
+// A check for [1]==[2] is in #prepare-a-script and
+// a check for [1]==[3] is in #execute-the-script-block,
+// but these are under debate: https://github.com/whatwg/html/issues/2137
+//
+// A check for [2]==[3] is in #execute-the-script-block, which is added by
+// https://github.com/whatwg/html/pull/2673
+
+// timing:
+// "before-prepare":
+// A <script> is moved during parsing before #prepare-a-script.
+// [1] != [2] == [3]
+//
+// "after-prepare":
+// A <script> is moved after parsing/#prepare-a-script but
+// before #execute-the-script-block.
+// [1] == [2] != [3]
+//
+// To move such scripts, #has-a-style-sheet-that-is-blocking-scripts
+// is utilized to block inline scripts after #prepare-a-script.
+// Note: this is a corner case in the spec which might be removed
+// from the spec in the future, e.g.
+// https://github.com/whatwg/html/issues/1349
+// https://github.com/chrishtr/rendering/blob/master/stylesheet-loading-proposal.md
+//
+// TODO(domfarolino): Remove the "parsing but moved back" tests, because if a
+// <script> is moved before #prepare-a-script, per spec it should never make
+// it to #execute-the-script-block. If an implementation does not implement
+// the check in #prepare-a-script, then it will fail the "before-prepare"
+// tests, so these are not necessary.
+// "parsing but moved back"
+// A <script> is moved before #prepare-a-script, but moved back again
+// to the original Document after #prepare-a-script.
+// [1] == [3] != [2]
+//
+// destType: "iframe" or "createHTMLDocument".
+// result: "fetch-error", "parse-error", or "success".
+// inlineOrExternal: "inline" or "external" or "empty-src".
+// type: "classic" or "module".
+async function runTest(timing, destType, result, inlineOrExternal, type) {
+ const description =
+ `Move ${result} ${inlineOrExternal} ${type} script ` +
+ `to ${destType} ${timing}`;
+
+ const t = async_test("Eval: " + description);
+ const tScriptLoadEvent = async_test("<script> load: " + description);
+ const tScriptErrorEvent = async_test("<script> error: " + description);
+ const tWindowErrorEvent = async_test("window error: " + description);
+
+ // If scripts should be moved after #prepare-a-script before
+ // #execute-the-script-block, we add a style sheet that is
+ // blocking scripts.
+ const hasBlockingStylesheet =
+ timing === "after-prepare" || timing === "move-back";
+
+ const [sourceWindow, sourceDocument] = await createDocument(
+ "iframe", result, inlineOrExternal, type, hasBlockingStylesheet);
+
+ // Due to https://crbug.com/1034176, Chromium needs
+ // blocking stylesheets also in the destination Documents.
+ const [destWindow, destDocument] = await createDocument(
+ destType, null, null, null, hasBlockingStylesheet);
+
+ const scriptOnLoad =
+ tScriptLoadEvent.unreached_func("Script load event fired unexpectedly");
+ const scriptOnError = (event) => {
+ // For Firefox: Prevent window.onerror is fired due to propagation
+ // from <script>'s error event.
+ event.stopPropagation();
+
+ tScriptErrorEvent.unreached_func("Script error evennt fired unexpectedly")();
+ };
+
+ sourceWindow.didExecute = false;
+ sourceWindow.t = t;
+ sourceWindow.scriptOnLoad = scriptOnLoad;
+ sourceWindow.scriptOnError = scriptOnError;
+ sourceWindow.onerror = tWindowErrorEvent.unreached_func(
+ "Window error event shouldn't fired on source window");
+ sourceWindow.readyToEvaluate = false;
+
+ destWindow.didExecute = false;
+ destWindow.t = t;
+ destWindow.scriptOnLoad = scriptOnLoad;
+ destWindow.scriptOnError = scriptOnError;
+ destWindow.onerror = tWindowErrorEvent.unreached_func(
+ "Window error event shouldn't fired on destination window");
+ destWindow.readyToEvaluate = false;
+
+ // t=0 sec: Move between documents before #prepare-a-script.
+ // At this time, the script element is not yet inserted to the DOM.
+ if (timing === "before-prepare" || timing === "move-back") {
+ destDocument.body.appendChild(
+ sourceDocument.querySelector("streaming-element"));
+ }
+ if (timing === "before-prepare") {
+ sourceWindow.readyToEvaluate = true;
+ destWindow.readyToEvaluate = true;
+ }
+
+ // t=1 sec: the script element is inserted to the DOM, i.e.
+ // #prepare-a-script is triggered (see monving-between-documents-iframe.py).
+ // In the case of `before-prepare`, the script can be evaluated.
+ // In other cases, the script evaluation is blocked by a style sheet.
+ await new Promise(resolve => step_timeout(resolve, 2000));
+
+ // t=2 sec: Move between documents after #prepare-a-script.
+ if (timing === "after-prepare") {
+ // At this point, the script hasn't been moved yet, so we'll move it for the
+ // first time, after #prepare-a-script, but before #execute-the-script-block.
+ destDocument.body.appendChild(
+ sourceDocument.querySelector("streaming-element"));
+ } else if (timing === "move-back") {
+ // At this point the script has already been moved to the destination block
+ // before #prepare-a-script, so we'll move it back to the source document
+ // before #execute-the-script-block.
+ sourceDocument.body.appendChild(
+ destDocument.querySelector("streaming-element"));
+ }
+ sourceWindow.readyToEvaluate = true;
+ destWindow.readyToEvaluate = true;
+
+ // t=3 or 5 sec: Blocking stylesheet and external script are loaded,
+ // and thus script evaulation is unblocked.
+
+ // Note: scripts are expected to be loaded at t=3, because the fetch
+ // is started by #prepare-a-script at t=1, and the script's delay is
+ // 2 seconds. However in Chromium, due to preload scanner, the script
+ // loading might take 4 seconds, because the first request by preload
+ // scanner of the source Document takes 2 seconds (between t=1 and t=3)
+ // which blocks the second request by #prepare-a-script that takes
+ // another 2 seconds (between t=3 and t=5).
+
+ // t=6 sec: After all possible script evaluation points, test whether
+ // the script/events were evaluated/fired or not.
+ // As we have concurrent tests, a single global step_timeout() is
+ // used instead of multiple `t.step_timeout()` etc.,
+ // to avoid potential race conditions between `t.step_timeout()`s.
+ return new Promise(resolve => {
+ step_timeout(() => {
+ tWindowErrorEvent.done();
+ tScriptLoadEvent.done();
+ tScriptErrorEvent.done();
+
+ t.step_func_done(() => {
+ assert_false(sourceWindow.didExecute,
+ "The script must not have executed in source window");
+ assert_false(destWindow.didExecute,
+ "The script must not have executed in destination window");
+ })();
+ resolve();
+ }, 4000);
+ });
+}
+
+async_test(t => {
+ t.step_timeout(() => {
+ assert_equals(window.didExecute, undefined,
+ "The script must not have executed in the top-level window");
+ t.done();
+ },
+ 4000);
+}, "Sanity check around top-level Window");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-iframe.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-iframe.py
new file mode 100644
index 0000000000..dbcfe9b8d0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-iframe.py
@@ -0,0 +1,102 @@
+import random
+import time
+
+from wptserve.utils import isomorphic_decode
+
+
+"""
+This script serves
+"""
+
+def main(request, response):
+ inlineOrExternal = request.GET.first(b"inlineOrExternal", b"null")
+ hasBlockingStylesheet = request.GET.first(b"hasBlockingStylesheet", b"true") == b"true"
+ result = request.GET.first(b"result", b"success")
+ type = u"text/javascript" if request.GET.first(b"type", b"classic") == b"classic" else u"module"
+
+ response.headers.set(b"Content-Type", b"text/html; charset=utf-8")
+ response.headers.set(b"Transfer-Encoding", b"chunked")
+ response.write_status_headers()
+
+ # Step 1: Start parsing.
+ body = u"""<!DOCTYPE html>
+ <head>
+ <script>
+ parent.postMessage("fox", "*");
+ </script>
+ """
+
+ if hasBlockingStylesheet:
+ body += u"""
+ <link rel="stylesheet" href="slow-flag-setter.py?result=css&cache=%f">
+ """ % random.random()
+
+ body += u"""
+ </head>
+ <body>
+ """
+
+ if inlineOrExternal == b"inline" or inlineOrExternal == b"external" or inlineOrExternal == b"empty-src":
+ body += u"""
+ <streaming-element>
+ """
+
+ # Trigger DOM processing
+ body += u"A" * 100000
+
+ response.writer.write(u"%x\r\n" % len(body))
+ response.writer.write(body)
+ response.writer.write(u"\r\n")
+
+ body = u""
+
+ if inlineOrExternal == b"inline":
+ time.sleep(1)
+ body += u"""
+ <script id="s1" type="%s"
+ onload="scriptOnLoad()"
+ onerror="scriptOnError(event)">
+ if (!window.readyToEvaluate) {
+ window.didExecute = "executed too early";
+ } else {
+ window.didExecute = "executed";
+ }
+ """ % type
+ if result == b"parse-error":
+ body += u"1=2 parse error\n"
+
+ body += u"""
+ </script>
+ </streaming-element>
+ """
+ elif inlineOrExternal == b"external":
+ time.sleep(1)
+ body += u"""
+ <script id="s1" type="%s"
+ src="slow-flag-setter.py?result=%s&cache=%s"
+ onload="scriptOnLoad()"
+ onerror="scriptOnError(event)"></script>
+ </streaming-element>
+ """ % (type, isomorphic_decode(result), random.random())
+ elif inlineOrExternal == b"empty-src":
+ time.sleep(1)
+ body += u"""
+ <script id="s1" type="%s"
+ src=""
+ onload="scriptOnLoad()"
+ onerror="scriptOnError(event)"></script>
+ </streaming-element>
+ """ % (type,)
+
+ # // if readyToEvaluate is false, the script is probably
+ # // wasn't blocked by stylesheets as expected.
+
+ # Trigger DOM processing
+ body += u"B" * 100000
+
+ response.writer.write(u"%x\r\n" % len(body))
+ response.writer.write(body)
+ response.writer.write(u"\r\n")
+
+ response.writer.write(u"0\r\n")
+ response.writer.write(u"\r\n")
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/slow-flag-setter.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/slow-flag-setter.py
new file mode 100644
index 0000000000..20d7ed4bd0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/slow-flag-setter.py
@@ -0,0 +1,29 @@
+
+import time
+
+def main(request, response):
+ headers = [(b"Content-Type", b"text/javascript")]
+
+ result = request.GET.first(b"result", b"success")
+ if result == b"css":
+ time.sleep(3)
+ headers = [(b"Content-Type", b"text/css")]
+ body = u""
+ else:
+ time.sleep(2)
+
+ body = u"""
+ fetch('exec');
+ console.log('exec');
+ if (!window.readyToEvaluate) {
+ window.didExecute = "executed too early";
+ } else {
+ window.didExecute = "executed";
+ }
+ """
+ if result == b"parse-error":
+ body = u"1=2 parse error;"
+ if result == b"fetch-error":
+ return 404, [(b'Content-Type', b'text/plain')], u"""window.didExecute = "fetch error";"""
+
+ return headers, body
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/tools/generate.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/tools/generate.py
new file mode 100644
index 0000000000..80a655e821
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/tools/generate.py
@@ -0,0 +1,61 @@
+template = '''<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Moving script elements between documents</title>
+<!-- This is generated by tools/generate.py. Do not manually edit. -->
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#execute-the-script-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/moving-between-documents-helper.js"></script>
+
+<body>
+<script>
+runTest("%s", "%s", "%s", "%s", "%s");
+</script>
+'''
+
+n = 0
+for timing in ["before-prepare", "after-prepare", "move-back"]:
+ for destType in ["iframe", "createHTMLDocument"]:
+ for inlineOrExternal in ["inline", "external", "empty-src"]:
+ for result in ["fetch-error", "parse-error", "success"]:
+ for type in ["classic", "module"]:
+ # The |inlineOrExternal| keyword creates a certain kind of script,
+ # and the |result| keyword can influence the generated script in
+ # different ways i.e., giving the script a parse-error, or creating
+ # a script that fails to load. When we're creating an inline script,
+ # it doesn't make sense to test the fetch-error case, so we ignore
+ # this combination, as the server will not react to it in any
+ # meaningful way.
+ if inlineOrExternal == "inline" and result == "fetch-error":
+ continue
+
+ if inlineOrExternal == "empty-src":
+ # The "empty-src" tests aim to exercise #prepare-a-script step 26
+ # substep 2, where the <script> has a src attribute that is empty:
+ # "If src is the empty string, queue a task to fire an event named
+ # error at the element, and return."
+ # Therefore, the server will generate a script that does not have a
+ # "parse-error" or "fetch-error", so we can ignore these combinations.
+ if result != "success":
+ continue
+
+ # The "empty-src" tests check that the parser document <=> node document
+ # check is implemented correctly in #prepare-a-script. Therefore we're
+ # only interested in tests that move the <script> before #prepare-a-script.
+ if timing != "before-prepare":
+ continue
+
+ # The current test helper uses
+ # #has-a-style-sheet-that-is-blocking-scripts to block script
+ # evaluation after #prepare-a-script, but in some cases this
+ # doesn't work:
+ # - inline scripts to createHTMLDocument
+ if timing != "before-prepare" and destType == "createHTMLDocument" and inlineOrExternal == "inline":
+ continue
+ # - module inline scripts https://github.com/whatwg/html/issues/3890
+ if timing != "before-prepare" and inlineOrExternal == "inline" and type == "module":
+ continue
+
+ with open('%s-%s-%s-%s-%s.html' % (timing, destType, result, inlineOrExternal, type), 'w') as f:
+ f.write(template % (timing, destType, result, inlineOrExternal, type))
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/muted-errors-iframe.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/muted-errors-iframe.html
new file mode 100644
index 0000000000..255e79e191
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/muted-errors-iframe.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<script src="cacheable-script-throw.py?iframe"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/muted-errors.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/muted-errors.sub.html
new file mode 100644
index 0000000000..61643c5f08
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/muted-errors.sub.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<title>Muted Errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// https://html.spec.whatwg.org/multipage/webappapis.html#report-the-error
+// If script's muted errors is true, then set message to "Script error.",
+// urlString to the empty string, line and col to 0, and errorValue to null.
+ setup({allow_uncaught_exception: true});
+
+ window.log = [];
+ window.addEventListener("error", ev => log.push(ev));
+
+ function check(shouldBeMuted) {
+ assert_equals(log.length, 1);
+ var ev = log[0];
+ log = [];
+ if (shouldBeMuted) {
+ assert_equals(ev.message, "Script error.");
+ assert_equals(ev.error, null, 'error');
+ assert_equals(ev.filename, "", 'filename');
+ assert_equals(ev.lineno, 0, 'lineno');
+ assert_equals(ev.colno, 0, 'colno');
+ } else {
+ assert_not_equals(ev.message, "Script error.");
+ assert_not_equals(ev.error, null);
+ }
+ }
+
+ var test1 = async_test("Errors for same-origin script shouldn't be muted");
+ var check1 = test1.step_func_done(() => check(false));
+
+ var test2 = async_test("Errors for cross-origin script should be muted");
+ var check2 = test2.step_func_done(() => check(true));
+
+ var test3 = async_test("Errors for cross-origin script should be muted " +
+ "even if the script is once loaded as same-origin");
+ function step3() {
+ var script = document.createElement('script');
+ script.setAttribute('src', "//{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py?iframe");
+ script.onerror = test3.unreached_func();
+ script.onload = test3.step_func_done(() => check(true));
+ document.body.appendChild(script);
+ }
+
+ var test4 = async_test("Errors for same-origin scripts redirected to a " +
+ "cross-origin url and redirected back to " +
+ "same-origin should be muted");
+ var check4 = test4.step_func_done(() => check(true));
+
+ var test5 = async_test("Errors for cross-origin scripts redirected to a " +
+ "same-origin url should be muted");
+ var check5 = test5.step_func_done(() => check(true));
+
+ const test6 = async_test("Non-synthetic errors for same-origin scripts redirected to a " +
+ "cross-origin URL and redirected back to same-origin should be " +
+ "muted");
+ const check6 = test6.step_func_done(() => check(true));
+
+ const test7 = async_test("Syntax error for same-origin script redirected to a " +
+ "cross-origin URL and redirected back to same-origin should be " +
+ "muted");
+ const check7 = test7.step_func_done(() => check(true));
+
+ function unreachable() { log.push("unexpected"); }
+</script>
+<script src="cacheable-script-throw.py" onerror="test1.unreached_func()()" onload="check1()"></script>
+<script src="//{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py"
+ onerror="test2.unreached_func()()" onload="check2()"></script>
+<iframe src="//{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/muted-errors-iframe.html"
+ onerror="test3.unreached_func()()" onload="step3()"></iframe>
+<script src="/fetch/api/resources/redirect.py?location=
+//{{domains[www2]}}:{{ports[http][0]}}/fetch/api/resources/redirect.py?location=
+//{{host}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py?same-cross-same"
+onerror="test4.unreached_func()()" onload="check4()"></script>
+<script src="//{{domains[www2]}}:{{ports[http][0]}}/fetch/api/resources/redirect.py?location=
+//{{host}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py?cross-same"
+onerror="test5.unreached_func()()" onload="check5()"></script>
+<script src="//{{domains[www2]}}:{{ports[http][0]}}/fetch/api/resources/redirect.py?location=
+//{{host}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/resources/throw.js"
+onerror="test6.unreached_func()()" onload="check6()"></script>
+<script src="//{{domains[www2]}}:{{ports[http][0]}}/fetch/api/resources/redirect.py?location=
+//{{host}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/resources/syntax-error.js"
+onerror="test7.unreached_func()()" onload="check7()"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-reflect.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-reflect.html
new file mode 100644
index 0000000000..d6a850f58f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-reflect.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>noModule IDL attribute must reflect nomodule content attribute</title>
+<link rel="author" title="Yusuke Suzuki" href="mailto:utatane.tea@gmail.com">
+<link rel="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script id="classicWithoutNomodule"></script>
+<script id="classicWithNomodule" nomodule></script>
+<script id="moduleWithoutNomodule" type=module></script>
+<script id="moduleWithNomodule" type=module nomodule></script>
+<script>
+
+test(() => {
+ assert_false(document.getElementById('classicWithoutNomodule').noModule);
+}, 'noModule IDL attribute on a parser created classic script element without nomodule content attribute');
+
+test(() => {
+ assert_true(document.getElementById('classicWithNomodule').noModule);
+}, 'noModule IDL attribute on a parser created classic script element with nomodule content attribute');
+
+test(() => {
+ assert_false(document.getElementById('moduleWithoutNomodule').noModule);
+}, 'noModule IDL attribute on a parser created module script element without nomodule content attribute');
+
+test(() => {
+ assert_true(document.getElementById('moduleWithNomodule').noModule);
+}, 'noModule IDL attribute on a parser created module script element with nomodule content attribute');
+
+
+test(() => {
+ const script = document.createElement('script');
+ assert_false(script.noModule);
+}, 'noModule IDL attribute on a dynamically created script element without nomodule content attribute');
+
+test(() => {
+ const script = document.createElement('script');
+ script.setAttribute('nomodule', 'nomodule');
+ assert_true(script.noModule);
+}, 'noModule IDL attribute on a dynamically created script element after nomodule content attribute is set to "nomodule"');
+
+test(() => {
+ const script = document.createElement('script');
+ script.setAttribute('nomodule', '');
+ assert_true(script.noModule);
+}, 'noModule IDL attribute on a dynamically created script element after nomodule content attribute is set to ""');
+
+test(() => {
+ const script = document.createElement('script');
+ script.setAttribute('nomodule', 'nomodule');
+ assert_true(script.noModule);
+ script.removeAttribute('nomodule');
+ assert_false(script.noModule);
+}, 'noModule IDL attribute on a dynamically created script element after nomodule content attribute had been removed');
+
+test(() => {
+ const script = document.createElement('script');
+ assert_false(script.hasAttribute('nomodule'));
+ script.noModule = true;
+ assert_true(script.hasAttribute('nomodule'));
+}, 'noModule IDL attribute must add nomodule content attribute on setting to true');
+
+test(() => {
+ const script = document.createElement('script');
+ script.setAttribute('nomodule', 'nomodule');
+ script.noModule = false;
+ assert_false(script.hasAttribute('nomodule'));
+}, 'noModule IDL attribute must remove nomodule content attribute on setting to false');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-async-classic-script.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-async-classic-script.html
new file mode 100644
index 0000000000..25de796830
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-async-classic-script.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>External classic scripts with nomodule content attribute must not run</title>
+<link rel="author" title="Yusuke Suzuki" href="mailto:utatane.tea@gmail.com">
+<link rel="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!-- Load this script synchronously to ensure test cases below can load it in 200ms -->
+<script src="resources/set-script-executed.js"></script>
+</head>
+<body>
+<script>
+let supportsNoModule = "noModule" in document.getElementsByTagName("script")[0];
+
+waitForLoadEvent = new Promise((resolve) => {
+ window.onload = resolve;
+});
+
+promise_test(() => {
+ window.executed = false;
+ let loaded = false;
+ let errored = false;
+
+ let script = document.createElement('script');
+
+ script.src = './resources/set-script-executed.js';
+ script.onload = () => loaded = true;
+ script.onerror = () => errored = true;
+ script.noModule = false;
+ document.body.appendChild(script);
+
+ return waitForLoadEvent.then(() => {
+ assert_true(supportsNoModule);
+ assert_true(executed);
+ assert_true(loaded);
+ assert_false(errored);
+ });
+}, 'An asynchronously loaded classic script with noModule set to false must run');
+
+promise_test(() => {
+ window.executed = false;
+ let loaded = false;
+ let errored = false;
+
+ let script = document.createElement('script');
+ script.src = './resources/set-script-executed.js';
+ script.onload = () => loaded = true;
+ script.onerror = () => errored = true;
+ script.noModule = true;
+ document.body.appendChild(script);
+
+ return waitForLoadEvent.then(() => {
+ assert_true(supportsNoModule);
+ assert_false(executed);
+ assert_false(loaded);
+ assert_false(errored);
+ });
+}, 'An asynchronously loaded classic script with noModule set to true must not run');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-external-module-script.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-external-module-script.html
new file mode 100644
index 0000000000..138b33c9fa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-external-module-script.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>An external module script with nomodule must run</title>
+<link rel="author" title="Yusuke Suzuki" href="mailto:utatane.tea@gmail.com">
+<link rel="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script nomodule type="module" src="./resources/exports-cocoa.js"></script>
+<script>
+
+waitForLoadEvent = new Promise((resolve) => {
+ window.onload = resolve;
+});
+
+promise_test(() => {
+ return waitForLoadEvent.then(() => {
+ assert_equals(typeof cocoa, 'undefined');
+ assert_equals(typeof exportedCocoa, 'object');
+ assert_equals(exportedCocoa.taste(), 'awesome');
+ });
+}, 'An external module script with nomodule content attribute must run');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-classic-scripts.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-classic-scripts.html
new file mode 100644
index 0000000000..588d59975c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-classic-scripts.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Inline classic scripts with nomodule content attribute must not run</title>
+<link rel="author" title="Yusuke Suzuki" href="mailto:utatane.tea@gmail.com">
+<link rel="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+window.executed = true;
+</script>
+<script>
+
+test(() => {
+ assert_true(executed);
+}, 'An inline classic script without nomodule content attribute must run');
+
+
+window.executed = false;
+</script>
+<script nomodule>
+window.executed = true;
+</script>
+<script>
+
+test(() => {
+ assert_false(executed);
+}, 'An inline classic script with nomodule content attribute must not run');
+
+</script>
+<script>
+
+test(() => {
+ window.executed = false;
+ const element = document.createElement("script");
+ element.noModule = false;
+ element.textContent = `window.executed = true`;
+ document.body.appendChild(element);
+ assert_true(window.executed);
+}, 'An inline classic script element dynamically inserted after noModule was set to false must run.');
+
+test(() => {
+ window.executed = false;
+ const element = document.createElement("script");
+ element.noModule = true;
+ element.textContent = `window.executed = true`;
+ document.body.appendChild(element);
+ assert_false(window.executed);
+}, 'An inline classic script element dynamically inserted after noModule was set to true must not run.');
+
+window.executed = false;
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-module-script.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-module-script.html
new file mode 100644
index 0000000000..b11c25932e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-module-script.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>An inline module script with nomodule must run</title>
+<link rel="author" title="Yusuke Suzuki" href="mailto:utatane.tea@gmail.com">
+<link rel="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script nomodule type="module">
+import Cocoa from "./resources/cocoa-module.js";
+var cocoa = new Cocoa();
+window.exportedCocoa = cocoa;
+</script>
+<script>
+
+waitForLoadEvent = new Promise((resolve) => {
+ window.onload = resolve;
+});
+
+promise_test(() => {
+ return waitForLoadEvent.then(() => {
+ assert_equals(typeof cocoa, 'undefined');
+ assert_equals(typeof exportedCocoa, 'object');
+ assert_equals(exportedCocoa.taste(), 'awesome');
+ });
+}, 'An inline module script with nomodule content attribute must run');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-synchronously-loaded-classic-scripts.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-synchronously-loaded-classic-scripts.html
new file mode 100644
index 0000000000..9f6207c9a5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/nomodule-set-on-synchronously-loaded-classic-scripts.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>External classic scripts with nomodule content attribute must not run</title>
+<link rel="author" title="Yusuke Suzuki" href="mailto:utatane.tea@gmail.com">
+<link rel="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+
+window.executed = false;
+window.loaded = false;
+window.errored = false;
+</script>
+<script src="./resources/set-script-executed.js" onload="loaded = true" onerror="errored = false"></script>
+<script>
+
+test(() => {
+ assert_true(executed);
+ assert_true(loaded);
+ assert_false(errored);
+}, 'A synchronously loaded external classic script without nomodule content attribute must run');
+
+window.executed = false;
+window.loaded = false;
+window.errored = false;
+</script>
+<script nomodule src="./resources/set-script-executed.js" onload="loaded = true" onerror="errored = false"></script>
+<script>
+
+test(() => {
+ assert_false(executed);
+ assert_false(loaded);
+ assert_false(errored);
+}, 'A synchronously loaded external classic script with nomodule content attribute must not run');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/promise-reject-and-remove.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/promise-reject-and-remove.html
new file mode 100644
index 0000000000..a3b2730e56
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/promise-reject-and-remove.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(t => {
+ window.onload = t.step_func_done();
+}, 'Removing iframe in promise reject handler should not crash');
+</script>
+<iframe src="resources/promise-reject-and-remove-iframe.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/bom-utf-16be.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/bom-utf-16be.js
new file mode 100644
index 0000000000..8a529e10b1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/bom-utf-16be.js
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/bom-utf-16le.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/bom-utf-16le.js
new file mode 100644
index 0000000000..578f7f1951
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/bom-utf-16le.js
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/bom-utf-8.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/bom-utf-8.js
new file mode 100644
index 0000000000..fb88bdda2a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/bom-utf-8.js
@@ -0,0 +1,2 @@
+// JavaScript file with UTF-8 BOM.
+window.executed_utf8_bom = '三村かな子';
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/cocoa-module.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/cocoa-module.js
new file mode 100644
index 0000000000..24360a7bfe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/cocoa-module.js
@@ -0,0 +1,5 @@
+export default class Cocoa {
+ taste() {
+ return "awesome";
+ }
+};
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/cross-origin.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/cross-origin.py
new file mode 100644
index 0000000000..abac1901b7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/cross-origin.py
@@ -0,0 +1,20 @@
+def main(request, response):
+ origin = request.headers.get(b"origin")
+
+ if origin is not None:
+ response.headers.set(b"Access-Control-Allow-Origin", origin)
+ response.headers.set(b"Access-Control-Allow-Methods", b"GET")
+ response.headers.set(b"Access-Control-Allow-Credentials", b"true")
+
+ if request.method == u"OPTIONS":
+ return u""
+
+ headers = [(b"Content-Type", b"text/javascript")]
+ milk = request.cookies.first(b"milk", None)
+
+ if milk is None:
+ return headers, u"var included = false;"
+ elif milk.value == b"yes":
+ return headers, u"var included = true;"
+
+ return headers, u"var included = false;"
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/exports-cocoa.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/exports-cocoa.js
new file mode 100644
index 0000000000..02967bc631
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/exports-cocoa.js
@@ -0,0 +1,3 @@
+import Cocoa from "./cocoa-module.js";
+var cocoa = new Cocoa();
+window.exportedCocoa = cocoa;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/flag-setter.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/flag-setter.js
new file mode 100644
index 0000000000..3274e5bfeb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/flag-setter.js
@@ -0,0 +1,3 @@
+"use strict";
+
+window.didExecute = true;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/load-error-events-helpers.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/load-error-events-helpers.js
new file mode 100644
index 0000000000..bbd6b09c6c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/load-error-events-helpers.js
@@ -0,0 +1,47 @@
+"use strict";
+// Helper functions to be used from load-error-events*.html tests.
+
+function event_test(name, load_to_be_fired, error_to_be_fired) {
+ return {
+ test: async_test(name),
+ executed: false,
+ load_event_to_be_fired: load_to_be_fired,
+ error_event_to_be_fired: error_to_be_fired
+ };
+}
+
+// Should be used as load/error event handlers of script tags,
+// with |t| = the object returned by event_test().
+function onLoad(t) {
+ t.test.step(function() {
+ if (t.load_event_to_be_fired) {
+ assert_true(t.executed,
+ 'Load event should be fired after script execution');
+ // Delay done() a little so that if an error event happens
+ // the assert_unreached is reached and fails the test.
+ t.test.step_timeout(() => t.test.done(), 100);
+ } else {
+ assert_unreached('Load event should not be fired.');
+ }
+ });
+};
+function onError(t) {
+ t.test.step(function() {
+ if (t.error_event_to_be_fired) {
+ assert_false(t.executed);
+ // Delay done() a little so that if a load event happens
+ // the assert_unreached is reached and fails the test.
+ t.test.step_timeout(() => t.test.done(), 100);
+ } else {
+ assert_unreached('Error event should not be fired.');
+ }
+ });
+};
+
+// To be called from inline scripts, which expect no load/error events.
+function onExecute(t) {
+ t.executed = true;
+ // Delay done() a little so that if a load/error event happens
+ // the assert_unreached is reached and fails the test.
+ t.test.step_timeout(() => t.test.done(), 100);
+}
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/load-error-events.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/load-error-events.py
new file mode 100644
index 0000000000..1eb82cd497
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/load-error-events.py
@@ -0,0 +1,15 @@
+import re
+
+def main(request, response):
+ headers = [(b"Content-Type", b"text/javascript")]
+ test = request.GET.first(b'test')
+ assert(re.match(b'^[a-zA-Z0-9_]+$', test))
+
+ if test.find(b'_load') >= 0:
+ status = 200
+ content = b'"use strict"; %s.executed = true;' % test
+ else:
+ status = 404
+ content = b'"use strict"; %s.test.step(function() { assert_unreached("404 script should not be executed"); });' % test
+
+ return status, headers, content
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/promise-reject-and-remove-iframe.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/promise-reject-and-remove-iframe.html
new file mode 100644
index 0000000000..6da274469f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/promise-reject-and-remove-iframe.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+const promise = Promise.reject();
+
+window.onload = () => {
+ promise.catch(() => parent.document.querySelector('iframe').remove());
+};
+</script>
+
+<!-- Load a slow script to delay window.onload for a while.
+ Without this, crashes are flaky. -->
+<script src="/common/slow.py"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/script-type-and-language-js.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/script-type-and-language-js.js
new file mode 100644
index 0000000000..d357bc4994
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/script-type-and-language-js.js
@@ -0,0 +1,141 @@
+function testAttribute(attr, val, shouldRun) {
+ test(function() {
+ assert_false(window.ran, "ran variable not reset");
+ let script;
+ if (document.contentType === 'image/svg+xml') {
+ // SVG
+ script = document.createElementNS("http://www.w3.org/2000/svg", "script");
+ } else {
+ // HTML or XHTML
+ script = document.createElement("script");
+ }
+ script.setAttribute(attr, val);
+ script.textContent = "window.ran = true;";
+ document.querySelector('#script-placeholder').appendChild(script);
+ assert_equals(window.ran, shouldRun);
+ }, "Script should" + (shouldRun ? "" : "n't") + " run with " + attr + "=" +
+ format_value(val));
+ window.ran = false;
+}
+function testTypeShouldRun(type) {
+ testAttribute("type", type, true);
+}
+function testLanguageShouldRun(lang) {
+ testAttribute("language", lang, true);
+}
+function testTypeShouldNotRun(type) {
+ testAttribute("type", type, false);
+}
+function testLanguageShouldNotRunUnlessSVG(lang) {
+ // In SVGs, there is no concrete spec but all browsers agree that
+ // language attributes have no effects and thus script elements
+ // without type attributes are always expected to run regardless of
+ // language attributes.
+ const expectedToRun = document.contentType === 'image/svg+xml';
+ testAttribute("language", lang, expectedToRun);
+}
+
+// Unlike `test*()` methods above, there should be a (parser-inserted) script
+// with an invalid type/language that would set `window.ran` to true just
+// before `testParserInsertedDidNotRun()`, and
+// `testParserInsertedDidNotRun()` asserts that the script did not run.
+// `window.ran` should be reset where needed. For example:
+// <script>window.ran = false;</script>
+// <script type="invalid-type">window.ran = true;</script>
+// <script>testParserInsertedDidNotRun('type=invalid-type');</script>
+function testParserInsertedDidNotRun(description) {
+ test(() => assert_false(window.ran),
+ "Script shouldn't run with " + description + " (parser-inserted)");
+ window.ran = false;
+}
+
+// When prefixed by "application/", these match with
+// https://mimesniff.spec.whatwg.org/#javascript-mime-type
+const application = [
+ "ecmascript",
+ "javascript",
+ "x-ecmascript",
+ "x-javascript"
+];
+
+// When prefixed by "text/", these match with
+// https://mimesniff.spec.whatwg.org/#javascript-mime-type
+const text = [
+ "ecmascript",
+ "javascript",
+ "javascript1.0",
+ "javascript1.1",
+ "javascript1.2",
+ "javascript1.3",
+ "javascript1.4",
+ "javascript1.5",
+ "jscript",
+ "livescript",
+ "x-ecmascript",
+ "x-javascript"
+];
+
+const legacyTypes = [
+ "javascript1.6",
+ "javascript1.7",
+ "javascript1.8",
+ "javascript1.9"
+];
+
+const spaces = [" ", "\t", "\n", "\r", "\f"];
+
+window.ran = false;
+
+// Type attribute
+
+testTypeShouldRun("");
+testTypeShouldNotRun(" ");
+
+application.map(t => "application/" + t).forEach(testTypeShouldRun);
+application.map(t => ("application/" + t).toUpperCase()).forEach(
+ testTypeShouldRun);
+
+spaces.forEach(function(s) {
+ application.map(t => "application/" + t + s).forEach(testTypeShouldRun);
+ application.map(t => s + "application/" + t).forEach(testTypeShouldRun);
+});
+
+application.map(t => "application/" + t + "\0").forEach(testTypeShouldNotRun);
+application.map(t => "application/" + t + "\0foo").forEach(
+ testTypeShouldNotRun);
+
+text.map(t => "text/" + t).forEach(testTypeShouldRun);
+text.map(t => ("text/" + t).toUpperCase()).forEach(testTypeShouldRun);
+
+legacyTypes.map(t => "text/" + t).forEach(testTypeShouldNotRun);
+
+spaces.forEach(function(s) {
+ text.map(t => "text/" + t + s).forEach(testTypeShouldRun);
+ text.map(t => s + "text/" + t).forEach(testTypeShouldRun);
+});
+
+text.map(t => "text/" + t + "\0").forEach(testTypeShouldNotRun);
+text.map(t => "text/" + t + "\0foo").forEach(testTypeShouldNotRun);
+
+text.forEach(testTypeShouldNotRun);
+legacyTypes.forEach(testTypeShouldNotRun);
+
+// Language attribute
+
+testLanguageShouldRun("");
+testLanguageShouldNotRunUnlessSVG(" ");
+
+text.forEach(testLanguageShouldRun);
+text.map(t => t.toUpperCase()).forEach(testLanguageShouldRun);
+
+legacyTypes.forEach(testLanguageShouldNotRunUnlessSVG);
+
+spaces.forEach(function(s) {
+ text.map(t => t + s).forEach(testLanguageShouldNotRunUnlessSVG);
+ text.map(t => s + t).forEach(testLanguageShouldNotRunUnlessSVG);
+});
+text.map(t => t + "xyz").forEach(testLanguageShouldNotRunUnlessSVG);
+text.map(t => "xyz" + t).forEach(testLanguageShouldNotRunUnlessSVG);
+
+text.map(t => t + "\0").forEach(testLanguageShouldNotRunUnlessSVG);
+text.map(t => t + "\0foo").forEach(testLanguageShouldNotRunUnlessSVG);
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/set-script-executed.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/set-script-executed.js
new file mode 100644
index 0000000000..a6095097dd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/set-script-executed.js
@@ -0,0 +1 @@
+window.executed = true;
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/syntax-error.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/syntax-error.js
new file mode 100644
index 0000000000..40dc81dcfa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/syntax-error.js
@@ -0,0 +1 @@
+This cannot be parsed as JavaScript
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/throw.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/throw.js
new file mode 100644
index 0000000000..be53dd1ef3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/resources/throw.js
@@ -0,0 +1 @@
+document.querySelector(":::not-going-to-be-valid");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-charset-01.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-charset-01.html
new file mode 100644
index 0000000000..c5ac0d0a62
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-charset-01.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+ <title>Script @type: unknown parameters</title>
+ <link rel="author" title="askalski" href="github.com/askalski">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#scriptingLanguages">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <div id="log"></div>
+
+ <!-- "Step1" tests -->
+ <!-- charset is set incorrectly via Content Type "text/javascript;charset=utf-8" in response
+ which has priority before a correct setting in "charset" attribute of script tag.
+ -->
+ <script type="text/javascript"
+ src="serve-with-content-type.py?fn=external-script-windows1250.js&ct=text/javascript%3Bcharset=utf-8" charset="windows-1250">
+ </script>
+ <script>
+ test(function() {
+ //these strings should not match, since the file charset is set incorrectly
+ assert_not_equals(window.getSomeString(), "śćążź");
+ });
+ </script>
+ <!-- charset is set correctly via Content Type "text/javascript;charset=utf-8" in response
+ which has priority before a incorrect setting in "charset" attribute of script tag.
+ -->
+
+ <script type="text/javascript"
+ src="serve-with-content-type.py?fn=external-script-windows1250.js&ct=text/javascript%3Bcharset=windows-1250" charset="utf-8">
+ </script>
+ <script>
+ //the charset is set correctly via Content Type "text/javascript;charset=windows-1250" in respones
+ test(function() {
+ assert_equals(window.getSomeString(), "śćążź");
+ });
+ </script>
+
+ <!-- end of step1 tests, now step2 tests -->
+ <!-- in this case, the response's Content Type does not bring charset information.
+ Second step takes block character encoding if available.-->
+ <script type="text/javascript"
+ src="serve-with-content-type.py?fn=external-script-windows1250.js&ct=text/javascript" charset="utf-8">
+ </script>
+ <script>
+ test(function() {
+ //these strings should not match, since the file charset is set incorrectly in "charset" tag of <script> above
+ assert_not_equals(window.getSomeString(), "śćążź");
+ });
+ </script>
+ <!-- charset is set correctly via Content Type "text/javascript;charset=utf-8" in response
+ which has priority before a incorrect setting in "charset" attribute of script tag.
+ -->
+
+ <script type="text/javascript"
+ src="serve-with-content-type.py?fn=external-script-windows1250.js&ct=text/javascript" charset="windows-1250">
+ </script>
+ <script>
+ //the charset is set correctly via content attribute in <script> above
+ test(function() {
+ assert_equals(window.getSomeString(), "śćążź");
+ });
+ </script>
+
+ <!-- end of step2 tests, now step3 tests -->
+ <!-- in this case, neither response's Content Type nor charset attribute bring correct charset information.
+ Third step takes this document's character encoding (declared correctly as UTF-8).-->
+ <script type="text/javascript"
+ src="serve-with-content-type.py?fn=external-script-windows1250.js&ct=text/javascript">
+ </script>
+ <script>
+ test(function() {
+ //these strings should not match, since the tested file is in windows-1250, and document is utf-8
+ assert_not_equals(window.getSomeString(), "śćążź");
+ });
+ </script>
+
+ <script type="text/javascript"
+ src="serve-with-content-type.py?fn=external-script-utf8.js&ct=text/javascript">
+ </script>
+ <script>
+ //these strings should match, both document and tested file are utf-8
+ test(function() {
+ assert_equals(window.getSomeString(), "śćążź");
+ });
+ </script>
+
+ <!-- the last portion of tests (step4) are in file script-charset-02.html
+
+</head>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-charset-02.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-charset-02.html
new file mode 100644
index 0000000000..63cbe838e0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-charset-02.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<head>
+ <title>Script encoding for document encoding windows-1250</title>
+ <link rel="author" title="askalski" href="github.com/askalski">
+ <link rel="author" title="Aaqa Ishtyaq" href="github.com/aaqaishtyaq">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <div id="log"></div>
+ <!-- to avoid conflating tests for script encoding declaring the encoding at the top of file. i.e, windows-1250-->
+ <meta charset="windows-1250">
+ <script>
+ test(function() {
+ assert_equals(document.characterSet, "windows-1250")
+ }, "assumption: document encoding is windows-1250");
+ </script>
+
+ <!-- in this case, neither response's Content Type nor charset attribute bring correct charset information.
+ -->
+ <script type="text/javascript"
+ src="serve-with-content-type.py?fn=external-script-windows1250.js&ct=text/javascript">
+ </script>
+
+ <script>
+ test(function() {
+ //these string should match since, windows-1250 is the fallback encoding.
+ assert_equals(window.getSomeString(), "\u015b\u0107\u0105\u017c\u017a");
+ }, "windows-1250 script decoded using document encoding (also windows-1250)");
+ </script>
+
+ <script type="text/javascript"
+ src="serve-with-content-type.py?fn=external-script-utf8.js&ct=text/javascript">
+ </script>
+ <script>
+ //these strings should match, since this string is the result of decoding the utf-8 text as windows-1250.
+ test(function() {
+ assert_equals(window.getSomeString(), "\u0139\u203a\xc4\u2021\xc4\u2026\u0139\u013d\u0139\u015f");
+ }, "UTF-8 script decoded using document encoding (windows-1250)");
+ </script>
+
+</head>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-charset-03.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-charset-03.html
new file mode 100644
index 0000000000..4ff4cc6b0b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-charset-03.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+<title>Script changing @charset</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#scriptingLanguages">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(function() {
+ var s = document.createElement("script");
+ s.src = "external-script-windows1250.js";
+ s.charset = "windows-1250";
+ document.body.appendChild(s);
+ s.charset = "utf-8";
+ window.onload = this.step_func_done(function() {
+ assert_equals(window.getSomeString(), "\u015b\u0107\u0105\u017c\u017a");
+ });
+})
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-crossorigin-network.sub.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-crossorigin-network.sub.html
new file mode 100644
index 0000000000..5886ab6f32
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-crossorigin-network.sub.html
@@ -0,0 +1,120 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>HTMLScriptElement: crossorigin attribute network test</title>
+<link rel="author" title="KiChjang" href="mailto:kungfukeith11@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#cors-settings-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+ <script type="text/javascript">
+ var test1 = async_test("same-origin use-credentials");
+ var test2 = async_test("same-origin invalid");
+ var test3 = async_test("same-origin missing");
+ var test4 = async_test("cross-origin use-credentials");
+ var test5 = async_test("cross-origin invalid");
+ var test6 = async_test("cross-origin missing");
+ var test7 = async_test("cross-origin use-credentials mixed case");
+ var test8 = async_test("cross-origin use-credentials non-ASCII");
+
+ var same = "resources/cross-origin.py";
+ var cross = new URL(same, location);
+ cross.port = {{ports[http][1]}};
+
+ var script1 = document.createElement("script");
+ script1.src = same;
+ script1.crossOrigin = "use-credentials";
+
+ var script2 = document.createElement("script");
+ script2.src = same;
+ script2.crossOrigin = "gibberish";
+
+ var script3 = document.createElement("script");
+ script3.src = same;
+
+ var script4 = document.createElement("script");
+ script4.src = cross;
+ script4.crossOrigin = "use-credentials";
+
+ var script5 = document.createElement("script");
+ script5.src = cross;
+ script5.crossOrigin = "gibberish";
+
+ var script6 = document.createElement("script");
+ script6.src = cross;
+
+ var script7 = document.createElement("script");
+ script7.src = cross;
+ script7.crossOrigin = "UsE-cReDenTiAlS";
+
+ var script8 = document.createElement("script");
+ script8.src = cross;
+ script8.crossOrigin = "uſe-credentialſ";
+
+ document.cookie = "milk=yes";
+
+ document.body.appendChild(script1);
+ script1.onload = function() {
+ test1.step(function() {
+ assert_true(included, "credentials included (credentialsMode include)");
+ test1.done();
+ });
+ };
+
+ document.body.appendChild(script2);
+ script2.onload = function() {
+ test2.step(function() {
+ assert_true(included, "credentials included (credentialsMode same-origin)");
+ test2.done();
+ });
+ };
+
+ document.body.appendChild(script3);
+ script3.onload = function() {
+ test3.step(function() {
+ assert_true(included, "credentials included (credentialsMode include)");
+ test3.done();
+ });
+ };
+
+ document.body.appendChild(script4);
+ script4.onload = function() {
+ test4.step(function() {
+ assert_true(included, "credentials included (credentialsMode include)");
+ test4.done();
+ });
+ };
+
+ document.body.appendChild(script5);
+ script5.onload = function() {
+ test5.step(function() {
+ assert_false(included, "credentials excluded (credentialsMode same-origin)");
+ test5.done();
+ });
+ };
+
+ document.body.appendChild(script6);
+ script6.onload = function() {
+ test6.step(function() {
+ assert_true(included, "credentials included (credentialsMode include)");
+ test6.done();
+ });
+ };
+
+ document.body.appendChild(script7);
+ script7.onload = function() {
+ test7.step(function() {
+ assert_true(included, "credentials included (credentialsMode include)");
+ test7.done();
+ });
+ };
+
+ document.body.appendChild(script8);
+ script8.onload = function() {
+ test8.step(function() {
+ assert_false(included, "credentials excluded (credentialsMode same-origin)");
+ test8.done();
+ });
+ };
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-crossorigin.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-crossorigin.html
new file mode 100644
index 0000000000..52857a08ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-crossorigin.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>HTMLScriptElement: crossOrigin IDL attribute</title>
+<link rel="author" title="KiChjang" href="mailto:kungfukeith11@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#cors-settings-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script id="script1"></script>
+<script id="script2" crossorigin=""></script>
+<script id="script3" crossorigin="foo"></script>
+<script id="script4" crossorigin="anonymous"></script>
+<script id="script5" crossorigin="use-credentials"></script>
+<script>
+test(function() {
+ var script1 = document.getElementById("script1");
+ var script2 = document.getElementById("script2");
+ var script3 = document.getElementById("script3");
+ var script4 = document.getElementById("script4");
+ var script5 = document.getElementById("script5");
+
+ assert_equals(script1.crossOrigin, null, "Missing value default should be null");
+ assert_equals(script2.crossOrigin, "anonymous", "Empty string should map to anonymous");
+ assert_equals(script3.crossOrigin, "anonymous", "Invalid value default should be anonymous");
+ assert_equals(script4.crossOrigin, "anonymous", "anonymous should be parsed correctly");
+ assert_equals(script5.crossOrigin, "use-credentials", "use-credentials should be parsed correctly");
+
+ script1.crossOrigin = "bar";
+ assert_equals(script1.crossOrigin, "anonymous", "Setting to invalid value would default to anonymous");
+
+ script2.crossOrigin = null;
+ assert_equals(script2.crossOrigin, null, "Resetting to null should work");
+
+ script4.crossOrigin = "use-credentials";
+ assert_equals(script4.crossOrigin, "use-credentials", "Switching from anonymous to use-credentials should work");
+
+ script5.crossOrigin = "anonymous";
+ assert_equals(script5.crossOrigin, "anonymous", "Switching from use-credentials to anonymous should work");
+}, document.title);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-defer-xhtml.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-defer-xhtml.xhtml
new file mode 100644
index 0000000000..3f4a50f779
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-defer-xhtml.xhtml
@@ -0,0 +1,31 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>XHTML Test: HTMLScriptElement - defer</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<meta charset="utf-8" />
+</head>
+<body>
+<div id="log"></div>
+
+<script>
+
+let script_run_status = "inline";
+let t = async_test("the defer script run later");
+
+</script>
+
+<script type="text/javascript" src="defer.js" defer="defer"></script>
+
+<script>
+
+t.step(() => {
+ assert_equals(script_run_status, "inline", "the script run status");
+ script_run_status = "deferred";
+});
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-defer.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-defer.html
new file mode 100644
index 0000000000..80eb98dc75
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-defer.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: HTMLScriptElement - defer</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>
+
+<script>
+
+let script_run_status = "inline";
+let t = async_test("the defer script run later");
+
+</script>
+
+<script type="text/javascript" src="defer.js" defer></script>
+
+<script>
+
+t.step(() => {
+ assert_equals(script_run_status, "inline", "the script run status");
+ script_run_status = "deferred";
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-for-event-xhtml.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-for-event-xhtml.xhtml
new file mode 100644
index 0000000000..69c4ef1f81
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-for-event-xhtml.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Scripts with for and event attributes</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id="log"></div>
+ <script>
+ var run = false;
+ </script>
+ <script for="window" event="bar">
+ // This script should not run, but should not cause a parse error either.
+ run = true;
+ </script>
+ <script>
+ test(function() {
+ assert_false(run, "Script was unexpectedly run.")
+ }, "Scripts with for and event attributes should not run.")
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-for-event.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-for-event.html
new file mode 100644
index 0000000000..552ea7041a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-for-event.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<title>Scripts with for and event attributes</title>
+<link rel="author" title="Matheus Kerschbaum" href="mailto:matjk7@gmail.com">
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var expected = [
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ true,
+ true,
+ false,
+ true,
+ true,
+];
+var run = expected.map(function() { return false });
+</script>
+<script for="w&#x130;ndow" event="onload">
+run[0] = true;
+</script>
+<script for="window" event="onload x">
+run[1] = true;
+</script>
+<script for="window" event="onload(x">
+run[2] = true;
+</script>
+<script for="window" event="onload(x)">
+run[3] = true;
+</script>
+<script for="window" event="onclick">
+run[4] = true;
+</script>
+<script for="" event="onload">
+run[5] = true;
+</script>
+<script for="window" event="">
+run[6] = true;
+</script>
+<script for="" event="">
+run[7] = true;
+</script>
+<script for="&#xa0;window" event="onload">
+run[8] = true;
+</script>
+<script for="window&#xa0;" event="onload">
+run[9] = true;
+</script>
+<script for="window" event="&#xa0;onload">
+run[10] = true;
+</script>
+<script for="window" event="onload&#xa0;">
+run[11] = true;
+</script>
+<script for=" window " event=" onload ">
+run[12] = true;
+</script>
+<script for=" window " event=" onload() ">
+run[13] = true;
+</script>
+<script for="object" event="handler">
+run[14] = true;
+</script>
+<script event="handler">
+run[15] = true;
+</script>
+<script for="object">
+run[16] = true;
+</script>
+<script>
+test(function() {
+ for (var i = 0; i < run.length; ++i) {
+ test(function() {
+ var script = document.querySelectorAll("script[for], script[event]")[i];
+ assert_equals(run[i], expected[i],
+ "script for=" + format_value(script.getAttribute("for")) +
+ " event=" + format_value(script.getAttribute("event")));
+ }, "Script " + i);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-noembed-noframes-iframe.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-noembed-noframes-iframe.xhtml
new file mode 100644
index 0000000000..8dd9ceb9a6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-noembed-noframes-iframe.xhtml
@@ -0,0 +1,36 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Script inside noembed, noframes and iframe</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+var run = [];
+</script>
+<div id="test">
+<noembed>
+<script>
+run.push("noembed");
+</script>
+</noembed>
+<noframes>
+<script>
+run.push("noframes");
+</script>
+</noframes>
+<iframe>
+<script>
+run.push("iframe");
+</script>
+</iframe>
+</div>
+<script>
+test(function() {
+ assert_array_equals(run, ["noembed", "noframes", "iframe"], "Haven't run.");
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-executed-after-shutdown-child.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-executed-after-shutdown-child.html
new file mode 100644
index 0000000000..2f3ce2368d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-executed-after-shutdown-child.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Script is not executed after script thread is shutdown</title>
+<script>
+onload = function() {
+ script_executed = parent.script_executed;
+ s = document.createElement('script');
+ s.type = 'text/javascript';
+ s.src = 'script-not-executed-after-shutdown.js?pipe=trickle(d3)';
+ document.body.appendChild(s);
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-executed-after-shutdown.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-executed-after-shutdown.html
new file mode 100644
index 0000000000..eb4def3ec4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-executed-after-shutdown.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Script is not executed after script thread is shutdown</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="testiframe" src="script-not-executed-after-shutdown-child.html"></iframe>
+<script>
+async_test(function(t) {
+ window.script_executed = t.unreached_func("script executed in removed iframe");
+ let iframe = document.getElementById("testiframe");
+ iframe.onload = function() {
+ iframe.parentNode.removeChild(iframe);
+ };
+ setTimeout(function() {
+ t.done();
+ }, 5000);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-executed-after-shutdown.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-executed-after-shutdown.js
new file mode 100644
index 0000000000..ccdf14c0cd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-executed-after-shutdown.js
@@ -0,0 +1 @@
+script_executed();
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-found-not-executed-2.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-found-not-executed-2.py
new file mode 100644
index 0000000000..4ff5be7374
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-found-not-executed-2.py
@@ -0,0 +1,4 @@
+def main(request, response):
+ headers = [(b"Content-Type", b"text/javascript")]
+ body = u"test2_token = \"script executed\";"
+ return 200, headers, body
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-found-not-executed.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-found-not-executed.html
new file mode 100644
index 0000000000..44ad30b018
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-found-not-executed.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset="utf-8">
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>var test1_token = "script not executed";</script>
+<script src="script-not-found-not-executed.py"></script>
+<script>
+test(function(){
+ assert_equals(test1_token, "script not executed");
+}, "Script that 404");
+</script>
+<script>var test2_token = "script not executed";</script>
+<script src="script-not-found-not-executed-2.py"></script>
+<script>
+test(function(){
+ assert_equals(test2_token, "script executed");
+}, "Script that does not 404");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-found-not-executed.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-found-not-executed.py
new file mode 100644
index 0000000000..9354d42703
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-not-found-not-executed.py
@@ -0,0 +1,4 @@
+def main(request, response):
+ headers = [(b"Content-Type", b"text/javascript")]
+ body = u"test1_token = \"script executed\";"
+ return 404, headers, body
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onerror-insertion-point-1.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onerror-insertion-point-1.html
new file mode 100644
index 0000000000..0fe39b11a8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onerror-insertion-point-1.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test that the insertion point is defined in the error event of a parser-inserted script that actually started a fetch (but just had it fail).</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ var t = async_test("");
+ var writeDone = t.step_func_done(function(text) {
+ assert_equals(text, "Some text");
+ });
+</script>
+<iframe src="support/script-onerror-insertion-point-1-helper.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onerror-insertion-point-2.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onerror-insertion-point-2.html
new file mode 100644
index 0000000000..6d3f3ef09e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onerror-insertion-point-2.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test that the insertion point is not defined in the error event of a
+ parser-inserted script that has an unparseable URL</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ var t = async_test("");
+ var writeDone = t.step_func_done(function(text) {
+ assert_equals(text, "text");
+ });
+</script>
+<iframe src="support/script-onerror-insertion-point-2-helper.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onload-insertion-point.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onload-insertion-point.html
new file mode 100644
index 0000000000..ce3ddeee65
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onload-insertion-point.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test that the insertion point is defined in the load event of a parser-inserted script.</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ var t = async_test("");
+ var writeDone = t.step_func_done(function(text) {
+ assert_equals(text, "Some text");
+ });
+</script>
+<iframe src="support/script-onload-insertion-point-helper.html"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onload-string.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onload-string.html
new file mode 100644
index 0000000000..85f2d4dcfa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-onload-string.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Script: setting onload to a string</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>
+<script>
+test(function() {
+ var s = document.createElement("script");
+ assert_equals(s.onload, null);
+ var dummy = function() {};
+ s.onload = dummy;
+ assert_equals(s.onload, dummy);
+ s.onload = "w('load DOM appended')";
+ assert_equals(s.onload, null);
+}, "Setting onload to a string should convert to null.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-referrerpolicy-idl.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-referrerpolicy-idl.html
new file mode 100644
index 0000000000..bf01cb83b8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-referrerpolicy-idl.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>&lt;script> referrerPolicy IDL</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#referrer-policy-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+ test(() => {
+ const script = document.createElement('script');
+ document.body.appendChild(script);
+ assert_equals(script.referrerPolicy,"",'Missing content attribute should reflect as empty');
+ script.setAttribute('referrerpolicy','no-referrer');
+ assert_equals(script.referrerPolicy,"no-referrer",'Valid value should reflect');
+ script.setAttribute('referrerpolicy','');
+ assert_equals(script.referrerPolicy,"",'Empty string should reflect as empty');
+ script.setAttribute('referrerpolicy','invalid-value-here');
+ assert_equals(script.referrerPolicy,"",'Invalid values should reflect as empty');
+ script.referrerPolicy = 'no-referrer';
+ assert_equals(script.referrerPolicy,"no-referrer",'Valid value via IDL');
+ script.referrerPolicy = null;
+ assert_equals(script.referrerPolicy,"",'Null should reflect as empty');
+ },'Missing/invalid/null referrerPolicy should reflect as the empty string')
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-supports.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-supports.html
new file mode 100644
index 0000000000..495056fce9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-supports.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLScriptElement.supports</title>
+<link rel=help href="https://html.spec.whatwg.org/#dom-script-supports">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+ assert_equals(typeof HTMLScriptElement.supports, 'function');
+}, 'Type of HTMLScriptElement.supports is function');
+
+test(function() {
+ assert_true(HTMLScriptElement.supports('classic'));
+}, 'HTMLScriptElement.supports resurns true for \'classic\'');
+
+test(function() {
+ assert_true(HTMLScriptElement.supports('module'));
+}, 'HTMLScriptElement.supports resurns true for \'module\'');
+
+test(function() {
+ assert_false(HTMLScriptElement.supports('application/ecmascript'));
+ assert_false(HTMLScriptElement.supports('application/javascript'));
+ assert_false(HTMLScriptElement.supports('application/x-ecmascript'));
+ assert_false(HTMLScriptElement.supports('application/x-javascript'));
+ assert_false(HTMLScriptElement.supports('text/ecmascript'));
+ assert_false(HTMLScriptElement.supports('text/javascript'));
+ assert_false(HTMLScriptElement.supports('text/javascript1.0'));
+ assert_false(HTMLScriptElement.supports('text/javascript1.1'));
+ assert_false(HTMLScriptElement.supports('text/javascript1.2'));
+ assert_false(HTMLScriptElement.supports('text/javascript1.3'));
+ assert_false(HTMLScriptElement.supports('text/javascript1.4'));
+ assert_false(HTMLScriptElement.supports('text/javascript1.5'));
+ assert_false(HTMLScriptElement.supports('text/jscript'));
+ assert_false(HTMLScriptElement.supports('text/livescript'));
+ assert_false(HTMLScriptElement.supports('text/x-ecmascript'));
+ assert_false(HTMLScriptElement.supports('text/x-javascript'));
+}, 'HTMLScriptElement.supports returns false for JavaScript MIME types');
+
+test(function() {
+ assert_false(HTMLScriptElement.supports(''));
+ assert_false(HTMLScriptElement.supports(' '));
+ assert_false(HTMLScriptElement.supports('classic '));
+ assert_false(HTMLScriptElement.supports('module '));
+ assert_false(HTMLScriptElement.supports(' classic '));
+ assert_false(HTMLScriptElement.supports(' module '));
+ assert_false(HTMLScriptElement.supports('classics'));
+ assert_false(HTMLScriptElement.supports('modules'));
+ assert_false(HTMLScriptElement.supports('Classic'));
+ assert_false(HTMLScriptElement.supports('Module'));
+ assert_false(HTMLScriptElement.supports('unsupported'));
+}, 'HTMLScriptElement.supports returns false for unsupported types');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text-modifications-csp.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text-modifications-csp.html
new file mode 100644
index 0000000000..a991151066
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text-modifications-csp.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<head>
+<meta charset=utf-8>
+<title>Modify HTMLScriptElement's text after #prepare-a-script that violates CSP</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<meta http-equiv="content-security-policy" content="script-src
+ 'nonce-allow'
+ 'sha256-2+5xh6b9uuIi4GaJtmHWtgR2nwRXJpBtMY4nVaOBpfc='
+">
+<!-- The hash is that of the original content of `script0`. -->
+
+<script nonce="allow">
+window.t = async_test("Modify inline script element's text " +
+ "after prepare-a-script before evaluation (CSP)");
+
+const updatedText =
+ 't.unreached_func("CSP check was done against the original text but the updated text was evaluated")();';
+
+function changeScriptText() {
+ document.querySelector('#script0').textContent = updatedText;
+}
+
+t.step_timeout(changeScriptText, 500);
+</script>
+
+<!-- This is "a style sheet that is blocking scripts" and thus ... -->
+<link rel="stylesheet" href="/common/slow.py?pipe=trickle(d1)"></link>
+
+<!-- This inline script becomes a parser-blocking script, and thus
+the step_timeout is evaluated after script0 is inserted into DOM,
+prepare-a-script'ed, but before its evaluation. -->
+<script id="script0">
+t.step(() => {
+ // When this is evaluated after the stylesheet is loaded,
+ // script0's textContent is modified by the async script above,
+ // but the evaluated script is still the original script here,
+ // not what is overwritten, because "child text content" is taken in
+ // #prepare-a-script and passed to "creating a classic script".
+ var s = document.getElementById('script0');
+ assert_equals(s.textContent, updatedText,
+ "<script>'s textContent should be already modified");
+ t.done();
+ });
+</script>
+<script nonce="allow">
+// If this makes the test fail, it indicates `script0` (the original or updated
+// text) was not evaluated, probably blocked by CSP that was checked against the
+// updated text.
+t.unreached_func("CSP check was done against the updated text")();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text-modifications.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text-modifications.html
new file mode 100644
index 0000000000..cb54da6995
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text-modifications.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<head>
+<meta charset=utf-8>
+<title>Modify HTMLScriptElement's text after #prepare-a-script</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+var t = async_test("Modify inline script element's text " +
+ "after prepare-a-script before evaluation");
+
+function changeScriptText() {
+ document.querySelector('#script0').textContent =
+ 't.unreached_func("This should not be evaluated")();';
+}
+
+t.step_timeout(changeScriptText, 500);
+</script>
+
+<!-- This is "a style sheet that is blocking scripts" and thus ... -->
+<link rel="stylesheet" href="/common/slow.py?pipe=trickle(d1)"></link>
+
+<!-- This inline script becomes a parser-blocking script, and thus
+the step_timeout is evaluated after script0 is inserted into DOM,
+prepare-a-script'ed, but before its evaluation. -->
+<script id="script0">
+t.step(() => {
+ // When this is evaluated after the stylesheet is loaded,
+ // script0's textContent is modified by the async script above,
+ // but the evaluated script is still the original script here,
+ // not what is overwritten, because "child text content" is taken in
+ // #prepare-a-script and passed to "creating a classic script".
+ var s = document.getElementById('script0');
+ assert_equals(s.textContent,
+ 't.unreached_func("This should not be evaluated")();',
+ "<script>'s textContent should be already modified");
+ t.done();
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text-xhtml.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text-xhtml.xhtml
new file mode 100644
index 0000000000..33a4635db3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text-xhtml.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>HTMLScriptElement.text</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-script-text"/>
+<script src="/resources/testharness.js"/>
+<script src="/resources/testharnessreport.js"/>
+</head>
+<body>
+<div id="log"></div>
+<script>
+<x>7;</x>
+<![CDATA[
+ var x = "y";
+]]>
+</script>
+<script>
+var script;
+setup(function() {
+ script = document.body.getElementsByTagName("script")[0];
+})
+test(function() {
+ assert_equals(script.text, '\n\n\n var x = "y";\n\n')
+ assert_equals(script.textContent, '\n7;\n\n var x = "y";\n\n')
+}, "Getter with CDATA section")
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text.html
new file mode 100644
index 0000000000..6e86472246
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-text.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLScriptElement.text</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#dom-script-text">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var script;
+setup(function() {
+ script = document.createElement("script")
+ script.appendChild(document.createComment("COMMENT"))
+ script.appendChild(document.createTextNode(" TEXT "))
+ script.appendChild(document.createProcessingInstruction("P", "I"))
+ script.appendChild(document.createElement("a"))
+ .appendChild(document.createTextNode("ELEMENT"))
+})
+
+test(function() {
+ assert_equals(script.text, " TEXT ")
+ assert_equals(script.textContent, " TEXT ELEMENT")
+}, "Getter")
+
+test(function() {
+ script.text = " text "
+ assert_equals(script.text, " text ")
+ assert_equals(script.textContent, " text ")
+ assert_equals(script.firstChild.nodeType, Node.TEXT_NODE)
+ assert_equals(script.firstChild.data, " text ")
+ assert_equals(script.firstChild, script.lastChild)
+ assert_array_equals(script.childNodes, [script.firstChild])
+}, "Setter (non-empty string)")
+
+test(function() {
+ script.text = ""
+ assert_equals(script.text, "")
+ assert_equals(script.textContent, "")
+ assert_equals(script.firstChild, null)
+}, "Setter (empty string)")
+
+test(function() {
+ script.text = null
+ assert_equals(script.text, "null")
+ assert_equals(script.textContent, "null")
+ assert_equals(script.firstChild.nodeType, Node.TEXT_NODE)
+ assert_equals(script.firstChild.data, "null")
+ assert_equals(script.firstChild, script.lastChild)
+}, "Setter (null)")
+
+test(function() {
+ script.text = undefined
+ assert_equals(script.text, "undefined")
+ assert_equals(script.textContent, "undefined")
+ assert_equals(script.firstChild.nodeType, Node.TEXT_NODE)
+ assert_equals(script.firstChild.data, "undefined")
+ assert_equals(script.firstChild, script.lastChild)
+}, "Setter (undefined)")
+
+test(function() {
+ var s = document.createElement("script");
+ var text = document.createTextNode("one");
+ s.appendChild(text);
+
+ assert_equals(s.firstChild, text);
+ assert_equals(text.nodeValue, "one");
+
+ s.text = "two";
+ assert_not_equals(s.firstChild, text);
+ assert_equals(text.nodeValue, "one");
+ assert_equals(s.firstChild.nodeValue, "two");
+}, "Setter (text node reuse)")
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-empty.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-empty.html
new file mode 100644
index 0000000000..6ce1b279f7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-empty.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<title>Script @type and @language: empty strings</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- Setup -->
+<script>
+window.run1 = window.run2 = window.run3 = window.run4 = false;
+</script>
+
+<!-- Systems under test -->
+<script type="">
+window.run1 = true;
+</script>
+
+<script type="" language="foo">
+window.run2 = true;
+</script>
+
+<script type="" language="">
+window.run3 = true;
+</script>
+
+<script language="">
+window.run4 = true;
+</script>
+
+<!-- Asserts -->
+<script>
+test(() => {
+ assert_true(window.run1);
+}, "A script with empty type and no language should run");
+
+test(() => {
+ assert_true(window.run2);
+}, "A script with empty type and a random language should run");
+
+test(() => {
+ assert_true(window.run3);
+}, "A script with empty type and empty language should run");
+
+test(() => {
+ assert_true(window.run4);
+}, "A script with no type and empty language should run");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-js-svg.svg b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-js-svg.svg
new file mode 100644
index 0000000000..2f31d4d75c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-js-svg.svg
@@ -0,0 +1,37 @@
+<?xml version="1.0" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml">
+<metadata>
+ <h:link rel="help" href="https://html.spec.whatwg.org/multipage/#scriptingLanguages" />
+ <h:link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script" />
+</metadata>
+<h:script src="/resources/testharness.js"/>
+<h:script src="/resources/testharnessreport.js"/>
+<h:div id="script-placeholder"/>
+<h:script src="resources/script-type-and-language-js.js"/>
+<script>window.ran = false;</script>
+<script type="javascript">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript');</script>
+<script type="javascript1.0">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.0');</script>
+<script type="javascript1.1">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.1');</script>
+<script type="javascript1.2">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.2');</script>
+<script type="javascript1.3">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.3');</script>
+<script type="javascript1.4">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.4');</script>
+<script type="javascript1.5">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.5');</script>
+<script type="javascript1.6">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.6');</script>
+<script type="javascript1.7">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.7');</script>
+<script type="livescript">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=livescript');</script>
+<script type="ecmascript">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=ecmascript');</script>
+<script type="jscript">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=jscript');</script>
+</svg>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-js-xhtml.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-js-xhtml.xhtml
new file mode 100644
index 0000000000..0bfdfce3c4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-js-xhtml.xhtml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Script @type and @language: JavaScript types</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com" />
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#scriptingLanguages" />
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="script-placeholder"/>
+<script src="resources/script-type-and-language-js.js"></script>
+
+<script>ran = false;</script>
+<script type="javascript">ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript');</script>
+<script type="javascript1.0">ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.0');</script>
+<script type="javascript1.1">ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.1');</script>
+<script type="javascript1.2">ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.2');</script>
+<script type="javascript1.3">ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.3');</script>
+<script type="javascript1.4">ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.4');</script>
+<script type="javascript1.5">ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.5');</script>
+<script type="javascript1.6">ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.6');</script>
+<script type="javascript1.7">ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.7');</script>
+<script type="livescript">ran = true;</script>
+<script>testParserInsertedDidNotRun('type=livescript');</script>
+<script type="ecmascript">ran = true;</script>
+<script>testParserInsertedDidNotRun('type=ecmascript');</script>
+<script type="jscript">ran = true;</script>
+<script>testParserInsertedDidNotRun('type=jscript');</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-js.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-js.html
new file mode 100644
index 0000000000..009123de2b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-js.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Script @type and @language: JavaScript types</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#scriptingLanguages">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="script-placeholder"></div>
+<script src="resources/script-type-and-language-js.js"></script>
+
+<script>window.ran = false;</script>
+<script type="javascript">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript');</script>
+<script type="javascript1.0">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.0');</script>
+<script type="javascript1.1">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.1');</script>
+<script type="javascript1.2">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.2');</script>
+<script type="javascript1.3">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.3');</script>
+<script type="javascript1.4">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.4');</script>
+<script type="javascript1.5">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.5');</script>
+<script type="javascript1.6">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.6');</script>
+<script type="javascript1.7">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=javascript1.7');</script>
+<script type="livescript">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=livescript');</script>
+<script type="ecmascript">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=ecmascript');</script>
+<script type="jscript">window.ran = true;</script>
+<script>testParserInsertedDidNotRun('type=jscript');</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-with-params.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-with-params.html
new file mode 100644
index 0000000000..977ee7d0a4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-and-language-with-params.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>Script @type and @language: unknown type parameters</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#scriptingLanguages">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- Setup -->
+<script>
+window.run1 = window.run2 = window.run3 = false;
+</script>
+
+<!-- Systems under test -->
+<script type="text/javascript;charset=UTF-8">
+window.run1 = true;
+</script>
+
+<script type="text/javascript;x-test=abc">
+window.run2 = true;
+</script>
+
+<script language="javascript" type="text/javascript;charset=UTF-8">
+window.run3 = true;
+</script>
+
+<!-- Asserts -->
+<script>
+test(() => {
+ assert_false(window.run1);
+}, "A script with a charset param in its type should not run");
+
+test(() => {
+ assert_false(window.run2);
+}, "A script with an x-test param in its type should not run");
+
+test(() => {
+ assert_false(window.run3);
+}, "A script with a charset param in its type should not run, even with language=javascript");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-whitespace.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-whitespace.html
new file mode 100644
index 0000000000..5e8acb1a17
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/script-type-whitespace.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<title>&lt;script type> non-ASCII whitespace handling</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+function testParserInsertedDidNotRun(description) {
+ test(() => assert_false(window.ran),
+ "Script shouldn't run with " + description + " (parser-inserted)");
+ window.ran = false;
+}
+</script>
+
+<script>window.ran = false;</script>
+<script type="text/javascript&#x000B;">window.ran = true;</script>
+<script>testParserInsertedDidNotRun("type=\"text/javascript&#x000B;\"");</script>
+
+<script type="text/javascript&#x0085;">window.ran = true;</script>
+<script>testParserInsertedDidNotRun("type=\"text/javascript&#x0085;\"");</script>
+
+<script type="text/javascript&#x00A0;">window.ran = true;</script>
+<script>testParserInsertedDidNotRun("type=\"text/javascript&#x00A0;\"");</script>
+
+<script type="text/javascript&#x1680;">window.ran = true;</script>
+<script>testParserInsertedDidNotRun("type=\"text/javascript&#x1680;\"");</script>
+
+<script type="text/javascript&#x3000;">window.ran = true;</script>
+<script>testParserInsertedDidNotRun("type=\"text/javascript&#x3000;\"");</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/scripting-enabled.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/scripting-enabled.html
new file mode 100644
index 0000000000..a2671a78f6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/scripting-enabled.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>JS is disabled on documents created without a browsing context</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#concept-n-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function(t) {
+ var doc = document.implementation.createHTMLDocument();
+ window.fail_test = t.unreached_func('should not have been called');
+
+ var script = doc.createElement('script');
+ script.textContent = 'fail_test();';
+ doc.documentElement.appendChild(script);
+}, 'script on document returned by createHTMLDocument should not execute');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/serve-json-then-js.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/serve-json-then-js.py
new file mode 100644
index 0000000000..9610734d44
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/serve-json-then-js.py
@@ -0,0 +1,21 @@
+# Respond with valid JSON to the first request with the given key,
+# and with valid JavaScript to the second. Used for testing scenarios where
+# the same request URL results in different responses on subsequent requests.
+def main(request, response):
+ try:
+ stash_key = request.GET.first(b"key")
+
+ run_count = request.server.stash.take(stash_key)
+ if not run_count:
+ run_count = 0
+
+ if run_count == 0:
+ response.headers.set(b"Content-Type", b"text/json")
+ response.content = '{"hello": "world"}'
+ else:
+ response.headers.set(b"Content-Type", b"application/javascript")
+ response.content = "export default 'hello';"
+
+ request.server.stash.put(stash_key, run_count + 1)
+ except:
+ response.set_error(400, u"Not enough parameters")
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/serve-with-content-type.py b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/serve-with-content-type.py
new file mode 100644
index 0000000000..675b3fc3eb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/serve-with-content-type.py
@@ -0,0 +1,17 @@
+import os
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ directory = os.path.dirname(isomorphic_decode(__file__))
+
+ try:
+ file_name = request.GET.first(b"fn")
+ content_type = request.GET.first(b"ct")
+ with open(os.path.join(directory, isomorphic_decode(file_name)), u"rb") as fh:
+ content = fh.read()
+
+ response.headers.set(b"Content-Type", content_type)
+ response.content = content
+ except:
+ response.set_error(400, u"Not enough parameters or file not found")
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onerror-insertion-point-1-helper.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onerror-insertion-point-1-helper.html
new file mode 100644
index 0000000000..d9b0c84ca4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onerror-insertion-point-1-helper.html
@@ -0,0 +1,2 @@
+Some <script src="nosuchscripthere.js"
+ onerror="document.write('text'); parent.writeDone(document.documentElement.textContent)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onerror-insertion-point-2-helper.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onerror-insertion-point-2-helper.html
new file mode 100644
index 0000000000..a9ee80026a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onerror-insertion-point-2-helper.html
@@ -0,0 +1,2 @@
+Some <script src="http://this is not parseable:-80/"
+ onerror="document.write('text'); document.close(); parent.writeDone(document.documentElement.textContent)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onload-insertion-point-helper.html b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onload-insertion-point-helper.html
new file mode 100644
index 0000000000..f0236b4fbb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onload-insertion-point-helper.html
@@ -0,0 +1,2 @@
+Some <script src="script-onload-insertion-point-helper.js"
+ onload="document.write('xt'); parent.writeDone(document.documentElement.textContent)"></script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onload-insertion-point-helper.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onload-insertion-point-helper.js
new file mode 100644
index 0000000000..8a96a0b783
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/support/script-onload-insertion-point-helper.js
@@ -0,0 +1 @@
+document.write("te");
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/node-document.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/node-document.html
new file mode 100644
index 0000000000..8676319b20
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/node-document.html
@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: Parsing XHTML: Node's node document</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<meta name="assert" content="Parsing XHTML: Node's node document must be set to that of the element to which it will be appended">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#parsing-xhtml-documents">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+
+
+test(function() {
+ var doc = newXHTMLDocument();
+ doc.body = doc.createElement('body');
+ doc.body.innerHTML = '<template id="tmpl"></template>';
+
+ var template = doc.querySelector('#tmpl');
+
+ assert_not_equals(template, null, 'Template element should not be null');
+ assert_not_equals(template.content, undefined,
+ 'Content attribute of template element should not be undefined');
+ assert_not_equals(template.content, null,
+ 'Content attribute of template element should not be null');
+
+ assert_equals(template.ownerDocument, doc.body.ownerDocument,
+ 'Wrong template node owner document');
+ var ownerDoc = template.content.ownerDocument;
+ assert_not_equals(ownerDoc, doc, 'Wrong template content owner document');
+ assert_not_equals(ownerDoc, document, 'Wrong template content owner document');
+ assert_equals(ownerDoc.defaultView, null,
+ 'Template content owner document should not have a browsing context');
+
+}, 'Parsing XHTML: Node\'s node document must be set to that of the element '
+ + 'to which it will be appended. Test empty template');
+
+
+
+test(function() {
+ var doc = newXHTMLDocument();
+
+ doc.body = doc.createElement('body');
+ doc.body.innerHTML = '<template id="tmpl"><div>Div content</div></template>';
+
+ var template = doc.querySelector('#tmpl');
+
+ assert_equals(template.ownerDocument, doc.body.ownerDocument,
+ 'Wrong template node owner document');
+
+ assert_not_equals(template, null, 'Template element should not be null');
+ assert_not_equals(template.content, undefined,
+ 'Content attribute of template element should not be undefined');
+ assert_not_equals(template.content, null,
+ 'Content attribute of template element should not be null');
+
+ var div = template.content.querySelector('div');
+ assert_equals(template.content.ownerDocument, div.ownerDocument,
+ 'Wrong DIV node owner document');
+
+}, 'Parsing XHTML: Node\'s node document must be set to that of the element '
+ + 'to which it will be appended. Test not empty template');
+
+
+
+test(function() {
+ var doc = newXHTMLDocument();
+ doc.body = doc.createElement('body');
+ doc.body.innerHTML = ''
+ + '<template id="tmpl"><div>Div content</div> And some more text'
+ + '<template id="tmpl2"><div>Template content</div></template>'
+ + '</template>';
+
+ var template = doc.querySelector('#tmpl');
+ assert_not_equals(template, null, 'Template element should not be null');
+ assert_equals(template.ownerDocument, doc, 'Wrong template node owner document');
+ assert_not_equals(template.content, undefined,
+ 'Content attribute of template element should not be undefined');
+ assert_not_equals(template.content, null,
+ 'Content attribute of template element should not be null');
+
+ var nestedTemplate = template.content.querySelector('#tmpl2');
+ assert_not_equals(nestedTemplate, null, 'Nested template element should not be null');
+ assert_not_equals(nestedTemplate.content, undefined,
+ 'Content attribute of nested template element should not be undefined');
+ assert_not_equals(nestedTemplate.content, null,
+ 'Content attribute of nested template element should not be null');
+
+ assert_equals(nestedTemplate.ownerDocument, template.content.ownerDocument,
+ 'Wrong nested template node owner document');
+
+
+ var div = nestedTemplate.content.querySelector('div');
+ assert_equals(nestedTemplate.content.ownerDocument, div.ownerDocument,
+ 'Wrong DIV node owner document');
+
+}, 'Parsing XHTML: Node\'s node document must be set to that of the element '
+ + 'to which it will be appended. Test nested templates');
+
+
+
+testInIFrame('../resources/template-child-nodes-div.xhtml', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template = doc.querySelector('template');
+
+ assert_equals(template.ownerDocument, doc, 'Wrong template node owner document');
+
+ assert_not_equals(template.content, undefined,
+ 'Content attribute of template element should not be undefined');
+ assert_not_equals(template.content, null,
+ 'Content attribute of template element should not be null');
+
+ var div = template.content.querySelector('div');
+ assert_equals(template.content.ownerDocument, div.ownerDocument,
+ 'Wrong DIV node owner document');
+
+}, 'Parsing XHTML: Node\'s node document must be set to that of the element '
+ + 'to which it will be appended. Test loading XHTML document from a file');
+
+
+
+testInIFrame('../resources/template-child-nodes-nested.xhtml', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template = doc.querySelector('template');
+
+ assert_equals(template.ownerDocument, doc, 'Wrong template node owner document');
+
+ var nestedTemplate = template.content.querySelector('template');
+
+ assert_equals(nestedTemplate.ownerDocument, template.content.ownerDocument,
+ 'Wrong template node owner document');
+
+ var div = nestedTemplate.content.querySelector('div');
+ assert_equals(nestedTemplate.content.ownerDocument, div.ownerDocument,
+ 'Wrong DIV node owner document');
+
+}, 'Parsing XHTML: Node\'s node document must be set to that of the element '
+ + 'to which it will be appended. Test loading of XHTML document '
+ + 'with nested templates from a file');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/tag-name.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/tag-name.xhtml
new file mode 100644
index 0000000000..a1e293d1cb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/tag-name.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>tagName in template</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<template><div></div></template>
+<div id="log"></div>
+<script><![CDATA[
+test(function() {
+ assert_equals(document.getElementsByTagName("template")[0].content.firstChild.tagName, 'div', "tagName case");
+}, "tagName case");
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/template-child-nodes.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/template-child-nodes.html
new file mode 100644
index 0000000000..40abda5684
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-parsing-xhtml-documents/template-child-nodes.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: Child nodes of template element in XHTML documents</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="author" title="Aleksei Yu. Semenov" href="a.semenov@unipro.ru">
+<meta name="assert" content="Child nodes of template element in XHTML documents are always appended to the template content (instead of template itself)">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#parsing-xhtml-documents">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+
+test(function() {
+ var doc = newXHTMLDocument();
+
+ doc.body = doc.createElement('body');
+ doc.body.innerHTML = '<template id="tmpl1">'
+ + '<div id="div1">This is div inside template</div>'
+ + '<div id="div2">This is another div inside template</div>'
+ + '</template>';
+
+ var template = doc.querySelector('#tmpl1');
+
+ assert_equals(template.childNodes.length, 0,
+ 'Wrong number of template child nodes');
+ assert_equals(template.content.childNodes.length, 2,
+ 'Wrong number of template content child nodes');
+
+}, 'Child nodes of template element in XHTML documents must be appended to template content');
+
+
+
+test(function() {
+ var doc = newXHTMLDocument();
+ doc.body = doc.createElement('body');
+ doc.body.innerHTML = '<template id="tmpl1">'
+ + '<div id="div1">This is div inside template</div>'
+ + '<div id="div2">This is another div inside template</div>'
+ + '<template id="tmpl2">'
+ + '<div id="div3">This is div inside nested template</div>'
+ + '<div id="div4">This is another div inside nested template</div>'
+ + '</template>' + '</template>';
+
+ var template = doc.querySelector('#tmpl1');
+
+ assert_equals(template.childNodes.length, 0,
+ 'Wrong number of template child nodes');
+ assert_equals(template.content.childNodes.length, 3,
+ 'Wrong number of template content child nodes');
+
+ var nestedTemplate = template.content.querySelector('#tmpl2');
+
+ assert_equals(nestedTemplate.childNodes.length, 0,
+ 'Wrong number of template child nodes');
+ assert_equals(nestedTemplate.content.childNodes.length, 2,
+ 'Wrong number of nested template content child nodes');
+
+}, 'Child nodes of nested template element in XHTML documents must be appended to template content');
+
+
+
+testInIFrame('../resources/template-child-nodes-div.xhtml', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template = doc.querySelector('template');
+
+ assert_equals(template.childNodes.length, 0,
+ 'Wrong number of template child nodes');
+ assert_equals(template.content.querySelectorAll('div').length, 2,
+ 'Wrong number of template content child nodes');
+
+}, 'Child nodes of template element in XHTML documents must be appended to template content. '
+ + 'Test loading XHTML document from a file');
+
+
+testInIFrame('../resources/template-child-nodes-nested.xhtml', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template = doc.querySelector('template');
+
+ assert_equals(template.childNodes.length, 0,
+ 'Wrong number of template child nodes');
+
+ var nestedTemplate = template.content.querySelector('template');
+
+ assert_equals(nestedTemplate.childNodes.length, 0,
+ 'Wrong number of template child nodes');
+
+ assert_equals(nestedTemplate.content.querySelectorAll('div').length, 2,
+ 'Wrong number of template content child nodes');
+
+}, 'Child nodes of nested template element in XHTML documents must be appended to template content. '
+ + 'Test loading XHTML document from a file');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-serializing-xhtml-documents/outerhtml.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-serializing-xhtml-documents/outerhtml.html
new file mode 100644
index 0000000000..416a3bc615
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-serializing-xhtml-documents/outerhtml.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: serialize template contents instead of template element</title>
+<meta name="author" title="Aleksei Yu. Semenov" href="a.semenov@unipro.ru">
+<meta name="assert" content="Template contents should be serialized instead of template element if serializing template element in XHTML document">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#serializing-xhtml-documents">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+test(function () {
+ var doc = newXHTMLDocument();
+ var template = doc.createElement('template');
+
+ var div = doc.createElement('div');
+ div.setAttribute('id', 'div1');
+ div.innerHTML = 'some text';
+ template.content.appendChild(div);
+
+ assert_equals(template.outerHTML, '<template xmlns="http://www.w3.org/1999/xhtml"><div id="div1">some text</div></template>',
+ 'template element is serialized incorrectly');
+
+}, 'Template contents should be serialized instead of template element if serializing template element');
+
+
+
+test(function () {
+ var doc = newXHTMLDocument();
+ var template = doc.createElement('template');
+ var nestedTemplate = doc.createElement('template');
+
+ template.content.appendChild(nestedTemplate);
+
+ var div = doc.createElement('div');
+ div.setAttribute('id', 'div1');
+ div.innerHTML = 'some text';
+ nestedTemplate.content.appendChild(div);
+
+ assert_equals(template.outerHTML, '<template xmlns="http://www.w3.org/1999/xhtml"><template><div id="div1">some text</div></template></template>',
+ 'template element is serialized incorrectly');
+
+
+}, 'Template contents should be serialized instead of template element if serializing template element. '
+ + 'Test nested template');
+
+
+test(function () {
+ var doc = newXHTMLDocument();
+ var template = doc.createElement('template');
+
+ var div = doc.createElement('div');
+ div.setAttribute('id', 'div1');
+ div.innerHTML = 'some text';
+ template.content.appendChild(div);
+ doc.body = doc.createElement('body');
+ doc.body.appendChild(template);
+
+ assert_equals(doc.documentElement.outerHTML, '<html xmlns="http://www.w3.org/1999/xhtml"><body><template><div id="div1">some text</div></template></body></html>',
+ 'template element is serialized incorrectly');
+
+}, 'Template contents should be serialized instead of template element if serializing template element. '
+ + 'Test serializing whole document');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-001-ref.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-001-ref.html
new file mode 100644
index 0000000000..55c8b2e30c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-001-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<title>Template Reftest Reference</title>
+<link rel="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru"/>
+<body>
+ <p>Test passes if there's no anything below this line.</p>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-001.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-001.html
new file mode 100644
index 0000000000..fc310f47c8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-001.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+ <title>Template Test: check that template content is invisible by default</title>
+ <link rel="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+ <link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#css-additions">
+ <meta name="assert" content="Test checks that the template contents are hidden implicitly">
+ <link rel="match" href="css-user-agent-style-sheet-test-001-ref.html">
+<body>
+ <p>Test passes if there's no anything below this line.</p>
+ <template>
+ <span style="color:red">Test fails if you can see this text</span>
+ </template>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-002.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-002.html
new file mode 100644
index 0000000000..92f3d81eac
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-002.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+ <title>Template Test: check that template content is invisible by default</title>
+ <link rel="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+ <link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#css-additions">
+ <meta name="assert" content="The template element itself must be hidden by default">
+ <link rel="match" href="css-user-agent-style-sheet-test-001-ref.html">
+<body>
+ <p>Test passes if there's no anything below this line.</p>
+ <template style="border: 1px solid; width: 100px; height: 100px">
+ <span style="color:red">Test fails if you can see this text or border around it</span>
+ </template>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-003.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-003.html
new file mode 100644
index 0000000000..4c477fde79
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-css-user-agent-style-sheet/css-user-agent-style-sheet-test-003.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+ <title>HTML Templates: template content is invisible by default</title>
+ <link rel="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+ <link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#css-additions">
+ <meta name="assert" content="The template element itself must be hidden by default">
+ <link rel="match" href="css-user-agent-style-sheet-test-001-ref.html">
+ <style>
+ template {
+ border: 1px solid;
+ width: 100px;
+ height: 100px;
+ }
+ </style>
+<body>
+ <p>Test passes if there's no anything below this line.</p>
+ <template>
+ <span style="color:red">Test fails if you can see this text or border around it</span>
+ </template>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-steps-to-clone-a-node/template-clone-children.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-steps-to-clone-a-node/template-clone-children.html
new file mode 100644
index 0000000000..c668d90953
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-steps-to-clone-a-node/template-clone-children.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: Clone template node: All the children of template content are copied if 'copy children flag' set to true</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="assert" content="Clone template node: all the children of template content are copied if 'copy children flag' set to true">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#node-clone-additions">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+test(function () {
+ var doc = newHTMLDocument();
+ doc.body.innerHTML = '<template id="tmpl1">' +
+ '<div id="div1">This is div inside template</div>' +
+ '<div id="div2">This is another div inside template</div>' +
+ '</template>';
+
+ var template = doc.querySelector('#tmpl1');
+ var copy = template.cloneNode(true);
+
+ assert_not_equals(copy.content, undefined, 'Template clone content attribute should not be undefined');
+ assert_not_equals(copy.content, null, 'Template clone content attribute should not be null');
+
+ assert_equals(copy.content.childNodes.length, 2,
+ 'Wrong number of template content\'s copy child nodes');
+ assert_not_equals(copy.content.querySelector('#div1'), null,
+ 'Template child node should be copied');
+ assert_not_equals(copy.content.querySelector('#div2'), null,
+ 'Template child node should be copied');
+
+}, 'Clone template node. Test call to cloneNode(true)');
+
+
+
+test(function () {
+ var doc = newHTMLDocument();
+ doc.body.innerHTML = '<template id="tmpl1">' +
+ '<div id="div1">This is div inside template</div>' +
+ '<div id="div2">This is another div inside template</div>' +
+ '</template>';
+
+ var template = doc.querySelector('#tmpl1');
+ var copy = template.cloneNode();
+
+ assert_not_equals(copy.content, undefined, 'Template clone content attribute should not be undefined');
+ assert_not_equals(copy.content, null, 'Template clone content attribute should not be null');
+
+ assert_equals(copy.content.childNodes.length, 0,
+ 'Wrong number of template content\'s copy child nodes');
+
+}, 'Clone template node. Test call to cloneNode() with the default parameter '
+ + '(false by default)');
+
+
+
+test(function () {
+ var doc = newHTMLDocument();
+ doc.body.innerHTML = '<template id="tmpl1">' +
+ '<div id="div1">This is div inside template</div>' +
+ '<div id="div2">This is another div inside template</div>' +
+ '</template>';
+
+ var template = doc.querySelector('#tmpl1');
+ var copy = template.cloneNode(false);
+
+ assert_not_equals(copy.content, undefined, 'Template clone content attribute is undefined');
+ assert_not_equals(copy.content, null, 'Template clone content attribute is null');
+
+ assert_equals(copy.content.childNodes.length, 0,
+ 'Wrong number of template content\'s copy child nodes');
+
+}, 'Clone template node. Test call to cloneNode(false)');
+
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-steps-to-clone-a-node/templates-copy-document-owner.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-steps-to-clone-a-node/templates-copy-document-owner.html
new file mode 100644
index 0000000000..a2afc23041
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/additions-to-the-steps-to-clone-a-node/templates-copy-document-owner.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: ownerDocument of cloned template content is set to template content owner</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="author" title="Aleksei Yu. Semenov" href="a.semenov@unipro.ru">
+<meta name="assert" content="ownerDocument of cloned template content is set to template content owner">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#node-clone-additions">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+function checkOwnerDocument(node, doc) {
+ if ((node !== null) && (node !== undefined)) {
+ assert_equals(node.ownerDocument, doc,
+ 'Wrong ownerDocument of the template copy\'s node ' + node.nodeName);
+ for (var i = 0; i < node.childNodes.length; i++) {
+ if (node.childNodes[i].nodeType === Node.ELEMENT_NODE) {
+ checkOwnerDocument(node.childNodes[i], doc);
+ if (node.childNodes[i].nodeName === 'TEMPLATE') {
+ checkOwnerDocument(node.childNodes[i].content, doc);
+ }
+ }
+ }
+ }
+}
+
+
+test(function () {
+ var doc = newHTMLDocument();
+ doc.body.innerHTML = '<template id="tmpl1">' +
+ '<div id="div1">This is div inside template</div>' +
+ '<div id="div2">This is another div inside template</div>' +
+ '</template>';
+
+ var template = doc.querySelector('#tmpl1');
+ var copy = template.cloneNode(true);
+
+ assert_equals(copy.content.childNodes.length, 2,
+ 'Wrong number of template content\'s copy child nodes');
+ checkOwnerDocument(copy.content, template.content.ownerDocument);
+
+}, 'ownerDocument of cloned template content is set to template content owner. '
+ + 'Test cloning with children');
+
+
+
+test(function () {
+ var doc = newHTMLDocument();
+ doc.body.innerHTML = '<template id="tmpl1">' +
+ '<div id="div1">This is div inside template</div>' +
+ '<div id="div2">This is another div inside template</div>' +
+ '</template>';
+
+ var template = doc.querySelector('#tmpl1');
+ var copy = template.cloneNode(false);
+
+ assert_equals(copy.content.childNodes.length, 0,
+ 'Wrong number of template content\'s copy child nodes');
+ checkOwnerDocument(copy.content, template.content.ownerDocument);
+
+}, 'ownerDocument of cloned template content is set to template content owner. '
+ + 'Test cloning without children');
+
+
+
+test(function () {
+ var doc = newHTMLDocument();
+ doc.body.innerHTML = '<template id="tmpl1">' +
+ '<div id="div1">This is div inside template</div>' +
+ '<div id="div2">This is another div inside template</div>' +
+ '</template>';
+
+ var template = doc.querySelector('#tmpl1');
+ var copy = template.cloneNode();
+
+ assert_equals(copy.content.childNodes.length, 0,
+ 'Wrong number of template content\'s copy child nodes');
+ checkOwnerDocument(copy.content, template.content.ownerDocument);
+
+}, 'ownerDocument of cloned template content is set to template content owner. '
+ + 'Test cloneNode() with no arguments (false by default)');
+
+
+
+test(function () {
+ var doc = newHTMLDocument();
+ doc.body.innerHTML = '<template id="tmpl1">' +
+ '<div id="div1">This is div inside template</div>' +
+ '<div id="div2">This is another div inside template</div>' +
+ '<template id="tmpl2">' +
+ '<div id="div3">This is div inside nested template</div>' +
+ '<div id="div4">This is another div inside nested template</div>' +
+ '</template>' +
+ '</template>';
+
+ var template = doc.querySelector('#tmpl1');
+ var copy = template.cloneNode(true);
+
+ assert_equals(copy.content.childNodes.length, 3,
+ 'Wrong number of template content\'s copy child nodes');
+ checkOwnerDocument(copy.content, template.content.ownerDocument);
+
+}, 'ownerDocument of cloned template content is set to template content owner. '
+ + 'Test cloning nested template');
+
+
+
+testInIFrame('../resources/template-contents.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template = doc.body.querySelector('template');
+ var copy = template.cloneNode(true);
+
+ checkOwnerDocument(copy.content, template.content.ownerDocument);
+
+}, 'ownerDocument of cloned template content is set to template content owner. '
+ + 'Test loading HTML document from file');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-document-type.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-document-type.html
new file mode 100644
index 0000000000..d063acded9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-document-type.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: The template contents owner document type is HTML document</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="assert" content="The template contents owner document type is HTML document, if template is declared in HTML document">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#definitions">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+testInIFrame('../resources/template-contents.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+ var template = doc.querySelector('template');
+ var content_owner = template.content.ownerDocument;
+
+ assert_class_string(content_owner, 'Document',
+ 'Template content owner should be a document');
+ assert_equals(content_owner.createElement('DIV').localName, 'div',
+ 'Template content owner should be an HTML document');
+
+}, 'The template contents owner document type is HTML document ' +
+ '(case when document has browsing context and the template ' +
+ 'is created by HTML parser)');
+
+
+testInIFrame('../resources/template-contents.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+ var template = doc.createElement('template');
+ var content_owner = template.content.ownerDocument;
+ var div = doc.createElement('DIV');
+ template.appendChild(div);
+
+ doc.body.appendChild(template);
+
+ assert_class_string(content_owner, 'Document',
+ 'Template content owner should be a document');
+ assert_equals(div.localName, 'div',
+ 'Template content owner should be an HTML document');
+
+}, 'The template contents owner document type is HTML document ' +
+ '(case when document has browsing context and the template ' +
+ 'is created by createElement())');
+
+
+test(function() {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+ var content_owner = template.content.ownerDocument;
+ var div = doc.createElement('DIV');
+ template.appendChild(div);
+
+ doc.body.appendChild(template);
+
+ assert_class_string(content_owner, 'Document',
+ 'Template content owner should be a document');
+ assert_equals(div.localName, 'div',
+ 'Template content owner should be an HTML document');
+
+}, 'The template contents owner document type is HTML document ' +
+ '(case when document has no browsing context and the template is created ' +
+ 'by createElement())');
+
+test(function() {
+ var doc = newHTMLDocument();
+ doc.body.innerHTML = '<template><div>Hello!</div></template>';
+ var template = doc.querySelector('template');
+ var content_owner = template.content.ownerDocument;
+
+ assert_class_string(content_owner, 'Document',
+ 'Template content owner should be a document');
+ assert_equals(content_owner.createElement('DIV').localName, 'div',
+ 'Template content owner should be an HTML document');
+
+}, 'The template contents owner document type is HTML document ' +
+ '(case when document has no browsing context and the template is created via innerHTML)');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-test-001.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-test-001.html
new file mode 100644
index 0000000000..a087f788e5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-test-001.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: The template contents owner document (no browsing context)</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="assert" content="Even if template's enclosing document has no browsing context, it gets its own template contents owner">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#definitions">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+
+test(function() {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+
+ doc.body.appendChild(template);
+
+ assert_not_equals(template.content.ownerDocument, doc, 'Wrong template content owner');
+
+}, 'Test the template contents owner document when enclosing document has '
+ + 'no browsing content. Template element is created by createElement()');
+
+
+
+test(function() {
+ var doc = newHTMLDocument();
+
+ doc.body.innerHTML = '<template><div>some text</div></template>';
+
+ var template = doc.querySelector('template');
+
+ assert_not_equals(template.content.ownerDocument, doc, 'Wrong template content owner');
+
+}, 'Test the template contents owner document when enclosing document has '
+ + 'no browsing content. Template element is created by innerHTML');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-test-002.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-test-002.html
new file mode 100644
index 0000000000..cf2e30b643
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents-owner-test-002.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: The template contents owner document (there's browsing context)</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="assert" content="If template's enclosing document has browsing context, then templates content owner must be a new Document node without browsing context">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#definitions">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+
+testInIFrame(null, function(context) {
+ var doc = context.iframes[0].contentDocument;
+ var template = doc.createElement('template');
+
+ var div = doc.createElement('div');
+ div.setAttribute('id', 'div1');
+
+ template.appendChild(div);
+
+ doc.body.appendChild(template);
+
+ // doc has browsing context. There should be another document as a template
+ // content owner
+ assert_not_equals(template.content.ownerDocument, doc, 'Wrong template owner document');
+
+}, 'The template contents owner document must be different from template owner document,' +
+ ' which has browsing context. Template element is created by createElement()');
+
+
+
+testInIFrame(null, function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ doc.body.innerHTML = '<template><div>some text</div></template>';
+
+ var template = doc.querySelector('template');
+
+ // doc has browsing context. There should be another document as a template
+ // content owner
+ assert_not_equals(template.content.ownerDocument, doc, 'Wrong template owner document');
+
+}, 'The template contents owner document must be different from template owner document,' +
+ ' which has browsing context. Template element is created via innerHTML');
+
+
+
+testInIFrame('../resources/template-contents.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template = doc.querySelector('template');
+
+ // doc has browsing context. There should be another document as a template
+ // content owner
+ assert_not_equals(template.content.ownerDocument, doc, 'Wrong template owner document');
+
+}, 'The template contents owner document must be different from template owner document,' +
+ ' which has browsing context. Template element is created by HTML parser');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents.html
new file mode 100644
index 0000000000..4a61dc8d3b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/definitions/template-contents.html
@@ -0,0 +1,172 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: The template contents is a DocumentFragment</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="assert" content="The template contents must be a DocumentFragment">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#definitions">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+
+test(function() {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+
+ doc.body.appendChild(template);
+
+ assert_equals(template.content.nodeType, Node.DOCUMENT_FRAGMENT_NODE,
+ 'Template content should be a DocumentFragment');
+
+ assert_class_string(template.content, 'DocumentFragment',
+ 'Template content class should be a DocumentFragment');
+
+}, 'The template contents must be a DocumentFragment (empty template)');
+
+
+test(function() {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+
+ template.innerHTML = '<div>This is a div</div><span>This is a span</span>';
+
+ doc.body.appendChild(template);
+
+ assert_equals(template.content.nodeType, Node.DOCUMENT_FRAGMENT_NODE,
+ 'Template content should be a DocumentFragment');
+
+ assert_class_string(template.content, 'DocumentFragment',
+ 'Template content class should be a DocumentFragment');
+
+}, 'The template contents must be a DocumentFragment (non empty template)');
+
+
+
+test(function() {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+
+ template.innerHTML = '<div>This is a div</div>';
+
+ doc.body.appendChild(template);
+
+ assert_equals(template.content.nodeType, Node.DOCUMENT_FRAGMENT_NODE,
+ 'Template content should be a documentFragment');
+
+}, 'The template contents must be a DocumentFragment (non empty template '
+ + 'containing div which is an Element instance)');
+
+
+test(function() {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+
+ template.innerHTML = 'Some text';
+
+ doc.body.appendChild(template);
+
+ assert_equals(template.content.nodeType, Node.DOCUMENT_FRAGMENT_NODE,
+ 'Template content should be a documentFragment');
+
+ assert_class_string(template.content, 'DocumentFragment',
+ 'Template content class should be a DocumentFragment');
+
+}, 'The template contents must be a DocumentFragment (not empty template '
+ + 'containing text node)');
+
+
+test(function() {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+
+ template.innerHTML = '<template id="t2">Some text</template>';
+
+ doc.body.appendChild(template);
+
+ assert_equals(template.content.nodeType, Node.DOCUMENT_FRAGMENT_NODE,
+ 'Template content should be a documentFragment');
+ assert_class_string(template.content, 'DocumentFragment',
+ 'Template content class should be a DocumentFragment');
+
+ var nestedTemplate = template.content.querySelector("#t2");
+ assert_equals(nestedTemplate.content.nodeType, Node.DOCUMENT_FRAGMENT_NODE,
+ 'Nested template content should be a documentFragment');
+
+ assert_class_string(nestedTemplate.content, 'DocumentFragment',
+ 'Nested template content class should be a DocumentFragment');
+
+
+}, 'The template contents must be a DocumentFragment (nested template '
+ + 'containing a text node)');
+
+
+testInIFrame('../resources/template-contents-empty.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template = doc.querySelector('template');
+
+ assert_equals(template.content.nodeType, Node.DOCUMENT_FRAGMENT_NODE,
+ 'Template content should be a documentFragment');
+ assert_class_string(template.content, 'DocumentFragment',
+ 'Template content class should be a DocumentFragment');
+
+
+}, 'The template contents must be a DocumentFragment (the empty template tag '
+ + 'inside HTML file loaded in iframe)');
+
+
+testInIFrame('../resources/template-contents.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template = doc.querySelector('template');
+
+ assert_equals(template.content.nodeType, Node.DOCUMENT_FRAGMENT_NODE,
+ 'Template content should be a documentFragment');
+ assert_class_string(template.content, 'DocumentFragment',
+ 'Template content class should be a DocumentFragment');
+
+}, 'The template contents must be a DocumentFragment (non empty template '
+ + 'tag inside HTML file loaded in iframe)');
+
+
+testInIFrame('../resources/template-contents-text.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template = doc.querySelector('template');
+
+ assert_equals(template.content.nodeType, Node.DOCUMENT_FRAGMENT_NODE,
+ 'Template content should be a documentFragment');
+ assert_class_string(template.content, 'DocumentFragment',
+ 'Template content class should be a DocumentFragment');
+
+}, 'The template contents must be a DocumentFragment (the template tag '
+ + 'with some text inside HTML file loaded in iframe)');
+
+
+testInIFrame('../resources/template-contents-nested.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template = doc.querySelector('template');
+
+ assert_equals(template.content.nodeType, Node.DOCUMENT_FRAGMENT_NODE,
+ 'Template content should be a documentFragment');
+ assert_class_string(template.content, 'DocumentFragment',
+ 'Template content class should be a DocumentFragment');
+
+ var nestedTemplate = template.content.querySelector("template");
+
+ assert_equals(nestedTemplate.content.nodeType, Node.DOCUMENT_FRAGMENT_NODE,
+ 'Nested template content should be a documentFragment');
+ assert_class_string(nestedTemplate.content, 'DocumentFragment',
+ 'Nested template content class should be a DocumentFragment');
+
+}, 'The template contents must be a DocumentFragment (the template tag '
+ + 'with nested template tag inside HTML file loaded in iframe)');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/innerhtml-on-templates/innerhtml.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/innerhtml-on-templates/innerhtml.html
new file mode 100644
index 0000000000..0968b37f92
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/innerhtml-on-templates/innerhtml.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: innerHTML of template element replaces all referenced by the content attribute</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="author" title="Aleksei Yu. Semenov" href="a.semenov@unipro.ru">
+<meta name="assert" content="innerHTML of template element replaces all referenced by the content attribute">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#innerhtml-on-templates">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+test(function () {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+
+ var div1 = doc.createElement('div');
+ div1.setAttribute('id', 'div1');
+ template.content.appendChild(div1);
+
+ assert_not_equals(template.content.querySelector('#div1'), null,
+ 'Element should present in template content');
+
+ template.innerHTML = '<div id="div2"></div>';
+
+ assert_equals(template.content.querySelector('#div1'), null,
+ 'Template content should be replaced by innerHTML');
+ assert_not_equals(template.content.querySelector('#div2'), null,
+ 'Element should present in template content');
+
+}, 'innerHTML of template element replaces all referenced by the content attribute');
+
+
+test(function () {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+
+ var div1 = doc.createElement('div');
+ div1.setAttribute('id', 'div1');
+ template.content.appendChild(div1);
+
+ assert_not_equals(template.content.querySelector('#div1'), null,
+ 'Element should present in template content');
+
+ template.innerHTML = '';
+
+ assert_false(template.content.hasChildNodes(),
+ 'Template content should be removed by innerHTML');
+
+}, 'innerHTML of template element replaces all referenced by the content attribute. '
+ + 'Test empty HTML string');
+
+
+test(function () {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+ var nestedTemplate = doc.createElement('template');
+
+ template.content.appendChild(nestedTemplate);
+
+ var div1 = doc.createElement('div');
+ div1.setAttribute('id', 'div1');
+ nestedTemplate.content.appendChild(div1);
+
+ assert_not_equals(nestedTemplate.content.querySelector('#div1'), null,
+ 'Element should present in template content');
+
+ nestedTemplate.innerHTML = '<div id="div2"></div>';
+
+ assert_equals(nestedTemplate.content.querySelector('#div1'), null,
+ 'Template content should be replaced by innerHTML');
+ assert_not_equals(nestedTemplate.content.querySelector('#div2'), null,
+ 'Element should present in template content');
+
+}, 'innerHTML of template element replaces all referenced by the content attribute. '
+ + 'Test nested template');
+
+
+testInIFrame('../resources/template-contents.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template = doc.querySelector('template');
+ assert_not_equals(template.content.querySelector('div'), null,
+ 'Div element should present in template content');
+
+ template.innerHTML = '<span>span internals</span>';
+
+ assert_equals(template.content.querySelector('div'), null,
+ 'div element should be replaced by span in template content');
+
+ assert_not_equals(template.content.querySelector('span'), null,
+ 'span element should present in template content');
+
+
+}, 'innerHTML of template element replaces all referenced by the content attribute. '
+ + 'Test loading of HTML document from a file');
+
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/end-template-tag-in-body.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/end-template-tag-in-body.html
new file mode 100644
index 0000000000..2cb149853f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/end-template-tag-in-body.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The file contains several &lt;/template&gt; tag in HTML body without start one</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+</head>
+<body>
+ </template>
+ <div>The file contains several &lt;/template&gt; tag in HTML body without start one</div>
+ </template></template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/end-template-tag-in-head.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/end-template-tag-in-head.html
new file mode 100644
index 0000000000..02d0c7be65
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/end-template-tag-in-head.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ </template>
+ <title>The file contains several &lt;/template&gt; tag in HTML head without start one</title>
+ </template></template>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+ </template>
+</head>
+<body>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/frameset-end-tag.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/frameset-end-tag.html
new file mode 100644
index 0000000000..b84d55595f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/frameset-end-tag.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The file contains frameset with the template and frameset end tag in it</title>
+ <link rel="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+</head>
+<frameset>
+ <template></frameset></template>
+</frameset>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/head-template-contents-div-no-end-tag.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/head-template-contents-div-no-end-tag.html
new file mode 100644
index 0000000000..e4e45bcea5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/head-template-contents-div-no-end-tag.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The file contains template element with open div tag, but without end div tag, in the head</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+ <template>
+ <div>Hello, template
+ </template>
+</head>
+<body>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/head-template-contents-table-no-end-tag.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/head-template-contents-table-no-end-tag.html
new file mode 100644
index 0000000000..9db2b4af06
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/head-template-contents-table-no-end-tag.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The file contains template element with open table, tr, td tags, but without end td, tr, table tags, in the head</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+ <template>
+ <table>
+ <tr>
+ <td>Hello, cell one!
+ </template>
+</head>
+<body>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/html-start-tag.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/html-start-tag.html
new file mode 100644
index 0000000000..0de652cf36
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/html-start-tag.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html tabindex="5">
+<head>
+ <title>The file contains html root element with attributes and some in the body</title>
+ <link rel="author" title="Sergey G. Grekhovv" href="mailto:sgrekhov@unipro.ru">
+</head>
+<body>
+<template id="tmpl"><html class="htmlClass"></html></template><html id="htmlId" tabindex="5">
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-child-nodes-div.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-child-nodes-div.xhtml
new file mode 100644
index 0000000000..14db5004dc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-child-nodes-div.xhtml
@@ -0,0 +1,14 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Template tag with children div tags inside</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"/>
+</head>
+<body>
+ <p>Template tag with div tags inside</p>
+ <template>
+ <div>This is div inside template</div>
+ <div>This is another div inside template</div>
+ </template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-child-nodes-nested.xhtml b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-child-nodes-nested.xhtml
new file mode 100644
index 0000000000..406fa6c3d4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-child-nodes-nested.xhtml
@@ -0,0 +1,16 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Template tag with children div tags inside another template tag</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru"/>
+</head>
+<body>
+ <p>Template tag with children div tags inside another template tag</p>
+ <template>
+ <template>
+ <div>This is div inside template</div>
+ <div>This is another div inside template</div>
+ </template>
+ </template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-attribute.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-attribute.html
new file mode 100644
index 0000000000..b9dd5f47a8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-attribute.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Empty template tag with attribute content</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<head>
+<body>
+ <template content='some text'></template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-body.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-body.html
new file mode 100644
index 0000000000..a1f246fd63
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-body.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>BODY tag inside template</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<head>
+<body>
+ <template><body></body></template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-div-no-end-tag.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-div-no-end-tag.html
new file mode 100644
index 0000000000..304acf3025
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-div-no-end-tag.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Div tag inside template tag</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+</head>
+<body>
+ <template>
+ <div>Hello, template
+ </template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-empty.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-empty.html
new file mode 100644
index 0000000000..f1a539cc08
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-empty.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Empty template tag</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<head>
+<body>
+ <template>
+ </template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-frameset.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-frameset.html
new file mode 100644
index 0000000000..4331367df3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-frameset.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>FRAMESET tag inside template</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<head>
+<body>
+ <template><frameset></frameset></template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-head.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-head.html
new file mode 100644
index 0000000000..1e3a337e8d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-head.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HEAD tag inside template</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<head>
+<body>
+ <template><head></head></template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-html.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-html.html
new file mode 100644
index 0000000000..5dd3a28e6a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-html.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML tag inside template</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<head>
+<body>
+ <template><html></html></template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-nested.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-nested.html
new file mode 100644
index 0000000000..dc2dc6f15f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-nested.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+ <title>Contains second template tag inside template tag</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<body>
+ <template>
+ <template>
+ <div>Inside nested template</div>
+ </template>
+ </template>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-table-no-end-tag.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-table-no-end-tag.html
new file mode 100644
index 0000000000..4639b4dc8e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-table-no-end-tag.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The file contains template element with open table, tr, td tags, without end td, tr, table tags</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+</head>
+<body>
+ <template>
+ <table>
+ <tr>
+ <td>Hello, cell one!
+ </template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-text.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-text.html
new file mode 100644
index 0000000000..a401848efc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents-text.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Some text inside template tag</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+</head>
+<body>
+ <template>Some text</template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents.html
new file mode 100644
index 0000000000..07256c06a3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-contents.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Div tag inside template tag</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+</head>
+<body>
+ <template>
+ <div>Hello, template</div>
+ </template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-descendant-body.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-descendant-body.html
new file mode 100644
index 0000000000..d64848c8db
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-descendant-body.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Div tag inside template tag</title>
+ <link rel="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+</head>
+<body>
+ <template>
+ <div>Hello, template</div>
+ </template>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-descendant-frameset.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-descendant-frameset.html
new file mode 100644
index 0000000000..4801178454
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-descendant-frameset.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Template tag inside frameset</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+</head>
+<frameset>
+ <template>
+ <div>Hello, template</div>
+ </template>
+</frameset>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-descendant-head.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-descendant-head.html
new file mode 100644
index 0000000000..6bab00ea99
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/template-descendant-head.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Template tag inside head</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+ <template>
+ <div>Hello, template</div>
+ </template>
+</head>
+<body>
+ Nothing interesting here
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/two-templates.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/two-templates.html
new file mode 100644
index 0000000000..f6e9ab58e8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/resources/two-templates.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The file contains two template elements</title>
+ <link rel="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+</head>
+<body>
+ <template id="template1">
+ <div>Hello, template</div>
+ </template>
+
+ <template id="template2">
+ <div>Hello, from second template</div>
+ </template>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/serializing-html-templates/outerhtml.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/serializing-html-templates/outerhtml.html
new file mode 100644
index 0000000000..1539afbe1e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/serializing-html-templates/outerhtml.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: serialize template contents instead of template element</title>
+<meta name="author" title="Aleksei Yu. Semenov" href="a.semenov@unipro.ru">
+<meta name="assert" content="template contents should be serialized instead of template element if serializing template element">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#serializing-html-templates">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+test(function () {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+
+ var div = doc.createElement('div');
+ div.setAttribute('id', 'div1');
+ div.innerHTML = 'some text';
+ template.content.appendChild(div);
+
+ assert_equals(template.outerHTML, '<template><div id="div1">some text</div></template>',
+ 'template element is serialized incorrectly');
+
+}, 'Template contents should be serialized instead of template element if serializing template element');
+
+
+
+test(function () {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+ var nestedTemplate = doc.createElement('template');
+
+ template.content.appendChild(nestedTemplate);
+
+ var div = doc.createElement('div');
+ div.setAttribute('id', 'div1');
+ div.innerHTML = 'some text';
+ nestedTemplate.content.appendChild(div);
+
+ assert_equals(template.outerHTML, '<template><template><div id="div1">some text</div></template></template>',
+ 'template element is serialized incorrectly');
+
+
+}, 'Template contents should be serialized instead of template element if serializing template element. '
+ + 'Test nested template');
+
+
+test(function () {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+
+ var div = doc.createElement('div');
+ div.setAttribute('id', 'div1');
+ div.innerHTML = 'some text';
+ template.content.appendChild(div);
+ doc.body.appendChild(template);
+
+ assert_equals(doc.documentElement.outerHTML, '<html><head><title>Test Document</title></head><body><template><div id="div1">some text</div></template></body></html>',
+ 'template element is serialized incorrectly');
+
+}, 'Template contents should be serialized instead of template element if serializing template element. '
+ + 'Test serializing whole document');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/content-attribute.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/content-attribute.html
new file mode 100644
index 0000000000..b4c11b841f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/content-attribute.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: Content attribute of template element is read-only</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<meta name="assert" content="Content attribute of template element is read-only">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#template-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+test(function() {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+
+ assert_readonly(template, 'content',
+ 'Content attribute of template element should be read-only');
+
+}, 'Content attribute of template element is read-only. ' +
+ 'Test empty template');
+
+
+test(function() {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+ var el1 = doc.createElement('div');
+ var el2 = doc.createElement('span');
+ el1.appendChild(el2);
+
+ template.content.appendChild(el1);
+
+ assert_readonly(template, 'content',
+ 'Content attribute of template element should be read-only');
+
+}, 'Content attribute of template element is read-only. ' +
+ 'Test not empty template populated by appendchild()');
+
+
+test(function() {
+ var doc = newHTMLDocument();
+ doc.body.innerHTML = '<template>Text<div>DIV</div></template>';
+
+ var template = doc.querySelector('template');
+
+ assert_readonly(template, 'content',
+ 'Content attribute of template element should be read-only');
+
+}, 'Content attribute of template element is read-only. ' +
+ 'Test not empty template populated by innerHTML');
+
+
+test(function() {
+ var doc = newHTMLDocument();
+ doc.body.innerHTML = '<template id="template1" content="Some text as a content"></template>';
+
+ var template = doc.querySelector('#template1');
+
+ assert_readonly(template, 'content',
+ 'Content attribute of template element should be read-only');
+
+}, 'Content attribute of template element is read-only. ' +
+ 'Test that custom content attribute named \'content\' doesn\'t ' +
+ 'make content IDL attribute writable');
+
+
+test(function() {
+ var doc = newHTMLDocument();
+ doc.body.innerHTML = '<template id="template1" content="<div id=div1>Div content</div>"></template>';
+
+ var template = doc.querySelector('#template1');
+
+ assert_readonly(template, 'content',
+ 'Content attribute of template element should be read-only');
+
+ assert_equals(template.content.childNodes.length, 0,
+ 'Content attribute of template element should be read-only');
+
+}, 'Content attribute of template element is read-only. ' +
+ 'Test that custom content attribute named \'content\' doesn\'t ' +
+ 'affect content IDL attribute');
+
+
+testInIFrame('../resources/template-contents-attribute.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template = doc.body.querySelector('template');
+
+ assert_readonly(template, 'content',
+ 'Content attribute of template element should be read-only');
+
+}, 'Content attribute of template element is read-only. '
+ + 'Text value of content attribute of template tag should be ignored, '
+ + 'when loading document from a file');
+
+
+testInIFrame('../resources/template-contents.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template = doc.body.querySelector('template');
+
+ assert_readonly(template, 'content',
+ 'Content attribute of template element should be read-only');
+
+}, 'Content attribute of template element is read-only. '
+ + 'Test content attribute of a document loaded from a file');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/node-document-changes.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/node-document-changes.html
new file mode 100644
index 0000000000..0c60c10738
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/node-document-changes.html
@@ -0,0 +1,192 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: When node's document changes its owner document should be changed</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<meta name="assert" content="When a template element's node document changes, the template element's content DocumentFragment must be adopted into the new node document's template contents owner document">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#template-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+test(function() {
+ var doc1 = newHTMLDocument();
+ var template = doc1.createElement('template');
+
+ assert_equals(template.ownerDocument, doc1, 'Wrong template node owner document');
+ assert_not_equals(template.content.ownerDocument, doc1,
+ 'Wrong template content owner document');
+
+ var doc2 = newHTMLDocument();
+ var template2 = doc2.createElement('template');
+ doc2.body.appendChild(template);
+
+ assert_equals(template.ownerDocument, template2.ownerDocument,
+ 'Template node owner document should be changed');
+ assert_equals(template.content.ownerDocument, template2.content.ownerDocument,
+ 'Template content owner document should be changed');
+
+}, 'Changing of template element\'s node document. ' +
+ 'Test that ownerDocument of an empty template and its content changes');
+
+test(function() {
+ var doc1 = newHTMLDocument();
+ doc1.body.innerHTML = '<template id="tmpl"><div>Div content</div> And some more text</template>';
+
+ var template = doc1.querySelector('#tmpl');
+
+ assert_equals(template.ownerDocument, doc1,
+ 'Wrong template node owner document');
+ assert_not_equals(template.content.ownerDocument, doc1,
+ 'Wrong template content owner document');
+
+ var doc2 = newHTMLDocument();
+ var template2 = doc2.createElement('template');
+ doc2.body.appendChild(template);
+
+ assert_equals(template.ownerDocument, template2.ownerDocument,
+ 'Template node owner document should be changed');
+ assert_equals(template.content.ownerDocument, template2.content.ownerDocument,
+ 'Template content owner document should be changed');
+
+ assert_equals(template.content.querySelector('div').ownerDocument,
+ template2.content.ownerDocument,
+ 'Template content descendants owner document should be changed');
+
+}, 'Changing of template element\'s node document. ' +
+ 'Test that ownerDocument of a not empty template and its content changes');
+
+test(function() {
+ var doc1 = newHTMLDocument();
+ doc1.body.innerHTML = ''
+ + '<template id="tmpl"><div>Div content</div> And some more text'
+ + '<template id="tmpl2"><div>Template content</div></template>'
+ + '</template>';
+
+ var template = doc1.querySelector('#tmpl');
+
+ assert_equals(template.ownerDocument, doc1, 'Wrong template node owner document');
+ assert_not_equals(template.content.ownerDocument, doc1,
+ 'Wrong template content owner document');
+
+ var nestedTemplate = template.content.querySelector('#tmpl2');
+
+ assert_equals(nestedTemplate.ownerDocument, template.content.ownerDocument,
+ 'Wrong nested template node owner document');
+ assert_equals(nestedTemplate.content.ownerDocument, template.content.ownerDocument,
+ 'Wrong nested template content owner document');
+
+ var doc2 = newHTMLDocument();
+ var template2 = doc2.createElement('template');
+ doc2.body.appendChild(template);
+
+ assert_equals(template.ownerDocument, template2.ownerDocument,
+ 'Template node owner document should be changed');
+ assert_equals(template.content.ownerDocument, template2.content.ownerDocument,
+ 'Template content owner document should be changed');
+ assert_equals(template.content.querySelector('div').ownerDocument,
+ template2.content.ownerDocument,
+ 'Template content descendants owner document should be changed');
+
+ assert_equals(nestedTemplate.ownerDocument,
+ template2.content.ownerDocument,
+ 'Nested template node owner document should be changed');
+ assert_equals(nestedTemplate.content.ownerDocument,
+ template2.content.ownerDocument,
+ 'Nested template content owner document should be changed');
+ assert_equals(nestedTemplate.content.querySelector('div').ownerDocument,
+ template2.content.ownerDocument,
+ 'Owner document of the nested template content descendants should be changed');
+
+}, 'Changing of template element\'s node document. ' +
+ 'Test that ownerDocument of nested template and its content changes');
+
+testInIFrame('../resources/template-contents.html', function(context) {
+ var doc1 = context.iframes[0].contentDocument;
+
+ var template = doc1.body.querySelector('template');
+
+ var doc2 = newHTMLDocument();
+ var template2 = doc2.createElement('template');
+ doc2.body.appendChild(template);
+
+ assert_equals(template.ownerDocument, template2.ownerDocument,
+ 'Template node owner document should be changed');
+ assert_equals(template.content.ownerDocument,
+ template2.content.ownerDocument,
+ 'Template content owner document should be changed');
+ assert_equals(template.content.querySelector('div').ownerDocument,
+ template2.content.ownerDocument,
+ 'Template content descendants owner document should be changed');
+
+}, 'Changing of template element\'s node document. ' +
+ 'Test document loaded from a file');
+
+testInIFrame('../resources/template-contents.html', function(context) {
+ var doc1 = context.iframes[0].contentDocument;
+
+ var doc2 = newHTMLDocument();
+ var template = doc2.createElement('template');
+ var div = doc2.createElement('div');
+ template.content.appendChild(div);
+
+ doc1.body.appendChild(template);
+
+ assert_not_equals(template.ownerDocument, doc2,
+ 'Template node owner document should be changed');
+ assert_not_equals(template.content.ownerDocument, doc2,
+ 'Template content owner document should be changed');
+ assert_not_equals(div.ownerDocument, doc2,
+ 'Template content descendants owner document should be changed');
+
+ assert_equals(template.ownerDocument, doc1,
+ 'Template node owner document should be changed');
+ // doc1 has browsing context so it cannot be template.content.ownerDocument
+ assert_not_equals(template.content.ownerDocument, doc1,
+ 'Template content owner document should be a new document');
+ assert_equals(div.ownerDocument, template.content.ownerDocument,
+ 'Template content descendants owner document should be ' +
+ 'template content document owner');
+
+}, 'Changing of template element\'s node document. ' +
+ 'Adobt template element into a document that has a browsing context');
+
+testInIFrame('../resources/template-contents.html', function(context) {
+ var doc1 = context.iframes[0].contentDocument;
+
+ var template = doc1.querySelector('template');
+ var div = template.content.querySelector('div');
+ var templateContentOwner = template.content.ownerDocument;
+
+ var doc2 = document;
+
+ doc2.body.appendChild(template);
+
+ assert_not_equals(template.ownerDocument, doc1,
+ 'Template node owner document should be changed');
+ assert_not_equals(template.content.ownerDocument, templateContentOwner,
+ 'Template content owner document should be changed');
+ assert_not_equals(div.ownerDocument, templateContentOwner,
+ 'Template content descendants owner document should be changed');
+
+ assert_equals(template.ownerDocument, doc2,
+ 'Template node owner document should be changed');
+ // doc2 has browsing context, so it cannot be template.content.ownerDocument
+ assert_not_equals(template.content.ownerDocument, doc2,
+ 'Template content owner document should be a new document');
+ assert_equals(div.ownerDocument, template.content.ownerDocument,
+ 'Template content descendants owner document should be ' +
+ 'template content document owner');
+
+}, 'Changing of template element\'s node document. ' +
+ 'Test the case when both old and new owner documents of template element ' +
+ 'have browsing context');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-as-a-descendant.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-as-a-descendant.html
new file mode 100644
index 0000000000..caa1beab47
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-as-a-descendant.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: Template element as a descendant of the body element.</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<meta name="assert" content="Template element can be a descendant of the body element">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-template-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+function templateIsAChild(element) {
+ element.innerHTML = '<template>some text</template>';
+
+ assert_not_equals(element.querySelector('template'), null,
+ 'Template element should be a descendant of the ' + element.tagName + ' element');
+}
+
+function templateIsDisallowedAsAChild(element) {
+ element.innerHTML = '<template>some text</template>';
+
+ assert_equals(element.querySelector('template'), null,
+ 'Template element should not be allowed as a descendant of the ' + element.tagName + ' element');
+}
+
+function templateIsAnIndirectChild(element) {
+ element.innerHTML = '<div><template>some text</template></div>';
+
+ assert_not_equals(element.querySelector('template'), null,
+ 'Template element should be a descendant of the ' + element.tagName + ' element');
+}
+
+function templateIsDisallowedAsAnIndirectChild(element) {
+ element.innerHTML = '<div><template>some text</template></div>';
+
+ assert_equals(element.querySelector('template'), null,
+ 'Template element should not be allowed as indirect descendant of the ' + element.tagName + ' element');
+}
+
+function templateIsAnAppendedChild(doc, element) {
+ var template = doc.createElement('template');
+
+ element.appendChild(template);
+
+ assert_not_equals(element.querySelector('template'), null,
+ 'Template element should be a descendant of the ' + element.tagName + ' element');
+}
+
+function templateIsAnAppendedIndirectChild(doc, element) {
+ var template = doc.createElement('template');
+ var div = doc.createElement('div');
+ div.appendChild(template);
+
+ element.appendChild(div);
+
+ assert_not_equals(element.querySelector('template'), null,
+ 'Template element should be a descendant of the ' + element.tagName + ' element');
+}
+
+var doc = newHTMLDocument();
+var frameset = doc.createElement('frameset');
+
+var parameters = [['Template element as a descendant of the BODY element. ' +
+ 'Template element is created by innerHTML',
+ doc.body],
+ ['Template element as a descendant of the HEAD element. ' +
+ 'Template element is created by innerHTML',
+ doc.head],
+ ];
+// Template element as a descendant of the HEAD and BODY elements
+generate_tests(templateIsAChild, parameters);
+
+parameters = [['Template element as a descendant of the FRAMESET element. ' +
+ 'Template element is created by innerHTML',
+ frameset],
+ ];
+// Template element should be disallowed as a descendant of the FRAMESET elements
+generate_tests(templateIsDisallowedAsAChild, parameters);
+
+
+parameters = [['Template element as an indirect descendant of the BODY element. ' +
+ 'Template element is created by innerHTML',
+ doc.body],
+ ['Template element as an indirect descendant of the HEAD element. ' +
+ 'Template element is created by innerHTML',
+ doc.head],
+ ];
+// Template element as an indirect descendant of the HEAD, BODY and FRAMESET elements
+generate_tests(templateIsAnIndirectChild, parameters);
+
+parameters = [['Template element as an indirect descendant of the FRAMESET element. ' +
+ 'Template element is created by innerHTML',
+ frameset],
+ ];
+// Template element should be disallowed as an indirect descendant of the FRAMESET elements
+generate_tests(templateIsDisallowedAsAnIndirectChild, parameters);
+
+
+
+parameters = [['Template element as a descendant of the BODY element. ' +
+ 'Template element is appended by appendChild()',
+ doc, doc.body],
+ ['Template element as a descendant of the HEAD element. ' +
+ 'Template element is appended by appendChild()',
+ doc, doc.head],
+ ['Template element as a descendant of the FRAMESET element. ' +
+ 'Template element is appended by appendChild()',
+ doc, frameset]
+ ];
+// Template element as a descendant of the HEAD, BODY and FRAMESET elements
+generate_tests(templateIsAnAppendedChild, parameters);
+
+
+
+parameters = [['Template element as an indirect descendant of the BODY element. ' +
+ 'Template element is appended by appendChild()',
+ doc, doc.body],
+ ['Template element as an indirect descendant of the HEAD element. ' +
+ 'Template element is appended by appendChild()',
+ doc, doc.head],
+ ['Template element as an indirect descendant of the FRAMESET element. ' +
+ 'Template element is appended by appendChild()',
+ doc, frameset]
+ ];
+// Template element as a descendant of the HEAD, BODY and FRAMESET elements
+generate_tests(templateIsAnAppendedIndirectChild, parameters);
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-construction-in-inactive-document-crash.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-construction-in-inactive-document-crash.html
new file mode 100644
index 0000000000..496a47b2c5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-construction-in-inactive-document-crash.html
@@ -0,0 +1,5 @@
+<iframe id="i"></iframe>
+<script>
+i.contentDocument.documentElement.appendChild(document.body);
+</script>
+<template><script></script></template>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-hierarcy.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-hierarcy.html
new file mode 100644
index 0000000000..823c0c830f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-hierarcy.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<meta name="author" title="Takayoshi Kochi" href="mailto:kochi@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<div id="parent">
+ <template id="tmpl"><span>Happy Templating!</span></template>
+</div>
+<script>
+test(() => {
+ var parent = document.getElementById('parent');
+ var tmpl = document.getElementById('tmpl');
+
+ assert_equals(tmpl.innerHTML, '<span>Happy Templating!</span>');
+ var span = tmpl.content.querySelector('span');
+
+ // Hierarchy checks at various combinations.
+ assert_throws_dom('HierarchyRequestError', () => {
+ tmpl.content.appendChild(parent);
+ }, 'Template content should throw if any of ancestor is being appended.');
+ assert_throws_dom('HierarchyRequestError', () => {
+ tmpl.content.appendChild(tmpl);
+ }, 'Template content should throw if its host is being appended.');
+ assert_throws_dom('HierarchyRequestError', () => {
+ span.appendChild(parent);
+ }, 'Template content child should throw if any of ancestor is being appended.');
+ assert_throws_dom('HierarchyRequestError', () => {
+ span.appendChild(tmpl);
+ }, 'Template content child should throw template\'s host is being appended.');
+}, "Template content should throw when its ancestor is being appended.");
+
+test(() => {
+ var parent = document.getElementById('parent');
+ var tmpl = document.getElementById('tmpl');
+
+ assert_equals(tmpl.innerHTML, '<span>Happy Templating!</span>');
+ var span = tmpl.content.querySelector('span');
+
+ var tmpl_doc = tmpl.content.ownerDocument;
+ assert_equals(tmpl.ownerDocument, document);
+ assert_not_equals(tmpl_doc, document);
+
+ var new_doc = document.implementation.createHTMLDocument();
+ assert_not_equals(new_doc, document);
+ assert_not_equals(new_doc, tmpl_doc);
+
+ // Try moving tmpl.content to new_doc and check the results.
+ const tmplContentNodeDocument = tmpl.content.ownerDocument;
+ const tmplContentAdoptResult = new_doc.adoptNode(tmpl.content);
+ assert_equals(tmpl.content, tmplContentAdoptResult);
+ assert_equals(tmpl.ownerDocument, document);
+ assert_equals(tmpl.content.ownerDocument, tmplContentNodeDocument);
+
+ // Hierarchy checks at various combinations.
+ assert_throws_dom('HierarchyRequestError', () => {
+ tmpl.content.appendChild(parent);
+ }, 'Template content should throw if any of ancestor is being appended.');
+ assert_throws_dom('HierarchyRequestError', () => {
+ tmpl.content.appendChild(tmpl);
+ }, 'Template content should throw if its host is being appended.');
+ assert_throws_dom('HierarchyRequestError', () => {
+ span.appendChild(parent);
+ }, 'Template content child should throw if any of ancestor is being appended.');
+ assert_throws_dom('HierarchyRequestError', () => {
+ span.appendChild(tmpl);
+ }, 'Template content child should throw template\'s host is being appended.');
+
+ // Sanity check: template.content before and after move.
+ var tmpl_content_reference = tmpl.content;
+ assert_equals(tmpl.content.firstChild, span,
+ '<span> should be kept until it is removed, even after ' +
+ 'adopted to another document.');
+ new_doc.body.appendChild(tmpl.content);
+ assert_equals(tmpl.content.firstChild, null,
+ '<span> should be removed from template content.');
+ assert_equals(tmpl_content_reference, tmpl.content,
+ 'template.content should be identical before and after ' +
+ 'moving its children.');
+}, 'Template content should throw exception when its ancestor in ' +
+ 'a different document but connected via host is being append.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-in-inactive-document-crash.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-in-inactive-document-crash.html
new file mode 100644
index 0000000000..66c564c77a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-in-inactive-document-crash.html
@@ -0,0 +1,7 @@
+<iframe id="i"></iframe>
+<script>
+var t = i.contentDocument.createElement("template");
+i.contentDocument.documentElement.appendChild(t);
+i.remove();
+t.content;
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-move-to-inactive-document-crash.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-move-to-inactive-document-crash.html
new file mode 100644
index 0000000000..89c56d1663
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-move-to-inactive-document-crash.html
@@ -0,0 +1,7 @@
+<div id="d">
+<iframe id="i"></iframe>
+<template id="t"> </template>
+</div>
+<script>
+i.contentDocument.documentElement.appendChild(d);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-node-document.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-node-document.html
new file mode 100644
index 0000000000..da76c6b04f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content-node-document.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: Node document of the template content attribute must be template contents owner</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<meta name="assert" content="Node document of the template content attribute must be template contents owner">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#template-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+test(function() {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+ var nestedTemplate = doc.createElement('template');
+ template.appendChild(nestedTemplate);
+
+ assert_equals(nestedTemplate.content.ownerDocument, template.content.ownerDocument,
+ 'Wrong node document of the template content attribute');
+
+}, 'Node document of the template content attribute must be template contents owner. ' +
+ 'Nested template element created by createElement');
+
+
+test(function() {
+ var doc = newHTMLDocument();
+ doc.body.innerHTML = '<template><template></template></template>';
+ var template = doc.querySelector('template');
+ var nestedTemplate = template.content.querySelector('template');
+
+ assert_equals(nestedTemplate.content.ownerDocument, template.content.ownerDocument,
+ 'Wrong node document of the template content attribute');
+
+}, 'Node document of the template content attribute must be template contents owner. ' +
+ 'Nested template element created by innerHTML');
+
+testInIFrame('../resources/two-templates.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var template1 = doc.querySelector('#template1');
+ var template2 = doc.querySelector('#template2');
+
+ // when there is a browsing context, template contents owner is only accessible via template.content.ownerDocument
+ // because template contents owner is bounded to document
+ // verify that multiple templates share the same instance of template contents owner
+
+ assert_equals(template1.content.ownerDocument, template2.content.ownerDocument,
+ 'Wrong node document of the template content attribute');
+}, 'Node document of the template content attribute must be template contents owner. ' +
+ 'Load HTML file with multiple template elements');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content.html
new file mode 100644
index 0000000000..b9e790daf8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-content.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: HTML elements in template content</title>
+<meta name="author" title="Sergey G. Grekhov" href="mailto:sgrekhov@unipro.ru">
+<meta name="author" title="Aleksei Yu. Semenov" href="a.semenov@unipro.ru">
+<meta name="assert" content="Template may contain any element, except the html element, the head element, the body element, or the frameset element">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#template-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+HTML5_ELEMENTS.forEach(function(value) {
+ if (value !== 'body' && value !== 'html' && value !== 'head' && value !== 'frameset') {
+
+ test(function() {
+ var doc = newHTMLDocument();
+ var template = doc.createElement('template');
+ var element = doc.createElement(value);
+ template.content.appendChild(element);
+ var valueToTest = template.content.querySelector(value);
+
+ doc.body.appendChild(template);
+
+ assert_not_equals(valueToTest, null);
+ }, 'Template may contain ' + value + ' element');
+
+ }
+});
+
+
+
+var parameters = [];
+
+HTML5_ELEMENTS.forEach(function(value) {
+ if (value !== 'body' && value !== 'html' && value !== 'head' && value !== 'frameset') {
+
+ test(function() {
+ var doc = newHTMLDocument();
+
+ if (isVoidElement(value)) {
+ doc.body.innerHTML = '<template><' + value + '/></template>';
+ } else {
+ doc.body.innerHTML = '<template><' + value + '></' + value + '></template>';
+ }
+
+ var template = doc.querySelector('template');
+ var element = template.content.querySelector(value);
+
+ assert_not_equals(element, null);
+ }, 'Template may contain ' + value + ' element. '
+ + 'The template element and contents are added via body.innerHTML');
+
+ }
+});
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-descendant-body.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-descendant-body.html
new file mode 100644
index 0000000000..70028c5ecd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-descendant-body.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: Template element as a descendant of the body element.</title>
+<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<meta name="assert" content="Template element can be a descendant of the body element">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#template-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+testInIFrame('../resources/template-contents.html', function(ctx) {
+ var doc = ctx.iframes[0].contentDocument;
+
+ assert_not_equals(doc.body.querySelector('template'), null,
+ 'Template element should be a descendant of the body element');
+
+}, 'Template element as a descendant of the body element. Test loading from a file');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-descendant-frameset.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-descendant-frameset.html
new file mode 100644
index 0000000000..ce20a7413e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-descendant-frameset.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: Template element as a descendant of the frameset element.</title>
+<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<meta name="assert" content="Template element can not be a descendant of the frameset element">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#parsing-main-inframeset">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+testInIFrame('../resources/template-descendant-frameset.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var frameset = doc.querySelector('frameset');
+
+ assert_equals(frameset.querySelector('template'), null,
+ 'Template element should not be a descendant of the frameset element');
+
+}, 'Template element as a descendant of the frameset element. Test loading from a file');
+
+
+testInIFrame('../resources/template-descendant-frameset.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var frameset = doc.querySelector('frameset');
+
+ frameset.innerHTML = '';
+ assert_equals(doc.querySelector('template'), null,
+ 'Initial conditions are not satisfied');
+
+ frameset.innerHTML = '<template>some text</template>';
+
+ assert_equals(frameset.querySelector('template'), null,
+ 'Template element should not be a descendant of the frameset element');
+
+}, 'Template element as a descendant of the frameset element. '
+ + 'Test template element is assigned to frameset\'s innerHTML)');
+
+
+testInIFrame('../resources/template-descendant-frameset.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ var frameset = doc.querySelector('frameset');
+
+ var template = doc.createElement('template');
+ frameset.appendChild(template);
+
+ assert_equals(frameset.querySelectorAll('template').length, 1,
+ 'Template element should be a descendant of the frameset element');
+
+}, 'Template element as a descendant of the frameset element. '
+ + 'Test template element appended to frameset by appendChild()');
+
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-descendant-head.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-descendant-head.html
new file mode 100644
index 0000000000..611ec50bb4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-descendant-head.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>HTML Templates: Template element as a descendant of the head element.</title>
+<meta name="author" title="Aleksei Yu. Semenov" href="mailto:a.semenov@unipro.ru">
+<meta name="assert" content="Template element can be a descendant of the head element">
+<link rel="help" href="http://www.w3.org/TR/2013/WD-html-templates-20130214/#template-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src='/html/resources/common.js'></script>
+</head>
+<body>
+<div id="log"></div>
+<script type="text/javascript">
+
+testInIFrame('../resources/template-descendant-head.html', function(context) {
+ var doc = context.iframes[0].contentDocument;
+
+ assert_not_equals(doc.head.querySelector('template'), null,
+ 'Template element should be a descendant of the head element');
+
+}, 'Template element as a descendant of the head element. Test loading from a file');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-element-clone-into-inactive-document-crash.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-element-clone-into-inactive-document-crash.html
new file mode 100644
index 0000000000..56b199d1eb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-element-clone-into-inactive-document-crash.html
@@ -0,0 +1,7 @@
+<template id="t"> </template>
+<iframe id="i"></iframe>
+<script>
+var doc = i.contentDocument;
+i.remove();
+doc.importNode(t, true);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-set-inner-html-in-inactive-document-crash.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-set-inner-html-in-inactive-document-crash.html
new file mode 100644
index 0000000000..79a76ee76f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-element/template-set-inner-html-in-inactive-document-crash.html
@@ -0,0 +1,6 @@
+<iframe id="i"></iframe>
+<script>
+ var doc = i.contentDocument;
+ i.remove();
+ doc.createElement("template").innerHTML = "";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-table-crash.html b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-table-crash.html
new file mode 100644
index 0000000000..0f6a49874d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-template-element/template-table-crash.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://crbug.com/1212619">
+<meta name="assert" content="The renderer should not crash.">
+
+<template id=tmpl></template>
+<table id=tbl>
+ <script>
+ tmpl.appendChild(tbl);
+ </script>
+ Crash
+</table>
diff --git a/testing/web-platform/tests/html/semantics/sections/the-h1-h2-h3-h4-h5-and-h6-elements/original-id.json b/testing/web-platform/tests/html/semantics/sections/the-h1-h2-h3-h4-h5-and-h6-elements/original-id.json
new file mode 100644
index 0000000000..748a548ca9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/sections/the-h1-h2-h3-h4-h5-and-h6-elements/original-id.json
@@ -0,0 +1 @@
+{"original_id":"the-h1,-h2,-h3,-h4,-h5,-and-h6-elements"} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/selectors/META.yml b/testing/web-platform/tests/html/semantics/selectors/META.yml
new file mode 100644
index 0000000000..3195b8671c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/META.yml
@@ -0,0 +1,2 @@
+suggested_reviewers:
+ - lilles
diff --git a/testing/web-platform/tests/html/semantics/selectors/case-sensitivity/values.window.js b/testing/web-platform/tests/html/semantics/selectors/case-sensitivity/values.window.js
new file mode 100644
index 0000000000..1973398bff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/case-sensitivity/values.window.js
@@ -0,0 +1,91 @@
+// https://html.spec.whatwg.org/#case-sensitivity-of-selectors
+[
+ "accept",
+ "accept-charset",
+ "align",
+ "alink",
+ "axis",
+ "bgcolor",
+ "charset",
+ "checked",
+ "clear",
+ "codetype",
+ "color",
+ "compact",
+ "declare",
+ "defer",
+ "dir",
+ "direction",
+ "disabled",
+ "enctype",
+ "face",
+ "frame",
+ "hreflang",
+ "http-equiv",
+ "lang",
+ "language",
+ "link",
+ "media",
+ "method",
+ "multiple",
+ "nohref",
+ "noresize",
+ "noshade",
+ "nowrap",
+ "readonly",
+ "rel",
+ "rev",
+ "rules",
+ "scope",
+ "scrolling",
+ "selected",
+ "shape",
+ "target",
+ "text",
+ "type",
+ "valign",
+ "valuetype",
+ "vlink",
+].forEach(attributeName => {
+ const xmlDocument = new Document();
+ const htmlDocument = document;
+ [
+ {
+ input: xmlDocument.createElementNS("http://www.w3.org/1999/xhtml", "a"),
+ expected: false,
+ title: "<html:a> in XML",
+ },
+ {
+ input: xmlDocument.createElementNS("http://www.w3.org/1999/xhtml", "unknown"),
+ expected: false,
+ title: "<html:unknown> in XML",
+ },
+ {
+ input: xmlDocument.createElementNS("", "unknown"),
+ expected: false,
+ title: "<:unknown> in XML"
+ },
+ {
+ input: htmlDocument.createElementNS("http://www.w3.org/1999/xhtml", "a"),
+ expected: true,
+ title: "<html:a> in HTML",
+ },
+ {
+ input: htmlDocument.createElementNS("http://www.w3.org/1999/xhtml", "unknown"),
+ expected: true,
+ title: "<html:unknown> in HTML",
+ },
+ {
+ input: htmlDocument.createElementNS("", "unknown"),
+ expected: false,
+ title: "<:unknown> in HTML"
+ },
+ ].forEach(({ input, expected, title }) => {
+ test(t => {
+ t.add_cleanup(() => input.removeAttribute(attributeName));
+ input.setAttribute(attributeName, "HEYÏ");
+ assert_equals(input.matches(`[${attributeName}^=hey]`), expected, `^=hey`);
+ assert_false(input.matches(`[${attributeName}^=heyi]`));
+ }, `${attributeName}'s value is properly ASCII-case-insensitive for ${title}`);
+ });
+});
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/active-disabled.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/active-disabled.html
new file mode 100644
index 0000000000..a75a157c58
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/active-disabled.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/pull/7465">
+<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>
+
+<label id=buttonlabel for=disabledbutton>label for disabled button</label>
+<button id=disabledbutton disabled>disabled</button>
+
+<button id=buttonparent disabled>
+ <div id=buttonchild>child of disabled</div>
+</button>
+
+<input id=disabledinput disabled>
+
+<textarea id=disabledtextarea disabled>disabled textarea</textarea>
+
+<script>
+function testElement(description, clickElement, checkElement) {
+ promise_test(async () => {
+ if (!checkElement)
+ checkElement = clickElement;
+
+ await (new test_driver.Actions()
+ .pointerMove(2, 2, {origin: clickElement})
+ .pointerDown())
+ .send();
+
+ assert_true(checkElement.matches(':active'));
+
+ await (new test_driver.Actions()
+ .pointerUp())
+ .send();
+ }, description);
+}
+
+testElement('Clicking on a disabled button should make it match the :active selector.',
+ disabledbutton);
+
+testElement('Clicking the label for a disabled button should make the button match the :active selector.',
+ buttonlabel, disabledbutton);
+
+testElement('Clicking on a child of a disabled button should make the button match the :active selector.',
+ buttonchild, buttonparent);
+
+testElement('Clicking on a disabled input should make it match the :active selector.',
+ disabledinput);
+
+testElement('Clicking on a disabled textarea should make it match the :active selector.',
+ disabledtextarea);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/autofill.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/autofill.html
new file mode 100644
index 0000000000..b7c3644bdb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/autofill.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:autofill)</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+test_valid_selector(":autofill");
+test_valid_selector(":-webkit-autofill", [":autofill", ":-webkit-autofill"]);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked-001-manual.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked-001-manual.html
new file mode 100644
index 0000000000..76a963a600
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked-001-manual.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<html>
+ <head>
+ <title>CSS Selectors (:checked)</title>
+ <link rel="author" title="Ian Hickson" href="mailto:ian@hixie.ch"/>
+ <link rel="alternate" href="http://www.hixie.ch/tests/adhoc/css/selectors/checked/001.html"/>
+ <style type="text/css">
+ :checked, :checked + span { border: solid blue; color: blue; background: navy; }
+ </style>
+ </head>
+ <body>
+ <p>Anything that is checked below should be blue.</p>
+ <p><input checked type="checkbox"> <span>X</span></p>
+ <p><input checked type="radio" name="x"> <span>X</span> <input checked type="radio" name="x"> <span>X</span></p>
+ <p><select><option selected>X</option></select></p>
+ <p><select size="2"><option selected>X</option></select></p>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked-indeterminate.window.js b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked-indeterminate.window.js
new file mode 100644
index 0000000000..167cbdd37f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked-indeterminate.window.js
@@ -0,0 +1,27 @@
+test(() => {
+ const input = document.createElement("input");
+ input.type = "checkbox";
+
+ assert_false(input.matches(":checked:indeterminate"));
+ assert_false(input.matches(":checked"));
+ assert_false(input.matches(":indeterminate"));
+
+ input.checked = true;
+ input.indeterminate = true;
+
+ assert_true(input.matches(":checked:indeterminate"));
+ assert_true(input.matches(":checked"));
+ assert_true(input.matches(":indeterminate"));
+
+ input.indeterminate = false;
+
+ assert_false(input.matches(":checked:indeterminate"));
+ assert_true(input.matches(":checked"));
+ assert_false(input.matches(":indeterminate"));
+
+ input.checked = false;
+
+ assert_false(input.matches(":checked:indeterminate"));
+ assert_false(input.matches(":checked"));
+ assert_false(input.matches(":indeterminate"));
+}, "An element can be :checked and :indeterminate at the same time");
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked-type-change.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked-type-change.html
new file mode 100644
index 0000000000..661d9e4355
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked-type-change.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Selector: pseudo-class :checked input type change</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#pseudo-classes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ span { color: red }
+ :checked + span { color: green }
+</style>
+<input id="checked" type="text" checked>
+<span id="sibling">This text should be green.</span>
+<script>
+ test(() => {
+ assert_equals(getComputedStyle(sibling).color, "rgb(255, 0, 0)",
+ "Not matching :checked for type=text");
+
+ checked.type = "radio";
+
+ assert_equals(getComputedStyle(sibling).color, "rgb(0, 128, 0)",
+ "Matching :checked for type=radio");
+ }, "Evaluation of :checked changes on input type change.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked.html
new file mode 100644
index 0000000000..754c2342bc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/checked.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:checked)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org" id=link1>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes" id=link2>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<div id="log"></div>
+<select id=select1>
+ <optgroup label="options" id=optgroup1>
+ <option value="option1" id=option1 selected>option1
+ <option value="option2" id=option2>option2
+ <option value="option2" id=option3 checked>option3
+</select>
+<input type=checkbox id=checkbox1 checked>
+<input type=checkbox id=checkbox2>
+<input type=checkbox id=checkbox3 selected>
+<input type=radio id=radio1 checked>
+<input type=radio id=radio2>
+<form>
+ <p><input type=submit contextmenu=formmenu id="submitbutton"></p>
+ <menu type=context id=formmenu>
+ <!-- historical; these should *not* match -->
+ <menuitem type=checkbox checked default id=menuitem1>
+ <menuitem type=checkbox default id=menuitem2>
+ <menuitem type=checkbox id=menuitem3>
+ <menuitem type=radio checked id=menuitem4>
+ <menuitem type=radio id=menuitem5>
+ </menu>
+</form>
+
+<script>
+ testSelectorIdsMatch(":checked", ["option1", "checkbox1", "radio1"], "':checked' matches checked <input>s in checkbox and radio button states, selected <option>s");
+
+ document.getElementById("checkbox1").removeAttribute("type"); // change type of input
+ document.getElementById("radio1").removeAttribute("type"); // change type of input
+ testSelectorIdsMatch(":checked", ["option1"], "':checked' should no longer match <input>s whose type checkbox/radio has been removed");
+
+ document.getElementById("option2").selected = "selected"; // select option2
+ document.getElementById("checkbox2").click(); // check chekbox2
+ document.getElementById("radio2").click(); // check radio2
+ testSelectorIdsMatch(":checked", ["option2", "checkbox2", "radio2"], "':checked' matches clicked checkbox and radio buttons");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/default.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/default.html
new file mode 100644
index 0000000000..3187801f67
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/default.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:default)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org" id=link1>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes" id=link2>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<div id="log"></div>
+<form>
+ <button id=button1 type=button>button1</button>
+ <button id=button2 type=submit>button2</button>
+</form>
+<form>
+ <button id=button3 type=reset>button3</button>
+ <button id=button4>button4</button>
+</form>
+<button id=button5 type=submit>button5</button>
+<form id=form1>
+ <input type=text id=input1>
+</form>
+<input type=text id=input2 form=form1>
+<form>
+ <input type=submit id=input3>
+ <input type=submit id=input4>
+</form>
+<form>
+ <input type=image id=input5>
+ <input type=image id=input6>
+</form>
+<form>
+ <input type=submit id=input7>
+</form>
+<input type=checkbox id=checkbox1 checked>
+<input type=checkbox id=checkbox2>
+<input type=checkbox id=checkbox3 default>
+<input type=radio name=radios id=radio1 checked>
+<input type=radio name=radios id=radio2>
+<input type=radio name=radios id=radio3 default>
+<select id=select1>
+ <optgroup label="options" id=optgroup1>
+ <option value="option1" id=option1>option1
+ <option value="option2" id=option2 selected>option2
+</select>
+<dialog id="dialog">
+ <input type=submit id=input8>
+</dialog>
+<form>
+ <button id=button6 type='invalid'>button6</button>
+ <button id=button7>button7</button>
+</form>
+<form>
+ <button id=button8>button8</button>
+ <button id=button9>button9</button>
+</form>
+
+
+<script>
+ testSelectorIdsMatch(":default", ["button2", "button4", "input3", "input5", "input7", "checkbox1", "radio1", "option2", "button6", "button8"], "':default' matches <button>s that are their form's default button, <input>s of type submit/image that are their form's default button, checked <input>s and selected <option>s");
+
+ document.getElementById("button1").type = "submit"; // change the form's default button
+ testSelectorIdsMatch(":default", ["button1", "button4", "input3", "input5", "input7", "checkbox1", "radio1", "option2", "button6", "button8"], "':default' matches dynamically changed form's default buttons");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir-dynamic.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir-dynamic.html
new file mode 100644
index 0000000000..8f2951f8ef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir-dynamic.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="author" title="Vincent Hilla" href="mailto:vhilla@mozilla.com">
+ <link rel="help" href="https://html.spec.whatwg.org/#the-directionality">
+</head>
+<body>
+ <input id="inp"/>
+ <textarea id="ta"></textarea>
+ <div id="div"></div>
+ <pre id="pre"></pre>
+
+ <script>
+ function doTest(e) {
+ e.dir = "ltr";
+ assert_true(e.matches(":dir(ltr)"), "dir to ltr on " + e.tagName + " element");
+
+ e.dir = "rtl";
+ assert_true(e.matches(":dir(rtl)"), "dir to rtl on " + e.tagName + " element");
+
+ e.dir = "auto";
+ assert_true(e.matches(":dir(ltr)"), "dir to auto, empty text on " + e.tagName + " element");
+
+ e.value = "\u05D0;";
+ e.textContent = "\u05D0;";
+ assert_true(e.matches(":dir(rtl)"), "auto dir, text to Hebrew on " + e.tagName + " element");
+
+ e.dir = "ltr";
+ assert_true(e.matches(":dir(ltr)"), "dir to ltr, Hebrew text on " + e.tagName + " element");
+
+ e.dir = "auto";
+ assert_true(e.matches(":dir(rtl)"), "dir to auto, Hebrew text on " + e.tagName + " element");
+
+ e.removeAttribute("dir");
+ assert_true(e.matches(":dir(ltr)"), "dir removed, Hebrew text on " + e.tagName + " element");
+ }
+
+ const elements = [inp, ta, div, pre];
+ for (const e of elements) {
+ test(() => doTest(e), "Dynamically changing dir, text on " + e.tagName.toLowerCase() + " element");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir-html-input-dynamic-text.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir-html-input-dynamic-text.html
new file mode 100644
index 0000000000..0c50cec369
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir-html-input-dynamic-text.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1622900">
+<link rel="help" href="https://html.spec.whatwg.org/#the-directionality">
+<input value="ltr" dir="auto">
+<script>
+test(function() {
+ let input = document.querySelector("input");
+ assert_true(input.matches(":dir(ltr)"), "Input with ltr value should match dir(ltr)");
+ input.textContent = "ﷺ";
+ assert_true(input.matches(":dir(ltr)"), "Should still match dir(ltr) after text change");
+ input.value = "ltr2";
+ assert_true(input.matches(":dir(ltr)"), "Should still match dir(ltr) after value change");
+ input.value = "ﷺ";
+ assert_true(input.matches(":dir(rtl)"), "Should match dir(rtl) after value change");
+ input.textContent = "ltr";
+ assert_true(input.matches(":dir(rtl)"), "Should match dir(rtl) after text change");
+}, ":dir on <input> isn't altered by text children")
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir.html
new file mode 100644
index 0000000000..588c3c6850
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<html id=html>
+ <head id=head>
+ <meta charset=utf-8 id=meta>
+ <title id=title>Selector: pseudo-classes (:dir(ltr), :dir(rtl))</title>
+ <link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org" id=link1>
+ <link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes" id=link2>
+ <script src="/resources/testharness.js" id=script1></script>
+ <script src="/resources/testharnessreport.js" id=script2></script>
+ <script src="utils.js" id=script3></script>
+ <style id=style>
+ #span1 {direction: rtl;}
+ #span5, #span6 {display: none;}
+ </style>
+ </head>
+ <body id=body>
+ <div id="log"></div>
+ <bdo dir="rtl" id=bdo1>WERBEH</bdo>
+ <bdo dir="ltr" id=bdo2>HEBREW</bdo>
+ <bdi id=bdi1>HEBREW</bdi>
+ <bdi dir="rtl" id=bdi2>WERBEH</bdi>
+ <bdi dir="ltr" id=bdi3>HEBREW</bdi>
+ <bdi id=bdi4>إيان</bdi>
+ <span id=span1>WERBEH</span>
+ <span dir="rtl" id=span2>WERBEH</span>
+ <span dir="ltr" id=span3>HEBREW</span>
+ &#x202E;<span id=span4>WERBEH</span>&#x202C;
+ <span dir="rtl" id=span5>WERBEH</span>
+ <span dir="ltr" id=span6>HEBREW</span>
+ <span dir="rtl" id=span7>
+ <input type=tel id=input-tel1>
+ <input type=tel id=input-tel2 dir="invalid">
+ </span>
+ <input type=tel id=input-tel3 dir="rtl">
+ <bdo dir="auto" id=bdo3>HEBREW</bdo>
+ <bdo dir="auto" id=bdo4>إيان</bdo>
+ <bdo dir="ltr" id=bdo5>עברית</bdo>
+ <textarea dir="auto" id="ta1">إيان</textarea>
+ <textarea dir="auto" id="ta2">HEBREWإيان</textarea>
+ <textarea dir="auto" id="ta3">إيان</textarea>
+ <pre dir="auto" id="pre1">إيان</pre>
+ <pre dir="auto" id="pre2">HEBREWإيان</pre>
+
+ <script id=script4>
+ ta3.value = "HEBREW";
+
+ const rtlElements = [
+ "bdo1",
+ "bdi2",
+ "bdi4",
+ "span2",
+ "span5",
+ "span7",
+ "input-tel3",
+ "bdo4",
+ "ta1",
+ "pre1",
+ ];
+
+ testSelectorIdsMatch(":dir(rtl)", rtlElements, "':dir(rtl)' matches all elements whose directionality is 'rtl'.");
+
+ const ltrElements = [
+ "html",
+ "head",
+ "meta",
+ "title",
+ "link1",
+ "link2",
+ "script1",
+ "script2",
+ "script3",
+ "style",
+ "body",
+ "log",
+ "bdo2",
+ "bdi1",
+ "bdi3",
+ "span1",
+ "span3",
+ "span4",
+ "span6",
+ "input-tel1",
+ "input-tel2",
+ "bdo3",
+ "bdo5",
+ "ta2",
+ "ta3",
+ "pre2",
+ "script4",
+ ];
+
+ testSelectorIdsMatch(":dir(ltr)", ltrElements, "':dir(ltr)' matches all elements whose directionality is 'ltr'.");
+
+ const bdo = document.createElement("bdo");
+ bdo.setAttribute("dir", "ltr");
+ testSelectorIdsMatch(":dir(ltr)", ltrElements, "':dir(ltr)' doesn't match elements not in the document.");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir01.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir01.html
new file mode 100644
index 0000000000..61bbd574a3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/dir01.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset=iso-8859-8 id=meta>
+<title id=title>Selector: pseudo-classes (:dir(ltr), :dir(rtl)) in iso-8859-8 documents</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org" id=link1>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes" id=link2>
+<script src="/resources/testharness.js" id=script1></script>
+<script src="/resources/testharnessreport.js" id=script2></script>
+<script src="utils.js" id=script3></script>
+<div id="log"></div>
+<div>This text is left to right<div id=div1 style="direction:rtl">this is right to left</div></div>
+<div>This text is left to right<span id=div2 style="direction:rtl">this is left to right</span></div>
+
+<script>
+ var ltr = new Array(),
+ all = document.querySelectorAll('*');
+ for(var i = all.length; i--; ltr.unshift(all[i]));
+ testSelectorElementsMatch(":dir(ltr)", ltr, "direction doesn't affect :dir()");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/disabled.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/disabled.html
new file mode 100644
index 0000000000..142a12dd12
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/disabled.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:disabled)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org" id=link1>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes" id=link2>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<style>
+ #input4 {display:none;}
+</style>
+<div id="log"></div>
+<button id=button1 type=submit>button1</button>
+<button id=button2 disabled>button2</button>
+<input id=input1>
+<input id=input2 disabled>
+<input id=input3 readonly>
+<input id=input4>
+<select id=select1>
+ <optgroup label="options" id=optgroup1>
+ <option value="option1" id=option1 selected>option1
+</select>
+<select disabled id=select2>
+ <optgroup label="options" disabled id=optgroup2>
+ <option value="option2" disabled id=option2>option2
+</select>
+<textarea id=textarea1>textarea1</textarea>
+<textarea disabled id=textarea2>textarea2</textarea>
+<fieldset id=fieldset1></fieldset>
+<fieldset disabled id=fieldset2>
+ <legend><input type=checkbox id=club></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>
+<label disabled></label>
+<object disabled></object>
+<output disabled></output>
+<img disabled/>
+<meter disabled></meter>
+<progress disabled></progress>
+
+<script>
+ testSelectorIdsMatch(":disabled", ["button2", "input2", "select2", "optgroup2", "option2", "textarea2", "fieldset2", "clubname", "clubnum"], "':disabled' should match only disabled elements");
+
+ document.getElementById("button2").removeAttribute("disabled");
+ testSelectorIdsMatch(":disabled", ["input2", "select2", "optgroup2", "option2", "textarea2", "fieldset2", "clubname", "clubnum"], "':disabled' should not match elements whose disabled attribute has been removed");
+
+ document.getElementById("button1").setAttribute("disabled", "disabled");
+ testSelectorIdsMatch(":disabled", ["button1", "input2", "select2", "optgroup2", "option2", "textarea2", "fieldset2", "clubname", "clubnum"], "':disabled' should also match elements whose disabled attribute has been set");
+
+ document.getElementById("button1").setAttribute("disabled", "disabled");
+ testSelectorIdsMatch(":disabled", ["button1", "input2", "select2", "optgroup2", "option2", "textarea2", "fieldset2", "clubname", "clubnum"], "':disabled' should also match elements whose disabled attribute has been set twice");
+
+ document.getElementById("input2").setAttribute("type", "submit"); // change input type to submit
+ testSelectorIdsMatch(":disabled", ["button1", "input2", "select2", "optgroup2", "option2", "textarea2", "fieldset2", "clubname", "clubnum"], "':disabled' should also match disabled elements whose type has changed");
+
+ var input = document.createElement("input");
+ input.setAttribute("disabled", "disabled");
+ testSelectorIdsMatch(":disabled", ["button1", "input2", "select2", "optgroup2", "option2", "textarea2", "fieldset2", "clubname", "clubnum"], "':disabled' should not match elements not in the document");
+
+ var fieldset = document.createElement("fieldset");
+ fieldset.id = "fieldset_nested";
+ fieldset.innerHTML = `
+ <input id=input_nested>
+ <button id=button_nested>button nested</button>
+ <select id=select_nested>
+ <optgroup label="options" id=optgroup_nested>
+ <option value="options" id=option_nested>option nested</option>
+ </optgroup>
+ </select>
+ <textarea id=textarea_nested>textarea nested</textarea>
+ <object id=object_nested></object>
+ <output id=output_nested></output>
+ <fieldset id=fieldset_nested2>
+ <input id=input_nested2>
+ </fieldset>
+ `;
+ document.getElementById("fieldset2").appendChild(fieldset);
+ testSelectorIdsMatch("#fieldset2 :disabled", ["clubname", "clubnum", "fieldset_nested", "input_nested", "button_nested", "select_nested", "textarea_nested", "fieldset_nested2", "input_nested2"], "':disabled' should match elements that are appended to a disabled fieldset dynamically");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/enabled.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/enabled.html
new file mode 100644
index 0000000000..0ad0e1b402
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/enabled.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:enabled)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org" id=link1>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes" id=link2>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<div id="log"></div>
+<a id=link3></a>
+<area id=link4></area>
+<link id=link5></link>
+<a href="http://www.w3.org" id=link6></a>
+<area href="http://www.w3.org" id=link7></area>
+<link href="http://www.w3.org" id=link8></link>
+<button id=button1>button1</button>
+<button id=button2 disabled>button2</button>
+<input id=input1>
+<input id=input2 disabled>
+<select id=select1>
+ <optgroup label="options" id=optgroup1>
+ <option value="option1" id=option1 selected>option1
+</select>
+<select disabled id=select2>
+ <optgroup label="options" disabled id=optgroup2>
+ <option value="option2" disabled id=option2>option2
+</select>
+<textarea id=textarea1>textarea1</textarea>
+<textarea disabled id=textarea2>textarea2</textarea>
+<form>
+ <p><input type=submit contextmenu=formmenu id=submitbutton></p>
+ <menu type=context id=formmenu>
+ <!-- historical; these should *not* match -->
+ <menuitem command="submitbutton" default id=menuitem1>
+ <menuitem command="resetbutton" disabled id=menuitem2>
+ </menu>
+</form>
+<fieldset id=fieldset1></fieldset>
+<fieldset disabled id=fieldset2></fieldset>
+
+<script>
+ testSelectorIdsMatch(":enabled", ["button1", "input1", "select1", "optgroup1", "option1", "textarea1", "submitbutton", "fieldset1"], "':enabled' elements that are not disabled");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/focus-autofocus.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/focus-autofocus.html
new file mode 100644
index 0000000000..80a75bb99e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/focus-autofocus.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:focus for autofocus)</title>
+<link rel="author" title="Kent Tamura" href="mailto:tkent@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes">
+<link rel=help href="https://html.spec.whatwg.org/multipage/forms.html#autofocusing-a-form-control:-the-autofocus-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+// This test can't be merged to focus.html because element.focus() may affect
+// autofocus behavior.
+var autofocusTest = async_test(":focus selector should work with an autofocused element.");
+var input = document.createElement("input");
+input.autofocus = true;
+input.addEventListener("focus", function() {
+ autofocusTest.step(function() {
+ assert_array_equals(document.querySelectorAll(":focus"), [input])
+ autofocusTest.done();
+ });
+}, false);
+document.body.appendChild(input);
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/focus-iframe.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/focus-iframe.html
new file mode 100644
index 0000000000..a269f1c671
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/focus-iframe.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:focus)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<input id="inputiframe" type=text value="foobar" />
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/focus.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/focus.html
new file mode 100644
index 0000000000..a319b24ef0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/focus.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:focus)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org" id=link1>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes" id=link2>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<body id=body tabindex=0>
+ <div id="log"></div>
+ <button id=button1 type=submit>button1</button>
+ <input id=input1>
+ <input id=input2 disabled>
+ <textarea id=textarea1>textarea1</textarea>
+ <input type=checkbox id=checkbox1 checked>
+ <input type=radio id=radio1 checked>
+ <div tabindex=0 id=div1>hello</div>
+ <div contenteditable id=div2>content</div>
+ <iframe src="focus-iframe.html" id=iframe></iframe>
+
+ <script>
+ setup({explicit_done: true});
+
+ onload = function() {
+ if (document.hasFocus() || frames[0].document.hasFocus()) {
+ run_test()
+ } else {
+ window.onfocus = run_test;
+ }
+ }
+
+ function run_test() {
+ document.getElementById("input1").focus(); // set the focus on input1
+ testSelectorIdsMatch(":focus", ["input1"], "input1 has the focus");
+
+ document.getElementById("div1").focus();
+ testSelectorIdsMatch(":focus", ["div1"], "tabindex attribute makes the element focusable");
+
+ document.getElementById("div2").focus();
+ testSelectorIdsMatch(":focus", ["div2"], "editable elements are focusable");
+
+ document.body.focus();
+ testSelectorIdsMatch(":focus", ["body"], "':focus' matches focussed body with tabindex");
+
+ document.getElementById("iframe").contentDocument.getElementById("inputiframe").focus();
+ testSelectorIdsMatch(":focus", [], "':focus' doesn't match focused elements in iframe");
+
+ done();
+ }
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-radio-group-ref.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-radio-group-ref.html
new file mode 100644
index 0000000000..68d95fe240
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-radio-group-ref.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test reference</title>
+<style>
+label[for="two"] {
+ background: green;
+}
+</style>
+<form>
+ <input type="radio" name="a" id="one" value="1">
+ <label for="one">One</label>
+
+ <input type="radio" name="a" id="two" value="2" checked>
+ <label for="two">Two</label>
+
+ <input type="radio" name="a" id="three" value="3">
+ <label for="three">Three</label>
+
+ <input type="radio" name="a" id="four" value="4">
+ <label for="four">Four</label>
+</form>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-radio-group.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-radio-group.html
new file mode 100644
index 0000000000..a49b957021
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-radio-group.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>:indeterminate and input type=radio</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=1861346">
+<link rel="match" href="indeterminate-radio-group-ref.html">
+<style>
+input:checked + label {
+ background: green;
+}
+input:indeterminate + label {
+ background: red;
+}
+</style>
+<form>
+ <input type="radio" name="a" id="one" value="1">
+ <label for="one">One</label>
+
+ <input type="radio" name="a" id="two" value="2" checked>
+ <label for="two">Two</label>
+
+ <input type="radio" name="a" id="three" value="3">
+ <label for="three">Three</label>
+
+ <input type="radio" name="a" id="four" value="4">
+ <label for="four">Four</label>
+</form>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-radio.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-radio.html
new file mode 100644
index 0000000000..4a7b2d6ece
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-radio.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>:indeterminate and input type=radio</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style type="text/css">
+#test {
+ color: green;
+}
+input:indeterminate + #test {
+ color: red;
+}
+</style>
+<input type="radio" name="radios">
+<div id="test"></div>
+<input type="radio" name="radios" checked>
+<script type="text/javascript">
+test(function() {
+ document.getElementsByTagName("input")[0].indeterminate = true;
+ var target = document.getElementById("test");
+ var val = getComputedStyle(target, null).getPropertyValue("color");
+ assert_equals(val, "rgb(0, 128, 0)",
+ "The indeterminate IDL attribute should not cause the " +
+ ":indeterminate pseudo-class to match on input type=radio");
+})
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-type-change.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-type-change.html
new file mode 100644
index 0000000000..b3e4cce302
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate-type-change.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Selector: pseudo-class :indeterminate input type change</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#pseudo-classes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ span { color: red }
+ :indeterminate + span { color: green }
+</style>
+<input id="indeterminate" type="text">
+<span id="sibling">This text should be green.</span>
+<script>
+ test(() => {
+ assert_equals(getComputedStyle(sibling).color, "rgb(255, 0, 0)",
+ "Not matching :indeterminate for type=text");
+
+ indeterminate.type = "radio";
+
+ assert_equals(getComputedStyle(sibling).color, "rgb(0, 128, 0)",
+ "Matching :indeterminate for type=radio");
+ }, "Evaluation of :indeterminate changes on input type change.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate.html
new file mode 100644
index 0000000000..df04846676
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/indeterminate.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:indeterminate)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org" id=link1>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes" id=link2>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<div id="log"></div>
+<input type=checkbox id=checkbox1>
+<input type=checkbox id=checkbox2>
+<input type=radio id=radio1 checked>
+<input type=radio name=radiogroup id=radio2>
+<input type=radio name=radiogroup id=radio3>
+<input type=radio name=group2 id=radio4>
+<input type=radio name=group2 id=radio5>
+<progress id="progress1"></progress>
+<progress id="progress2" value=10></progress>
+
+<script>
+ testSelectorIdsMatch(":indeterminate", ["radio2", "radio3", "radio4", "radio5", "progress1"], "':progress' matches <input>s radio buttons whose radio button group contains no checked input and <progress> elements without value attribute");
+
+ document.getElementById("radio2").setAttribute("checked", "checked");
+ testSelectorIdsMatch(":indeterminate", ["radio4", "radio5", "progress1"], "dynamically check a radio input in a radio button group");
+
+ document.getElementById("radio4").click();
+ testSelectorIdsMatch(":indeterminate", ["progress1"], "click on radio4 which is in the indeterminate state");
+
+ document.getElementById("progress1").setAttribute("value", "20");
+ testSelectorIdsMatch(":indeterminate", [], "adding a value to progress1 should put it in a determinate state");
+
+ document.getElementById("progress2").removeAttribute("value");
+ testSelectorIdsMatch(":indeterminate", ["progress2"], "removing progress2's value should put it in an indeterminate state");
+
+ document.getElementById("checkbox1").indeterminate = true; // set checkbox1 in the indeterminate state
+ testSelectorIdsMatch(":indeterminate", ["checkbox1", "progress2"], "':progress' also matches <input> checkbox whose indeterminate IDL is set to true");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.js b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.js
new file mode 100644
index 0000000000..b5d9898a64
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.js
@@ -0,0 +1,75 @@
+test(t => {
+ const input = document.body.appendChild(document.createElement("input"));
+ t.add_cleanup(() => input.remove());
+ input.type = "checkbox";
+ input.switch = true;
+ input.indeterminate = true;
+
+ assert_false(input.matches(":indeterminate"));
+}, "Switch control does not match :indeterminate");
+
+test(t => {
+ const input = document.body.appendChild(document.createElement("input"));
+ t.add_cleanup(() => input.remove());
+ input.type = "checkbox";
+ input.switch = true;
+ input.indeterminate = true;
+
+ assert_false(input.matches(":indeterminate"));
+
+ input.switch = false;
+ assert_true(input.matches(":indeterminate"));
+}, "Checkbox that is no longer a switch control does match :indeterminate");
+
+test(t => {
+ const input = document.body.appendChild(document.createElement("input"));
+ t.add_cleanup(() => input.remove());
+ input.type = "checkbox";
+ input.indeterminate = true;
+
+ assert_true(input.matches(":indeterminate"));
+
+ input.setAttribute("switch", "blah");
+ assert_false(input.matches(":indeterminate"));
+}, "Checkbox that becomes a switch control does not match :indeterminate");
+
+test(t => {
+ const input = document.body.appendChild(document.createElement("input"));
+ t.add_cleanup(() => input.remove());
+ input.type = "checkbox";
+ input.indeterminate = true;
+
+ assert_true(document.body.matches(":has(:indeterminate)"));
+
+ input.switch = true;
+ assert_false(document.body.matches(":has(:indeterminate)"));
+}, "Parent of a checkbox that becomes a switch control does not match :has(:indeterminate)");
+
+test(t => {
+ const input = document.body.appendChild(document.createElement("input"));
+ t.add_cleanup(() => input.remove());
+ input.type = "checkbox";
+ input.switch = true
+ input.checked = true;
+
+ assert_true(document.body.matches(":has(:checked)"));
+
+ input.switch = false;
+ assert_true(document.body.matches(":has(:checked)"));
+
+ input.checked = false;
+ assert_false(document.body.matches(":has(:checked)"));
+}, "Parent of a switch control that becomes a checkbox continues to match :has(:checked)");
+
+test(t => {
+ const input = document.body.appendChild(document.createElement("input"));
+ t.add_cleanup(() => input.remove());
+ input.type = "checkbox";
+ input.switch = true;
+ input.indeterminate = true;
+ assert_false(input.matches(":indeterminate"));
+ input.type = "text";
+ input.removeAttribute("switch");
+ input.type = "checkbox";
+ assert_true(input.matches(":indeterminate"));
+}, "A switch control that becomes a checkbox in a roundabout way");
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/inrange-outofrange-type-change.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/inrange-outofrange-type-change.html
new file mode 100644
index 0000000000..9c1be9ca27
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/inrange-outofrange-type-change.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Selector: pseudo-classes (:in-range, :out-of-range) input type change</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#pseudo-classes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ span {
+ color: red;
+ }
+ #t1:in-range + span {
+ color: green;
+ }
+ #t2:out-of-range + span {
+ color: green;
+ }
+</style>
+<input id="t1" type="text" min="0" max="10" value="5">
+<span id="sibling1">This text should be green.</span>
+<input id="t2" type="text" min="0" max="10" value="50">
+<span id="sibling2">This text should be green.</span>
+<script>
+ test(() => {
+ assert_equals(getComputedStyle(sibling1).color, "rgb(255, 0, 0)",
+ "Not matching :in-range for type=text");
+
+ t1.type = "number";
+
+ assert_equals(getComputedStyle(sibling1).color, "rgb(0, 128, 0)",
+ "Matching :in-range for type=number");
+ }, "Evaluation of :in-range changes for input type change.");
+
+ test(() => {
+ assert_equals(getComputedStyle(sibling2).color, "rgb(255, 0, 0)",
+ "Not matching :out-of-range for type=text");
+
+ t2.type = "number";
+
+ assert_equals(getComputedStyle(sibling2).color, "rgb(0, 128, 0)",
+ "Matching :in-range for type=number");
+ }, "Evaluation of :out-of-range changes for input type change.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/inrange-outofrange.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/inrange-outofrange.html
new file mode 100644
index 0000000000..e9acbb3741
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/inrange-outofrange.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:in-range, :out-of-range)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org" id="link1">
+<link rel="author" title="Chris Rebert" href="http://chrisrebert.com" id="link2">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#selector-in-range" id="link3">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#selector-out-of-range" id="link4">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<div id="log"></div>
+<input type=number value=0 min=0 max=10 id=number1>
+<input type=number value=0 min=0 max=10 id=number2 disabled>
+<input type=number value=0 min=1 max=10 id=number3>
+<input type=number value=11 min=0 max=10 id=number4>
+<input type=number value=0 min=0 max=10 id=number5 readonly>
+
+<input type="date" min="2005-10-10" max="2020-10-10" value="2010-10-10" id="datein">
+<input type="date" min="2010-10-10" max="2020-10-10" value="2005-10-10" id="dateunder">
+<input type="date" min="2010-10-10" max="2020-10-10" value="2030-10-10" id="dateover">
+
+<input type="time" min="01:00:00" max="05:00:00" value="02:00:00" id="timein">
+<input type="time" min="02:00:00" max="05:00:00" value="01:00:00" id="timeunder">
+<input type="time" min="02:00:00" max="05:00:00" value="07:00:00" id="timeover">
+
+<input type="week" min="2016-W05" max="2016-W10" value="2016-W07" id="weekin">
+<input type="week" min="2016-W05" max="2016-W10" value="2016-W02" id="weekunder">
+<input type="week" min="2016-W05" max="2016-W10" value="2016-W26" id="weekover">
+
+<input type="month" min="2000-04" max="2000-09" value="2000-06" id="monthin">
+<input type="month" min="2000-04" max="2000-09" value="2000-02" id="monthunder">
+<input type="month" min="2000-04" max="2000-09" value="2000-11" id="monthover">
+
+<input type="datetime-local" min="2008-03-12T23:59:59" max="2015-02-13T23:59:59" value="2012-11-28T23:59:59" id="datetimelocalin">
+<input type="datetime-local" min="2008-03-12T23:59:59" max="2015-02-13T23:59:59" value="2008-03-01T23:59:59" id="datetimelocalunder">
+<input type="datetime-local" min="2008-03-12T23:59:59" max="2015-02-13T23:59:59" value="2016-01-01T23:59:59" id="datetimelocalover">
+
+<!-- None of the following have range limitations since they have neither min nor max attributes -->
+<input type="number" value="0" id="numbernolimit">
+<input type="date" value="2010-10-10" id="datenolimit">
+<input type="time" value="02:00:00" id="timenolimit">
+<input type="week" value="2016-W07" id="weeknolimit">
+<input type="month" value="2000-06" id="monthnolimit">
+<input type="datetime-local" value="2012-11-28T23:59:59" id="datetimelocalnolimit">
+
+<!-- range inputs have default minimum of 0 and default maximum of 100 -->
+<input type="range" value="50" id="range0">
+
+<!-- range input's value gets immediately clamped to the nearest boundary point -->
+<input type="range" min="2" max="7" value="5" id="range1">
+<input type="range" min="2" max="7" value="1" id="range2">
+<input type="range" min="2" max="7" value="9" id="range3">
+
+<!-- None of the following input types can have range limitations -->
+<input min="1" value="0" type="text">
+<input min="1" value="0" type="search">
+<input min="1" value="0" type="url">
+<input min="1" value="0" type="tel">
+<input min="1" value="0" type="email">
+<input min="1" value="0" type="password">
+<input min="1" value="#000000" type="color">
+<input min="1" value="0" type="checkbox">
+<input min="1" value="0" type="radio">
+<input min="1" value="0" type="file">
+<input min="1" value="0" type="submit">
+<input min="1" value="0" type="image">
+<!-- The following types are also barred from constraint validation -->
+<input min="1" value="0" type="hidden">
+<input min="1" value="0" type="button">
+<input min="1" value="0" type="reset">
+
+<script>
+ testSelectorIdsMatch(":in-range", ["number1", "datein", "timein", "weekin", "monthin", "datetimelocalin", "range0", "range1", "range2", "range3"], "':in-range' matches all elements that are candidates for constraint validation, have range limitations, and that are neither suffering from an underflow nor suffering from an overflow");
+
+ testSelectorIdsMatch(":out-of-range", ["number3", "number4", "dateunder", "dateover", "timeunder", "timeover", "weekunder", "weekover", "monthunder", "monthover", "datetimelocalunder", "datetimelocalover"], "':out-of-range' matches all elements that are candidates for constraint validation, have range limitations, and that are either suffering from an underflow or suffering from an overflow");
+
+ document.getElementById("number1").value = -10;
+ testSelectorIdsMatch(":in-range", ["datein", "timein", "weekin", "monthin", "datetimelocalin", "range0", "range1", "range2", "range3"], "':in-range' update number1's value < min");
+ testSelectorIdsMatch(":out-of-range", ["number1", "number3", "number4", "dateunder", "dateover", "timeunder", "timeover", "weekunder", "weekover", "monthunder", "monthover", "datetimelocalunder", "datetimelocalover"], "':out-of-range' update number1's value < min");
+
+ document.getElementById("number3").min = 0;
+ testSelectorIdsMatch(":in-range", ["number3", "datein", "timein", "weekin", "monthin", "datetimelocalin", "range0", "range1", "range2", "range3"], "':in-range' update number3's min < value");
+ testSelectorIdsMatch(":out-of-range", ["number1", "number4", "dateunder", "dateover", "timeunder", "timeover", "weekunder", "weekover", "monthunder", "monthover", "datetimelocalunder", "datetimelocalover"], "':out-of-range' update number3's min < value");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/invalid-after-clone.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/invalid-after-clone.html
new file mode 100644
index 0000000000..92345602a8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/invalid-after-clone.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset="utf-8">
+<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>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<input>
+<textarea></textarea>
+<script>
+promise_test(async () => {
+ for (let tag of ["input", "textarea"]) {
+ let element = document.querySelector(tag);
+ await test_driver.send_keys(element, 'something');
+
+ assert_true(element.validity.valid, tag + ' should be valid');
+
+ element.maxLength = 0;
+ assert_true(element.matches(":invalid"), tag + ' should match :invalid');
+ assert_false(element.validity.valid, tag + ' should be invalid');
+
+ let clone = element.cloneNode(true);
+ assert_true(clone.matches(":invalid"), tag + ' clone should match :invalid');
+ assert_false(clone.validity.valid, tag + 'clone should be invalid');
+ }
+}, 'Cloned invalid inputs / textareas with interactive changes get their validity state copied correctly');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/link.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/link.html
new file mode 100644
index 0000000000..e9733eca70
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/link.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:link)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org" id=link1>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes" id=link2>
+<link rel=stylesheet href="non-existent.css" id=link3>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<div id="log"></div>
+<a id=link4></a>
+<area id=link5></area>
+<link id=link6></link>
+<a href="http://www.w3.org" id=link7></a>
+<area href="http://www.w3.org" id=link8></area>
+<link href="http://www.w3.org" id=link9></link>
+<a href="http://[" id=link10></a>
+
+<script>
+ testSelectorIdsMatch(":link", ["link7", "link8", "link10"], "Only <a>s and <area>s that have a href attribute match ':link'");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/placeholder-shown-type-change.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/placeholder-shown-type-change.html
new file mode 100644
index 0000000000..206ae80c75
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/placeholder-shown-type-change.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Selector: pseudo-class :placeholder-shown input type change</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#pseudo-classes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ span {
+ color: red;
+ }
+ :placeholder-shown + span {
+ color: green;
+ }
+</style>
+<input id="input" type="submit" placeholder="placeholder"></input>
+<span id="sibling">This text should be green.</span>
+<script>
+ test(() => {
+ assert_equals(getComputedStyle(sibling).color, "rgb(255, 0, 0)",
+ "Not matching :placeholder-shown for type=submit");
+
+ input.type = "text";
+ assert_equals(getComputedStyle(sibling).color, "rgb(0, 128, 0)",
+ "Matching :placeholder-shown for type=text");
+ }, "Evaluation of :placeholder-shown changes for input type change.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/readwrite-readonly-type-change.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/readwrite-readonly-type-change.html
new file mode 100644
index 0000000000..90ef1d25d4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/readwrite-readonly-type-change.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Selector: pseudo-classes (:read-write, :read-only) input type change</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#pseudo-classes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ span {
+ color: red;
+ background-color: pink;
+ }
+ :required + span {
+ color: green;
+ }
+ :not(:optional) + span {
+ background-color: lime;
+ }
+</style>
+<input id="hiddenInput" type="hidden" required>
+<span id="sibling">This text should be green on lime background.</span>
+<script>
+ test(() => {
+ assert_equals(getComputedStyle(sibling).color, "rgb(255, 0, 0)",
+ "Not matching :required for type=hidden");
+ assert_equals(getComputedStyle(sibling).backgroundColor, "rgb(255, 192, 203)",
+ "Matching :optional for type=hidden");
+
+ hiddenInput.type = "text";
+
+ assert_equals(getComputedStyle(sibling).color, "rgb(0, 128, 0)",
+ "Matching :required for type=text");
+ assert_equals(getComputedStyle(sibling).backgroundColor, "rgb(0, 255, 0)",
+ "Matching :not(:optional) for type=text");
+ }, "Evaluation of :required and :optional changes for input type change.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/readwrite-readonly.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/readwrite-readonly.html
new file mode 100644
index 0000000000..fb8a5b9ad7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/readwrite-readonly.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:read-write, :read-only)</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes" id=link2>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<div id="log"></div>
+
+<div id=set0>
+<!-- The readonly attribute does not apply to the following input types -->
+<input id=checkbox1 type=checkbox>
+<input id=hidden1 type=hidden value=abc>
+<input id=range1 type=range>
+<input id=color1 type=color>
+<input id=radio1 type=radio>
+<input id=file1 type=file>
+<input id=submit1 type=submit>
+<input id=image1 type=image>
+<input id=button1 type=button value="Button">
+<input id=reset1 type=reset>
+</div>
+
+<div id=set1>
+<input id=input1>
+<input id=input2 readonly>
+<input id=input3 disabled>
+<input id=input4 type=checkbox>
+<input id=input5 type=checkbox readonly>
+</div>
+
+<div id=set2>
+<textarea id=textarea1>textarea1</textarea>
+<textarea readonly id=textarea2>textarea2</textarea>
+</div>
+
+<div id=set3>
+<textarea id=textarea3>textarea3</textarea>
+<textarea disabled id=textarea4>textarea4</textarea>
+</div>
+
+<div id=set4>
+<p id=p1>paragraph1.</p>
+<p id=p2 contenteditable>paragraph2.</p>
+</div>
+
+<div id=set5>
+ <div id=cd1 contenteditable>
+ <p id=p3></p>
+ <input id=ci1 readonly>
+ <input id=ci2 disabled>
+ <input id=ci3>
+ <input id=ci4>
+ <textarea id=ct1 readonly></textarea>
+ <textarea id=ct2 disabled></textarea>
+ <textarea id=ct3></textarea>
+ <textarea id=ct4></textarea>
+ </div>
+</div>
+
+<script>
+ testSelectorIdsMatch("#set0 :read-write", [], "The :read-write pseudo-class must not match input elements to which the readonly attribute does not apply");
+
+ testSelectorIdsMatch("#set0 :read-only", ["checkbox1", "hidden1", "range1", "color1", "radio1", "file1", "submit1", "image1", "button1", "reset1"], "The :read-only pseudo-class must match input elements to which the readonly attribute does not apply");
+
+ testSelectorIdsMatch("#set1 :read-write", ["input1"], "The :read-write pseudo-class must match input elements to which the readonly attribute applies, and that are mutable");
+
+ testSelectorIdsMatch("#set1 :read-only", ["input2", "input3", "input4", "input5"], "The :read-only pseudo-class must not match input elements to which the readonly attribute applies, and that are mutable");
+
+ document.getElementById("input1").setAttribute("readonly", "readonly");
+ testSelectorIdsMatch("#set1 :read-write", [], "The :read-write pseudo-class must not match input elements after the readonly attribute has been added");
+
+ testSelectorIdsMatch("#set1 :read-only", ["input1", "input2", "input3", "input4", "input5"], "The :read-only pseudo-class must match input elements after the readonly attribute has been added");
+
+ document.getElementById("input1").removeAttribute("readonly");
+ testSelectorIdsMatch("#set1 :read-write", ["input1"], "The :read-write pseudo-class must not match input elements after the readonly attribute has been removed");
+
+ testSelectorIdsMatch("#set1 :read-only", ["input2", "input3", "input4", "input5"], "The :read-only pseudo-class must match input elements after the readonly attribute has been removed");
+
+ document.getElementById("input1").disabled = true;
+ testSelectorIdsMatch("#set1 :read-write", [], "The :read-write pseudo-class must not match input elements after the disabled attribute has been added");
+
+ testSelectorIdsMatch("#set1 :read-only", ["input1", "input2", "input3", "input4", "input5"], "The :read-only pseudo-class must match input elements after the disabled attribute has been added");
+
+ document.getElementById("input1").disabled = false;
+
+ testSelectorIdsMatch("#set1 :read-write", ["input1"], "The :read-write pseudo-class must match input elements after the disabled attribute has been removed");
+
+ testSelectorIdsMatch("#set1 :read-only", ["input2", "input3", "input4", "input5"], "The :read-only pseudo-class must not match input elements after the disabled attribute has been removed");
+
+ testSelectorIdsMatch("#set2 :read-write", ["textarea1"], "The :read-write pseudo-class must match textarea elements that do not have a readonly attribute, and that are not disabled");
+
+ testSelectorIdsMatch("#set2 :read-only", ["textarea2"], "The :read-only pseudo-class must match textarea elements that have a readonly attribute, or that are disabled");
+
+ document.getElementById("textarea1").setAttribute("readonly", "readonly");
+ testSelectorIdsMatch("#set2 :read-write", [], "The :read-write pseudo-class must match textarea elements after the readonly attribute has been added");
+
+ testSelectorIdsMatch("#set2 :read-only", ["textarea1", "textarea2"], "The :read-only pseudo-class must match textarea elements after the readonly attribute has been added");
+
+ testSelectorIdsMatch("#set3 :read-write", ["textarea3"], "The :read-write pseudo-class must not match textarea elements that are disabled");
+
+ testSelectorIdsMatch("#set3 :read-only", ["textarea4"], "The :read-only pseudo-class must match textarea elements that are disabled");
+
+ testSelectorIdsMatch("#set4 :read-write", ["p2"], "The :read-write pseudo-class must match elements that are editable");
+
+ testSelectorIdsMatch("#set4 :read-only", ["p1"], "The :read-only pseudo-class must not match elements that are editable");
+
+ document.designMode = "on";
+
+ testSelectorIdsMatch("#set4 :read-write", ["p1", "p2"], "The :read-write pseudo-class must match elements that are editing hosts");
+
+ testSelectorIdsMatch("#set4 :read-only", [], "The :read-only pseudo-class must not match elements that are editing hosts");
+
+ document.designMode = "off";
+
+ testSelectorIdsMatch("#set5 :read-write", ["cd1", "p3", "ci3", "ci4", "ct3", "ct4"], "The :read-write pseudo-class must match elements that are inside editing hosts, but not match inputs and textareas inside that aren't");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/required-optional-hidden.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/required-optional-hidden.html
new file mode 100644
index 0000000000..fe3d6e2f42
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/required-optional-hidden.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Selector: pseudo-classes (:required, :optional) for hidden input</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#pseudo-classes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ span {
+ color: red;
+ background-color: pink;
+ }
+ :required + span {
+ color: green;
+ }
+ :not(:optional) + span {
+ background-color: lime;
+ }
+</style>
+<input id="hiddenInput" type="hidden" required>
+<span id="sibling">This text should be green on lime background.</span>
+<script>
+ test(() => {
+ assert_equals(getComputedStyle(sibling).color, "rgb(255, 0, 0)",
+ "Not matching :required for type=hidden");
+ assert_equals(getComputedStyle(sibling).backgroundColor, "rgb(255, 192, 203)",
+ "Matching :optional for type=hidden");
+
+ hiddenInput.type = "text";
+
+ assert_equals(getComputedStyle(sibling).color, "rgb(0, 128, 0)",
+ "Matching :required for type=text");
+ assert_equals(getComputedStyle(sibling).backgroundColor, "rgb(0, 255, 0)",
+ "Matching :not(:optional) for type=text");
+ }, "Evaluation of :required and :optional changes for input type change.");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/required-optional.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/required-optional.html
new file mode 100644
index 0000000000..f06fdfa1e0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/required-optional.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:required, :optional)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org" id=link1>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes" id=link2>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<div id="log"></div>
+<input type=text id=text1 value="foobar" required>
+<input type=text id=text2 required>
+<input type=text id=text3>
+<select id=select1 required>
+ <optgroup label="options" id=optgroup1>
+ <option value="option1" id=option1>option1
+</select>
+<select id=select2>
+ <optgroup label="options" id=optgroup2>
+ <option value="option2" id=option2>option2
+</select>
+<textarea required id=textarea1>textarea1</textarea>
+<textarea id=textarea2>textarea2</textarea>
+
+<script>
+ testSelectorIdsMatch(":required", ["text1", "text2", "select1", "textarea1"], "':required' matches required <input>s, <select>s and <textarea>s");
+ testSelectorIdsMatch(":optional", ["text3", "select2", "textarea2"], "':optional' matches elements <input>s, <select>s and <textarea>s that are not required");
+
+ document.getElementById("text1").removeAttribute("required");
+ testSelectorIdsMatch(":required", ["text2", "select1", "textarea1"], "':required' doesn't match elements whose required attribute has been removed");
+ testSelectorIdsMatch(":optional", ["text1", "text3", "select2", "textarea2"], "':optional' matches elements whose required attribute has been removed");
+
+ document.getElementById("select2").setAttribute("required", "required");
+ testSelectorIdsMatch(":required", ["text2", "select1", "select2", "textarea1"], "':required' matches elements whose required attribute has been added");
+ testSelectorIdsMatch(":optional", ["text1", "text3", "textarea2"], "':optional' doesn't match elements whose required attribute has been added");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/utils.js b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/utils.js
new file mode 100644
index 0000000000..7a2fb77f10
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/utils.js
@@ -0,0 +1,20 @@
+function getElementsByIds(ids) {
+ var result = [];
+ ids.forEach(function(id) {
+ result.push(document.getElementById(id));
+ });
+ return result;
+}
+
+function testSelectorIdsMatch(selector, ids, testName) {
+ test(function(){
+ var elements = document.querySelectorAll(selector);
+ assert_array_equals([...elements], getElementsByIds(ids));
+ }, testName);
+}
+
+function testSelectorElementsMatch(selector, elements, testName) {
+ test(function(){
+ assert_array_equals([...document.querySelectorAll(selector)], elements);
+ }, testName);
+}
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/valid-invalid-fieldset-disconnected.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/valid-invalid-fieldset-disconnected.html
new file mode 100644
index 0000000000..6ad329438a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/valid-invalid-fieldset-disconnected.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:valid, :invalid) on disconnected fieldset element</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/semantics-other.html#selector-valid">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<fieldset id=fieldset1>
+ <input id=input1>
+</fieldset>
+
+<fieldset id=fieldset2>
+ <select id=select1 required multiple>
+ <option>foo
+ </select>
+</fieldset>
+
+<script>
+test(() => {
+ const fieldset = document.querySelector("#fieldset1");
+ const input = document.querySelector("#input1");
+
+ assert_true(fieldset.matches(":valid"));
+ assert_false(fieldset.matches(":invalid"));
+
+ fieldset.remove();
+ input.setCustomValidity("foo");
+
+ assert_false(fieldset.matches(":valid"));
+ assert_true(fieldset.matches(":invalid"));
+
+ input.setCustomValidity("");
+
+ assert_true(fieldset.matches(":valid"));
+ assert_false(fieldset.matches(":invalid"));
+}, "<input> element becomes invalid inside disconnected <fieldset>");
+
+test(() => {
+ const fieldset = document.querySelector("#fieldset2");
+ const select = document.querySelector("#select1");
+
+ assert_false(fieldset.matches(":valid"));
+ assert_true(fieldset.matches(":invalid"));
+
+ fieldset.remove();
+ select.required = false;
+
+ assert_true(fieldset.matches(":valid"));
+ assert_false(fieldset.matches(":invalid"));
+
+ select.required = true;
+ select.firstElementChild.selected = true;
+
+ assert_true(fieldset.matches(":valid"));
+ assert_false(fieldset.matches(":invalid"));
+}, "<select> element becomes valid inside disconnected <fieldset>");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/valid-invalid.html b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/valid-invalid.html
new file mode 100644
index 0000000000..d93407707f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/selectors/pseudo-classes/valid-invalid.html
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:valid, :invalid)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org" id=link1>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes" id=link2>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<style>
+ #styleTests form, #styleTests fieldset, #failExample { background-color:red; }
+ #styleTests > :valid, #validExample { background-color:green; }
+ #styleTests > :invalid, #invalidExample { background-color:lime; }
+</style>
+</head>
+<body>
+<div id="log"></div>
+<div id='simpleConstraints'>
+ <input type=text id=text1 value="foobar" required>
+ <input type=text id=text2 required>
+</div>
+<div id='FormSelection'>
+ <form id=form1>
+ <input type=text id=text3 value="foobar" required>
+ </form>
+ <form id=form2>
+ <input type=text id=text4 required>
+ </form>
+</div>
+<div id='FieldSetSelection'>
+ <fieldset id=fieldset1>
+ <input type=text id=text5 value="foobar" required>
+ </fieldset>
+ <fieldset id=fieldset2>
+ <input type=text id=text6 required>
+ </fieldset>
+</div>
+<div id='patternConstraints'>
+ <input type=text id=text7 value="AAA" pattern="[0-9][A-Z]{3}">
+ <input type=text id=text8 value="0AAA" pattern="[0-9][A-Z]{3}">
+</div>
+<div id='numberConstraints'>
+ <input type=number id=number1 value=0 min=1>
+ <input type=number id=number2 value=1 min=1>
+</div>
+<div id='styleTests'>
+ <form>
+ </form>
+ <form>
+ <input type=text min=8 value=4>
+ </form>
+ <form>
+ <input type=number min=8 value=4>
+ </form>
+ <fieldset>
+ </fieldset>
+ <fieldset>
+ <input type=text min=8 value=4>
+ </fieldset>
+ <fieldset>
+ <input type=number min=8 value=4>
+ </fieldset>
+ <div id='validExample'></div>
+ <div id='invalidExample'></div>
+ <div id='failExample'></div>
+</div>
+<script>
+ testSelectorIdsMatch("#simpleConstraints :valid", ["text1"], "':valid' matches elements that satisfy their constraints");
+
+ testSelectorIdsMatch("#FormSelection :valid", ["form1", "text3"], "':valid' matches form elements that are not the form owner of any elements that themselves are candidates for constraint validation but do not satisfy their constraints");
+
+ testSelectorIdsMatch("#FieldSetSelection :valid", ["fieldset1", "text5"], "':valid' matches fieldset elements that have no descendant elements that themselves are candidates for constraint validation but do not satisfy their constraints");
+
+ testSelectorIdsMatch("#patternConstraints :valid", [ "text8" ], "':valid' matches elements that satisfy their pattern constraints");
+
+ testSelectorIdsMatch("#numberConstraints :valid", [ "number2" ], "':valid' matches elements that satisfy their number constraints");
+
+
+ testSelectorIdsMatch("#simpleConstraints :invalid", ["text2"], "':invalid' matches elements that do not satisfy their simple text constraints");
+
+ testSelectorIdsMatch("#FormSelection :invalid", ["form2", "text4"], "':invalid' matches form elements that are the form owner of one or more elements that themselves are candidates for constraint validation but do not satisfy their constraints");
+
+ testSelectorIdsMatch("#FieldSetSelection :invalid", ["fieldset2", "text6"], "':invalid' matches fieldset elements that have of one or more descendant elements that themselves are candidates for constraint validation but do not satisfy their constraints");
+
+ testSelectorIdsMatch("#patternConstraints :invalid", ["text7"], "':invalid' matches elements that do not satisfy their pattern constraints");
+
+ testSelectorIdsMatch("#numberConstraints :invalid", ["number1"], "':invalid' matches elements that do not satisfy their number constraints");
+
+ document.getElementById("text7").value="0BBB";
+ testSelectorIdsMatch("#patternConstraints :valid", [ "text7", "text8" ], "':valid' matches new elements that satisfy their constraints");
+ testSelectorIdsMatch("#patternConstraints :invalid", [], "':invalid' doesn't match new elements that satisfy their constraints");
+
+ document.getElementById("text8").value="BBB";
+ testSelectorIdsMatch("#patternConstraints :valid", ["text7"], "':valid' doesn't match new elements that do not satisfy their constraints");
+ testSelectorIdsMatch("#patternConstraints :invalid", ["text8"], "':invalid' matches new elements that do not satisfy their constraints");
+
+ function getBGColor(elem) {
+ return getComputedStyle(elem).backgroundColor;
+ }
+
+ function testStyles(type) {
+ var elems = document.querySelectorAll("#styleTests " + type),
+ empty = elems[0],
+ valid = elems[1],
+ invalid = elems[2],
+ validInput = valid.querySelector("input"),
+ invalidInput = invalid.querySelector("input"),
+ expectedValidBGColor = getBGColor(document.getElementById("validExample")),
+ expectedInvalidBGColor = getBGColor(document.getElementById("invalidExample")),
+ expectedFailBGColor = getBGColor(document.getElementById("failExample"));
+
+ test(function() {
+ assert_equals(getBGColor(empty), expectedValidBGColor, "wrong background-color");
+ }, 'empty ' + type + ' correctly styled on page-load');
+
+ test(function() {
+ assert_equals(getBGColor(valid), expectedValidBGColor, "wrong background-color");
+ }, 'valid ' + type + ' correctly styled on page-load');
+ test(function() {
+ assert_equals(getBGColor(invalid), expectedInvalidBGColor, "wrong background-color");
+ }, 'invalid ' + type + ' correctly styled on page-load');
+
+ test(function() {
+ empty.appendChild(validInput.cloneNode());
+ assert_equals(getBGColor(empty), expectedValidBGColor, "wrong background-color");
+ }, 'programmatically adding valid to empty ' + type + ' results in correct style');
+ test(function() {
+ empty.appendChild(invalidInput.cloneNode());
+ assert_equals(getBGColor(empty), expectedInvalidBGColor, "wrong background-color");
+ }, 'programmatically adding invalid to empty ' + type + ' results in correct style');
+
+ validInput.type = "number";
+ invalidInput.type = "text";
+ test(function() {
+ assert_equals(getBGColor(valid), expectedInvalidBGColor, "wrong background-color");
+ }, 'programmatically-invalidated ' + type + ' correctly styled');
+ test(function() {
+ assert_equals(getBGColor(invalid), expectedValidBGColor, "wrong background-color");
+ }, 'programmatically-validated ' + type + ' correctly styled');
+ }
+ test(testStyles.bind(undefined, "form"), ":valid/:invalid styling for <form>");
+ test(testStyles.bind(undefined, "fieldset"), ":valid/:invalid styling for <fieldset>");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/META.yml b/testing/web-platform/tests/html/semantics/tabular-data/META.yml
new file mode 100644
index 0000000000..ce84e4ae4c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/META.yml
@@ -0,0 +1,2 @@
+suggested_reviewers:
+ - tkent-google
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/attributes-common-to-td-and-th-elements/cellIndex.html b/testing/web-platform/tests/html/semantics/tabular-data/attributes-common-to-td-and-th-elements/cellIndex.html
new file mode 100644
index 0000000000..b8449229d5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/attributes-common-to-td-and-th-elements/cellIndex.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>HTMLTableCellElement.cellIndex</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var th = document.createElement("th");
+ assert_true("cellIndex" in th, '"cellIndex" in th');
+ var td = document.createElement("td");
+ assert_true("cellIndex" in td, '"cellIndex" in td');
+}, "cellIndex should exist.")
+test(function() {
+ var th = document.createElement("th");
+ assert_equals(th.cellIndex, -1);
+ var td = document.createElement("td");
+ assert_equals(td.cellIndex, -1);
+}, "For cells without a parent, cellIndex should be -1.")
+test(function() {
+ var table = document.createElement("table");
+ var th = table.appendChild(document.createElement("th"));
+ assert_equals(th.cellIndex, -1);
+ var td = table.appendChild(document.createElement("td"));
+ assert_equals(td.cellIndex, -1);
+}, "For cells whose parent is not a tr, cellIndex should be -1.")
+test(function() {
+ var tr = document.createElementNS("", "tr");
+ var th = tr.appendChild(document.createElement("th"));
+ assert_equals(th.cellIndex, -1);
+ var td = tr.appendChild(document.createElement("td"));
+ assert_equals(td.cellIndex, -1);
+}, "For cells whose parent is not a HTML tr, cellIndex should be -1.")
+test(function() {
+ var tr = document.createElement("tr");
+ var th = tr.appendChild(document.createElement("th"));
+ assert_equals(th.cellIndex, 0);
+ var td = tr.appendChild(document.createElement("td"));
+ assert_equals(td.cellIndex, 1);
+}, "For cells whose parent is a tr, cellIndex should be the index.")
+test(function() {
+ var tr = document.createElement("tr");
+ var th = tr.appendChild(document.createElement("th"));
+ assert_equals(th.cellIndex, 0);
+ tr.appendChild(document.createElement("div"));
+ tr.appendChild(document.createTextNode("Hello World"));
+ var td = tr.appendChild(document.createElement("td"));
+ assert_equals(td.cellIndex, 1)
+}, "For cells whose parent is a tr with non td/th sibling, cellIndex should skip those non td/th siblings.")
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/historical.html b/testing/web-platform/tests/html/semantics/tabular-data/historical.html
new file mode 100644
index 0000000000..a6be56e13d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/historical.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>Historical table features should not be supported</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+function t(property, tagNames) {
+ if (typeof tagNames === "string") {
+ tagNames = [tagNames];
+ }
+ tagNames.forEach(function(tagName) {
+ test(function() {
+ assert_false(property in document.createElement(tagName));
+ }, tagName + '.' + property + ' should not be supported');
+ });
+}
+
+// added in https://github.com/whatwg/html/commit/6db0d8d4e3456140de958c963afe9bb9ec7b6a25
+// removed in https://github.com/whatwg/html/commit/59b7e2466c2b7c5c408a4963b05b13fd808aa07a
+t('onsort', 'table');
+t('sortable', 'table');
+t('stopSorting', 'table');
+t('sorted', 'th');
+t('sort', 'th');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/html-table-section-element.js b/testing/web-platform/tests/html/semantics/tabular-data/html-table-section-element.js
new file mode 100644
index 0000000000..68b68ceed8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/html-table-section-element.js
@@ -0,0 +1,22 @@
+// https://html.spec.whatwg.org/multipage/#dom-tbody-rows
+function testRowsAttribute(localName) {
+ var elem = document.createElement(localName);
+ assert_equals(elem.rows.length, 0);
+
+ // Child <p> should *not* count as a row
+ elem.appendChild(document.createElement("p"));
+ assert_equals(elem.rows.length, 0);
+
+ // Child <tr> should count as a row
+ var childTr = document.createElement("tr");
+ elem.appendChild(childTr);
+ assert_equals(elem.rows.length, 1);
+
+ // Nested table with child <tr> should *not* count as a row
+ var nested = document.createElement(localName);
+ nested.appendChild(document.createElement("tr"));
+ var nestedTable = document.createElement("table");
+ nestedTable.appendChild(nested);
+ childTr.appendChild(nestedTable);
+ assert_equals(elem.rows.length, 1);
+}
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/processing-model-1/col-span-limits.html b/testing/web-platform/tests/html/semantics/tabular-data/processing-model-1/col-span-limits.html
new file mode 100644
index 0000000000..a4a425b9c1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/processing-model-1/col-span-limits.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<title>Limits on col/colgroup.span</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style>
+ div.square {
+ height:20px;
+ width:20px;
+ border:1px solid lime;
+ }
+ main table {
+ border-collapse:collapse;
+ border:1px solid blue;
+ }
+ main table col {
+ border-left:2px solid black;
+ }
+</style>
+<div id=log></div>
+<main>
+<table id=table1>
+ <col span=1000>
+ <tr>
+ <td colspan=999><div class="square"></div></td>
+ <td><div class="square"></div></td>
+ </tr>
+ <tr>
+ <td colspan=1000><div class="square"></div></td>
+ </tr>
+</table>
+<br>
+These two must look the same, each having 2 cells in one row:
+<table id=table2>
+ <col span=1000>
+ <tr>
+ <td colspan=1000><div class="square"></div></td>
+ <td><div class="square"></div></td>
+ </tr>
+</table>
+<br>
+<table id=table3>
+ <col span=1001>
+ <tr>
+ <td colspan=1000><div class="square"></div></td>
+ <td><div class="square"></div></td>
+ </tr>
+</table>
+</main>
+
+<script>
+test(() => {
+ assert_equals(table1.offsetWidth, 53);
+}, "col span of 1000 must work");
+
+test(() => {
+ assert_equals(table2.offsetWidth, 51, "table2 width");
+ assert_equals(table3.offsetWidth, 51, "table3 width");
+}, "col span of 1001 must be treated as 1000");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/processing-model-1/span-limits.html b/testing/web-platform/tests/html/semantics/tabular-data/processing-model-1/span-limits.html
new file mode 100644
index 0000000000..cdfa61bbcd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/processing-model-1/span-limits.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<title>Limits on colSpan/rowSpan</title>
+<meta name="timeout" content="long">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+
+<table border=1>
+ <tr><td colspan=500>a<td colspan=500 id=a1>a
+ <!-- This cell must span the previous two -->
+ <tr><td colspan=1000 id=a2>a
+</table>
+
+<table border=1>
+ <tr><td colspan=1000 id=b1>a<td>a
+ <!-- This cell must span only the first cell in the previous row -->
+ <tr><td colspan=1001 id=b2>a
+</table>
+
+<table border=1 style="float:left">
+ <!-- The first column must go all the way down to the bottom -->
+ <tr><td rowspan=65534 id=c1>a<td>
+ <!-- We'll add another 65533 rows later -->
+</table>
+
+<table border=1>
+ <!-- The first column must go one cell below the bottom -->
+ <tr><td rowspan=65535 id=d1>a<td>
+ <!-- We'll add another 65534 rows later -->
+</table>
+
+<script>
+var $ = document.querySelector.bind(document);
+
+test(() => {
+ assert_equals($("#a2").getBoundingClientRect().right,
+ $("#a1").getBoundingClientRect().right);
+}, "colspan of 1000 must work");
+
+test(() => {
+ assert_equals($("#b2").getBoundingClientRect().right,
+ $("#b1").getBoundingClientRect().right);
+}, "colspan of 1001 must be treated as 1000");
+
+test(() => {
+ var s = "";
+ for (var i = 0; i < 65532; i++) {
+ s += "<tr><td>";
+ }
+ s += "<tr><td id=c2>";
+ document.querySelectorAll("table")[2].firstElementChild.innerHTML += s;
+ assert_equals($("#c1").getBoundingClientRect().bottom,
+ $("#c2").getBoundingClientRect().bottom);
+}, "rowspan of 65534 must work");
+
+test(() => {
+ var s = "";
+ for (var i = 0; i < 65532; i++) {
+ s += "<tr><td>";
+ }
+ s += "<tr><td id=d2><tr><td>a<td>";
+ document.querySelectorAll("table")[3].firstElementChild.innerHTML += s;
+ assert_equals($("#d1").getBoundingClientRect().bottom,
+ $("#d2").getBoundingClientRect().bottom);
+}, "rowspan of 65535 must be treated as 65534");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-caption-element/caption_001.html b/testing/web-platform/tests/html/semantics/tabular-data/the-caption-element/caption_001.html
new file mode 100644
index 0000000000..ecb1bef854
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-caption-element/caption_001.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>HTML5 Table API Tests</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-caption-element" />
+ </head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <body>
+ <div id="log"></div>
+ <table id="table1" style="display:none">
+ <tr><td></td></tr>
+ <caption>first caption</caption>
+ <caption>second caption</caption>
+ </table>
+ <table id="table2" style="display:none">
+ <tr><td></td></tr>
+ </table>
+ <table id="table3" style="display:none">
+ <tr><td></td></tr>
+ </table>
+ <table id="table4" style="display:none">
+ <tr><td></td></tr>
+ <caption>first caption</caption>
+ </table>
+ <script>
+ test(function () {
+ assert_equals(document.getElementById('table1').caption.innerHTML, "first caption");
+ }, "first caption element child of the first table element");
+
+ test(function () {
+ var caption = document.createElement("caption");
+ caption.innerHTML = "new caption";
+ var table = document.getElementById('table1');
+ table.caption = caption;
+
+ assert_equals(caption.parentNode, table);
+ assert_equals(table.firstChild, caption);
+ assert_equals(table.caption.innerHTML, "new caption");
+
+ captions = table.getElementsByTagName('caption');
+ assert_equals(captions.length, 2);
+ assert_equals(captions[0].innerHTML, "new caption");
+ assert_equals(captions[1].innerHTML, "second caption");
+ }, "setting caption on a table");
+
+ test(function () {
+ assert_equals(document.getElementById('table2').caption, null);
+ }, "caption IDL attribute is null");
+
+ test(function () {
+ var table = document.getElementById('table3');
+ var caption = document.createElement("caption")
+ table.rows[0].appendChild(caption);
+ assert_equals(table.caption, null);
+ }, "caption of the third table element should be null");
+
+ test(function () {
+ assert_not_equals(document.getElementById('table4').caption, null);
+
+ var parent = document.getElementById('table4').caption.parentNode;
+ parent.removeChild(document.getElementById('table4').caption);
+
+ assert_equals(document.getElementById('table4').caption, null);
+ }, "dynamically removing caption on a table");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/caption-methods.html b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/caption-methods.html
new file mode 100644
index 0000000000..a349ed2b77
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/caption-methods.html
@@ -0,0 +1,276 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Creating and deleting captions</title>
+ <link rel="author" title="Erika Navara" href="mailto:edoyle@microsoft.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-table-element" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-table-createcaption" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-table-deletecaption" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id="log"></div>
+ <table id="table0" style="display:none">
+ </table>
+ <table id="table1" style="display:none">
+ <caption id="caption1">caption</caption>
+ <tr>
+ <td>cell</td>
+ <td>cell</td>
+ </tr>
+ </table>
+ <table id="table2" style="display:none">
+ <foo:caption>caption</foo:caption>
+ <tr>
+ <td>cell</td>
+ <td>cell</td>
+ </tr>
+ </table>
+ <table id="table3" style="display:none">
+ <caption id="caption3">caption 3</caption>
+ <tr>
+ <td>cell</td>
+ <td>cell</td>
+ </tr>
+ </table>
+ <table id="table4" style="display:none">
+ </table>
+ <table id="table5" style="display:none">
+ </table>
+ <table id="table6" style="display:none">
+ <caption id="caption6">caption 6</caption>
+ <tr>
+ <td>cell</td>
+ <td>cell</td>
+ </tr>
+ </table>
+ <table id="table7" style="display:none">
+ <caption id="caption7">caption 7</caption>
+ <tbody id="tbody7">
+ <tr>
+ <td>cell</td>
+ <td>cell</td>
+ </tr>
+ </tbody>
+ </table>
+ <table id="table10" style="display:none">
+ <tbody>
+ <tr>
+ <td>cell</td>
+ <td>cell</td>
+ </tr>
+ </tbody>
+ <caption>caption 10</caption>
+ </table>
+ <table id="table11" style="display:none">
+ <caption id="caption11">caption 11</caption>
+ </table>
+ <table id="table12" style="display:none">
+ <caption>caption 1</caption>
+ <caption>caption 2</caption>
+ </table>
+ <table id="table13" style="display:none">
+ </table>
+ <table id="table14" style="display:none">
+ <tbody>
+ <tr>
+ <td>cell</td>
+ <td>cell</td>
+ </tr>
+ </tbody>
+ <caption id="caption14">caption 14</caption>
+ </table>
+ <script>
+ test(function () {
+ var table0 = document.getElementById('table0');
+ var caption = document.createElementNS("foo", "caption");
+ table0.appendChild(caption);
+ var table0FirstNode = table0.firstChild;
+ var testCaption = table0.createCaption();
+ assert_not_equals(testCaption, table0FirstNode);
+ assert_equals(testCaption, table0.firstChild);
+ }, "createCaption method creates new caption if existing caption is not in html namespace")
+
+ test(function () {
+ var table1 = document.getElementById('table1');
+ var testCaption = table1.createCaption();
+ var table1FirstCaption = table1.caption;
+ assert_equals(testCaption, table1FirstCaption);
+ }, "createCaption method returns the first caption element child of the table")
+
+ test(function () {
+ var table2 = document.getElementById('table2');
+ var test2Caption = table2.createCaption();
+ var table2FirstNode = table2.firstChild;
+ assert_true(test2Caption instanceof HTMLTableCaptionElement);
+ assert_equals(table2FirstNode, test2Caption);
+ }, "createCaption method creates a new caption and inserts it as the first node of the table element")
+
+ test(function () {
+ var table = document.createElement('table');
+ assert_equals(table.createCaption(), table.createCaption());
+ }, "createCaption will not create new caption if one exists")
+
+ test(function () {
+ var table = document.createElementNS("http://www.w3.org/1999/xhtml", "foo:table")
+ var caption = table.createCaption();
+ assert_equals(caption.prefix, null);
+ }, "createCaption will not copy table's prefix")
+
+ test(function () {
+ var table3 = document.getElementById('table3');
+ assert_equals(table3.caption.textContent, "caption 3");
+ table3.deleteCaption();
+ assert_equals(table3.caption, null);
+ }, "deleteCaption method removes the first caption element child of the table element")
+
+ test(function () {
+ var table4 = document.getElementById('table4');
+ var caption = document.createElementNS("foo", "caption");
+ table4.appendChild(caption);
+ table4.deleteCaption();
+ assert_equals(caption.parentNode, table4);
+ }, "deleteCaption method not remove caption that is not in html namespace")
+
+ test(function() {
+ var table5 = document.getElementById('table5');
+ var caption = document.createElement('caption');
+ caption.appendChild(table5)
+
+ // Node cannot be inserted at the specified point in the hierarchy
+ assert_throws_dom("HierarchyRequestError", function() {
+ table5.caption = caption;
+ });
+
+ assert_not_equals(table5.caption, caption);
+ }, "Setting caption rethrows exception");
+
+ test(function() {
+ var table6 = document.getElementById("table6");
+ var caption = document.getElementById("caption6");
+ assert_equals(table6.caption, caption);
+
+ var newCaption = document.createElement("caption");
+ table6.caption = newCaption;
+ assert_equals(newCaption.parentNode, table6);
+ assert_equals(table6.firstChild, newCaption);
+ assert_equals(table6.caption, newCaption);
+ }, "Assigning a caption to table.caption")
+
+ test(function() {
+ var table7 = document.getElementById("table7");
+ var caption = document.getElementById("caption7");
+ assert_equals(table7.caption, caption);
+
+ table7.caption = null;
+ assert_equals(caption.parentNode, null);
+ assert_equals(table7.firstElementChild, document.getElementById("tbody7"));
+ assert_equals(table7.caption, null);
+ }, "Assigning null to table.caption")
+
+ test(function() {
+ var table8 = document.createElement("table");
+ var caption = document.createElement("captİon");
+ assert_throws_js(TypeError, function() {
+ table8.caption = caption;
+ });
+ }, "Assigning a non-caption to table.caption")
+
+ test(function() {
+ var table9 = document.createElement("table");
+ var caption = document.createElementNS("http://www.example.com", "caption");
+ assert_throws_js(TypeError, function() {
+ table9.caption = caption;
+ });
+ }, "Assigning a foreign caption to table.caption")
+
+ test(function() {
+ var table = document.createElement("table");
+ var caption = document.createElement("caption");
+ caption.innerHTML = "new caption";
+ table.caption = caption;
+
+ assert_equals(caption.parentNode, table);
+ assert_equals(table.firstChild, caption);
+ assert_equals(table.caption.innerHTML, "new caption");
+ }, "Set table.caption when the table doesn't already have a caption")
+
+ test(function() {
+ var table10 = document.getElementById("table10");
+ var caption = document.createElement("caption");
+ caption.innerHTML = "new caption";
+ table10.caption = caption;
+
+ assert_equals(caption.parentNode, table10);
+ assert_equals(table10.firstChild, caption);
+ assert_equals(table10.caption.innerHTML, "new caption");
+
+ var captions = table10.getElementsByTagName('caption');
+ assert_equals(captions.length, 1);
+ }, "Set table.caption when the table has a caption child but with other siblings before it")
+
+ test(function() {
+ var table11 = document.getElementById("table11");
+ var caption = document.createElement("caption");
+ caption.innerHTML = "new caption";
+ table11.caption = caption;
+
+ assert_equals(caption.parentNode, table11);
+ assert_equals(table11.firstChild, caption);
+ assert_equals(table11.caption.innerHTML, "new caption");
+
+ var captions = table11.getElementsByTagName('caption');
+ assert_equals(captions.length, 1);
+ }, "Set table.caption when the table has a caption descendant")
+
+ test(function() {
+ var table12 = document.getElementById("table12");
+ var caption = document.createElement("caption");
+ caption.innerHTML = "new caption";
+ table12.caption = caption;
+
+ assert_equals(caption.parentNode, table12);
+ assert_equals(table12.firstChild, caption);
+ assert_equals(table12.caption.innerHTML, "new caption");
+
+ var captions = table12.getElementsByTagName('caption');
+ assert_equals(captions.length, 2);
+ assert_equals(captions[0].innerHTML, "new caption");
+ assert_equals(captions[1].innerHTML, "caption 2");
+ }, "Set table.caption when the table has two caption children")
+
+ promise_test(async t => {
+ var table13 = document.getElementById("table13");
+ var iframe = document.createElement("iframe");
+ iframe.srcdoc = '<table><caption id="caption13">caption 13</caption></table>';
+ document.body.appendChild(iframe);
+
+ var iframeWatcher = new EventWatcher(t, iframe, "load");
+ await iframeWatcher.wait_for("load");
+ var caption = iframe.contentWindow.document.getElementById("caption13");
+ table13.caption = caption;
+
+ assert_equals(caption.parentNode, table13);
+ assert_equals(table13.firstChild, caption);
+ assert_equals(table13.caption.innerHTML, "caption 13");
+
+ var captions = table13.getElementsByTagName('caption');
+ assert_equals(captions.length, 1);
+ }, "Assigning a caption has a different owner document to table.caption")
+
+ test(function() {
+ var table14 = document.getElementById("table14");
+ var caption = document.getElementById("caption14");
+ table14.caption = caption;
+
+ assert_equals(caption.parentNode, table14);
+ assert_equals(table14.firstChild, caption);
+
+ var captions = table14.getElementsByTagName('caption');
+ assert_equals(captions.length, 1);
+ }, "Assigning the caption already in the table to table.caption")
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/createTBody.html b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/createTBody.html
new file mode 100644
index 0000000000..6100aedfdf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/createTBody.html
@@ -0,0 +1,173 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLTableElement.createTBody</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+function assert_tbody(tbody) {
+ assert_equals(tbody.localName, "tbody");
+ assert_equals(tbody.namespaceURI, htmlNS);
+ assert_equals(tbody.prefix, null);
+}
+var htmlNS = "http://www.w3.org/1999/xhtml";
+test(function() {
+ var table = document.createElement("table");
+ var tbody = table.createTBody();
+ assert_equals(table.firstChild, tbody);
+ assert_tbody(tbody);
+}, "No child nodes");
+
+test(function() {
+ var table = document.createElement("table");
+ var before = table.appendChild(document.createElement("tbody"));
+ assert_array_equals(table.childNodes, [before]);
+
+ var tbody = table.createTBody();
+ assert_array_equals(table.childNodes, [before, tbody]);
+ assert_tbody(tbody);
+}, "One tbody child node");
+
+test(function() {
+ var table = document.createElement("table");
+ var before1 = table.appendChild(document.createElement("tbody"));
+ var before2 = table.appendChild(document.createElement("tbody"));
+ assert_array_equals(table.childNodes, [before1, before2]);
+
+ var tbody = table.createTBody();
+ assert_array_equals(table.childNodes, [before1, before2, tbody]);
+ assert_tbody(tbody);
+}, "Two tbody child nodes");
+
+test(function() {
+ var table = document.createElement("table");
+ var before1 = table.appendChild(document.createElement("thead"));
+ var before2 = table.appendChild(document.createElement("tbody"));
+ assert_array_equals(table.childNodes, [before1, before2]);
+
+ var tbody = table.createTBody();
+ assert_array_equals(table.childNodes, [before1, before2, tbody]);
+ assert_tbody(tbody);
+}, "A thead and a tbody child node");
+
+test(function() {
+ var table = document.createElement("table");
+ var before1 = table.appendChild(document.createElement("tfoot"));
+ var before2 = table.appendChild(document.createElement("tbody"));
+ assert_array_equals(table.childNodes, [before1, before2]);
+
+ var tbody = table.createTBody();
+ assert_array_equals(table.childNodes, [before1, before2, tbody]);
+ assert_tbody(tbody);
+}, "A tfoot and a tbody child node");
+
+test(function() {
+ var table = document.createElement("table");
+ var before = table.appendChild(document.createElement("tbody"));
+ var after = table.appendChild(document.createElement("thead"));
+ assert_array_equals(table.childNodes, [before, after]);
+
+ var tbody = table.createTBody();
+ assert_array_equals(table.childNodes, [before, tbody, after]);
+ assert_tbody(tbody);
+}, "A tbody and a thead child node");
+
+test(function() {
+ var table = document.createElement("table");
+ var before = table.appendChild(document.createElement("tbody"));
+ var after = table.appendChild(document.createElement("tfoot"));
+ assert_array_equals(table.childNodes, [before, after]);
+
+ var tbody = table.createTBody();
+ assert_array_equals(table.childNodes, [before, tbody, after]);
+ assert_tbody(tbody);
+}, "A tbody and a tfoot child node");
+
+test(function() {
+ var table = document.createElement("table");
+ var before1 = table.appendChild(document.createElement("tbody"));
+ var before2 = table.appendChild(document.createElement("tbody"));
+ var after = table.appendChild(document.createElement("div"));
+ assert_array_equals(table.childNodes, [before1, before2, after]);
+
+ var tbody = table.createTBody();
+ assert_array_equals(table.childNodes, [before1, before2, tbody, after]);
+ assert_tbody(tbody);
+}, "Two tbody child nodes and a div");
+
+test(function() {
+ var table = document.createElement("table");
+ var before = table.appendChild(document.createElement("tbody"));
+ var after = table.appendChild(document.createElementNS("x", "tbody"));
+ assert_array_equals(table.childNodes, [before, after]);
+
+ var tbody = table.createTBody();
+ assert_array_equals(table.childNodes, [before, tbody, after]);
+ assert_tbody(tbody);
+}, "One HTML and one namespaced tbody child node");
+
+test(function() {
+ var table = document.createElement("table");
+ var before1 = table.appendChild(document.createElement("tbody"));
+ var before2 = before1.appendChild(document.createElement("tbody"));
+ assert_array_equals(table.childNodes, [before1]);
+
+ var tbody = table.createTBody();
+ assert_array_equals(table.childNodes, [before1, tbody]);
+ assert_tbody(tbody);
+}, "Two nested tbody child nodes");
+
+test(function() {
+ var table = document.createElement("table");
+ var before1 = table.appendChild(document.createElement("thead"));
+ var before2 = before1.appendChild(document.createElement("tbody"));
+ assert_array_equals(table.childNodes, [before1]);
+
+ var tbody = table.createTBody();
+ assert_array_equals(table.childNodes, [before1, tbody]);
+ assert_tbody(tbody);
+}, "A tbody node inside a thead child node");
+
+test(function() {
+ var table = document.createElement("table");
+ var before1 = table.appendChild(document.createElement("tfoot"));
+ var before2 = before1.appendChild(document.createElement("tbody"));
+ assert_array_equals(table.childNodes, [before1]);
+
+ var tbody = table.createTBody();
+ assert_array_equals(table.childNodes, [before1, tbody]);
+ assert_tbody(tbody);
+}, "A tbody node inside a tfoot child node");
+
+test(function() {
+ var table = document.createElement("table");
+ var before = table.appendChild(document.createElement("tbody"));
+ var after1 = table.appendChild(document.createElement("thead"));
+ var after2 = after1.appendChild(document.createElement("tbody"));
+ assert_array_equals(table.childNodes, [before, after1]);
+
+ var tbody = table.createTBody();
+ assert_array_equals(table.childNodes, [before, tbody, after1]);
+ assert_tbody(tbody);
+}, "A tbody node inside a thead child node after a tbody child node");
+
+test(function() {
+ var table = document.createElement("table");
+ var before = table.appendChild(document.createElement("tbody"));
+ var after1 = table.appendChild(document.createElement("tfoot"));
+ var after2 = after1.appendChild(document.createElement("tbody"));
+ assert_array_equals(table.childNodes, [before, after1]);
+
+ var tbody = table.createTBody();
+ assert_array_equals(table.childNodes, [before, tbody, after1]);
+ assert_tbody(tbody);
+}, "A tbody node inside a tfoot child node after a tbody child node");
+
+test(function() {
+ var table = document.createElementNS(htmlNS, "foo:table");
+ var tbody = table.createTBody();
+
+ assert_equals(tbody.prefix, null);
+}, "A prefixed table creates tbody without prefix");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/delete-caption.html b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/delete-caption.html
new file mode 100644
index 0000000000..6183fa98b8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/delete-caption.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>deleteCaption()</title>
+ <link rel="author" title="Ben Boyle" href="mailto:benjamins.boyle@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-table-deletecaption" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <table id="one-caption">
+ <caption>Fixture table caption</caption>
+ </table>
+
+ <table id="two-captions">
+ <caption>Fixture table caption</caption>
+ <caption>A second caption element</caption>
+ </table>
+
+ <table id="zero-captions"></table>
+
+ <table id="descendent-caption">
+ <tr>
+ <td>
+ <table>
+ <caption>Nested caption</caption>
+ </table>
+ </td>
+ </tr>
+ </table>
+
+ <script>
+ // The deleteCaption() method must remove the first caption element child of the table element, if any.
+ // https://html.spec.whatwgorg/multipage/tables.html#dom-table-deletecaption
+ test(function() {
+ var table = document.getElementById('one-caption');
+
+ table.deleteCaption();
+ assert_equals(table.getElementsByTagName('caption').length, 0, 'caption was removed');
+
+ }, 'deleteCaption() delete only caption on table');
+
+ test(function() {
+ var table = document.getElementById('one-caption');
+ var result;
+
+ result = table.deleteCaption();
+ // does .deleteCaption() have a return value?
+ assert_equals(result, undefined, '.deleteCaption() returns undefined');
+ }, 'deleteCaption() returns undefined');
+
+ test(function() {
+ var table = document.getElementById('two-captions');
+
+ table.deleteCaption();
+ assert_equals(table.getElementsByTagName('caption').length, 1, '1 caption (of 2) was removed');
+ assert_equals(table.getElementsByTagName('caption')[0].textContent, 'A second caption element', 'The first caption was removed');
+
+ // removing the only caption
+ table.deleteCaption();
+ assert_equals(table.getElementsByTagName('caption').length, 0, 'last caption was removed');
+ }, 'deleteCaption()');
+
+ test(function() {
+ var table = document.getElementById('zero-captions');
+ // removing a caption when none exists
+ table.deleteCaption();
+
+ assert_equals(table.getElementsByTagName('caption').length, 0, 'no exceptions using .deleteCaption() on a table without any captions');
+
+ }, 'deleteCaption() does not throw any exceptions when called on a table without a caption');
+
+ test(function() {
+ var table = document.getElementById( 'descendent-caption' );
+ table.deleteCaption();
+
+ assert_equals(table.getElementsByTagName('caption').length, 1, 'descendent caption was not deleted');
+ }, 'deleteCaption() does not delete captions in descendent tables');
+
+ test(function() {
+ var table = document.getElementById('zero-captions');
+ var caption;
+
+ caption = document.createElementNS('http://www.w3.org/2000/svg', 'caption');
+ table.insertBefore(caption, table.firstChild);
+ assert_equals(table.getElementsByTagName('caption').length, 1, 'SVG:caption is created');
+
+ table.deleteCaption();
+ assert_equals(table.getElementsByTagName('caption').length, 1, 'SVG:caption is not deleted');
+
+ }, 'deleteCaption() handles captions from different namespaces');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/insertRow-method-01.html b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/insertRow-method-01.html
new file mode 100644
index 0000000000..8ed7b5fad6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/insertRow-method-01.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>insertRow(): INDEX_SIZE_ERR</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-table-insertrow">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="test">
+<table>
+<tr>
+<td>
+</table>
+</div>
+<script>
+test(function() {
+ var table = document.getElementById("test").getElementsByTagName("table")[0];
+ assert_throws_dom("INDEX_SIZE_ERR", function() {
+ table.insertRow(-2);
+ })
+ assert_throws_dom("INDEX_SIZE_ERR", function() {
+ table.insertRow(2);
+ })
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/insertRow-method-02.html b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/insertRow-method-02.html
new file mode 100644
index 0000000000..410425fb1e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/insertRow-method-02.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>insertRow(): Empty table</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-table-insertrow">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="test">
+<table></table>
+</div>
+<script>
+var HTML = "http://www.w3.org/1999/xhtml";
+test(function() {
+ var table = document.getElementById("test").getElementsByTagName("table")[0];
+ test(function() {
+ assert_equals(table.childNodes.length, 0);
+ assert_equals(table.rows.length, 0);
+ }, "table should start out empty")
+
+ var tr;
+ test(function() {
+ tr = table.insertRow(0);
+ assert_equals(tr.localName, "tr");
+ assert_equals(tr.namespaceURI, HTML);
+ }, "insertRow should insert a tr element")
+
+ var tbody = tr.parentNode;
+ test(function() {
+ assert_equals(tbody.localName, "tbody");
+ assert_equals(tbody.namespaceURI, HTML);
+ assert_equals(tbody.parentNode, table);
+ }, "insertRow should insert a tbody element")
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/insertRow-method-03.html b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/insertRow-method-03.html
new file mode 100644
index 0000000000..19c3ceb3c6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/insertRow-method-03.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>insertRow(): non-empty table</title>
+<link rel="author" title="g-k" href="mailto:greg.guthe@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-table-insertrow">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="test">
+ <table>
+ <tbody><tr id="first"></tr><tr id="second"></tr></tbody>
+ </table>
+</div>
+<script>
+var HTML = "http://www.w3.org/1999/xhtml";
+test(function() {
+ var table = document.getElementById("test").getElementsByTagName("table")[0];
+ test(function() {
+ assert_equals(table.childNodes.length, 3);
+ assert_equals(table.rows.length, 2);
+ }, "table should start out with two rows")
+
+ var tr;
+ test(function() {
+ tr = table.insertRow(1);
+ assert_equals(tr.localName, "tr");
+ assert_equals(tr.namespaceURI, HTML);
+ assert_equals(table.getElementsByTagName("tr")[0].id, "first");
+ assert_equals(table.getElementsByTagName("tr")[1].id, "");
+ assert_equals(table.getElementsByTagName("tr")[2].id, "second");
+ }, "insertRow should insert a tr element before the second row")
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/remove-row.html b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/remove-row.html
new file mode 100644
index 0000000000..43a128c57e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/remove-row.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Delete Row tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<table id="element">
+ <thead>
+ <th>First column</th>
+ <th>Second column</th>
+ </thead>
+ <tbody>
+ <tr>
+ <td>1.1</td>
+ <td>1.2</td>
+ </tr>
+ <tr>
+ <td>2.1</td>
+ <td>2.2</td>
+ </tr>
+ </tbody>
+</table>
+
+<script>
+var el = document.getElementById('element');
+
+test(function() {
+ assert_throws_dom("IndexSizeError", function() {
+ el.deleteRow(-2)
+ })
+}, 'deleteRow function invalid argument');
+test(function() {
+ assert_throws_dom("IndexSizeError", function() {
+ el.deleteRow(el.rows.length)
+ })
+}, 'deleteRow function invalid argument bis');
+
+test(function() {
+ var old_length = el.rows.length;
+ el.insertRow(-1);
+ el.deleteRow(-1);
+ assert_equals(old_length, el.rows.length);
+}, "check normal deleteRow");
+test(function() {
+ assert_equals(el.rows.length, 3);
+ do {
+ var old_length = el.rows.length;
+ el.deleteRow(-1);
+ assert_equals(el.rows.length, old_length - 1);
+ } while (el.rows.length);
+}, "check normal deleteRow bis");
+
+test(function() {
+ assert_equals(el.rows.length, 0);
+ el.deleteRow(-1);
+}, 'deleteRow(-1) with no rows');
+
+test(function() {
+ assert_equals(el.rows.length, 0);
+ assert_throws_dom("IndexSizeError", function() {
+ el.deleteRow(0);
+ });
+}, 'deleteRow(0) with no rows');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/tBodies.html b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/tBodies.html
new file mode 100644
index 0000000000..128dbc9f7d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/tBodies.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>HTMLTableElement.tBodies</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var text =
+ '<html xmlns="http://www.w3.org/1999/xhtml">' +
+ ' <head>' +
+ ' <title>Virtual Library</title>' +
+ ' </head>' +
+ ' <body>' +
+ ' <table id="mytable" border="1">' +
+ ' <tbody>' +
+ ' <tr><td>Cell 1</td><td>Cell 2</td></tr>' +
+ ' <tr><td>Cell 3</td><td>Cell 4</td></tr>' +
+ ' </tbody>' +
+ ' </table>' +
+ ' </body>' +
+ '</html>';
+
+ var parser = new DOMParser();
+ var doc = parser.parseFromString(text, "text/xml");
+
+ // import <table>
+ var table = doc.documentElement.getElementsByTagName('table')[0];
+ var mytable = document.body.appendChild(document.importNode(table, true));
+
+ assert_equals(mytable.tBodies.length, 1);
+ var tbody = document.createElement('tbody');
+ mytable.appendChild(tbody);
+ var tr = tbody.insertRow(-1);
+ tr.insertCell(-1).appendChild(document.createTextNode('Cell 5'));
+ tr.insertCell(-1).appendChild(document.createTextNode('Cell 6'));
+ assert_equals(mytable.tBodies.length, 2);
+ assert_equals(mytable.rows.length, 3);
+ assert_equals(tr.rowIndex, 2);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/tFoot.html b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/tFoot.html
new file mode 100644
index 0000000000..40220bc1e2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/tFoot.html
@@ -0,0 +1,65 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>tFoot tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<table id="t">
+<caption id="tcaption"></caption><thead id="thead"></thead><tbody id="tbody1"></tbody><tbody id="tbody2"></tbody><tfoot id="tfoot1"></tfoot><tfoot id="tfoot2"></tfoot><tfoot id="tfoot3"></tfoot></table>
+<script>
+test(function() {
+ var t = document.getElementById("t");
+ var tfoot1 = document.getElementById("tfoot1");
+
+ assert_equals(t.tFoot, tfoot1);
+
+ var tfoot2 = document.getElementById("tfoot2");
+ t.tFoot = null;
+
+ assert_equals(t.tFoot, tfoot2);
+
+ var tfoot3 = document.getElementById("tfoot3");
+ t.deleteTFoot();
+
+ assert_equals(t.tFoot, tfoot3);
+
+ var tfoot = t.createTFoot();
+ assert_equals(t.tFoot, tfoot);
+ assert_equals(tfoot, tfoot3);
+
+ t.deleteTFoot();
+ assert_equals(t.tFoot, null);
+
+ var tbody2 = document.getElementById("tbody2");
+
+ tfoot = t.createTFoot();
+ assert_equals(t.tFoot, tfoot);
+
+ assert_equals(t.tFoot.previousSibling, tbody2);
+ assert_equals(t.tFoot.nextSibling, null);
+
+ t.deleteTFoot();
+ assert_equals(t.tFoot, null);
+
+ t.tFoot = tfoot;
+ assert_equals(t.tFoot, tfoot);
+
+ assert_equals(t.tFoot.previousSibling, tbody2);
+ assert_equals(t.tFoot.nextSibling, null);
+
+ assert_throws_js(TypeError, function(){
+ t.tFoot = document.createElement("div");
+ });
+
+ assert_throws_dom("HierarchyRequestError", function(){
+ t.tFoot = document.createElement("thead");
+ });
+})
+
+test(function () {
+ var table = document.createElementNS("http://www.w3.org/1999/xhtml", "foo:table")
+ var tfoot = table.createTFoot();
+
+ assert_equals(table.tFoot, tfoot);
+ assert_equals(tfoot.prefix, null);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/tHead.html b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/tHead.html
new file mode 100644
index 0000000000..fadebecd6f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/tHead.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>tHead tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<table id="t">
+<caption id="tcaption"></caption><thead id="thead1"></thead><thead id="thead2"></thead><thead id="thead3"></thead><tbody id="tbody1"></tbody><tbody id="tbody2"></tbody><tfoot id="tfoot"></tfoot>
+</table>
+<table>
+<thead id="t2thead">
+<td>
+<table id="t2">
+</table>
+</table>
+<script>
+test(function() {
+ var t = document.getElementById("t");
+ var thead1 = document.getElementById("thead1");
+
+ assert_equals(t.tHead, thead1);
+
+ var thead2 = document.getElementById("thead2");
+ t.tHead = null;
+
+ assert_equals(t.tHead, thead2);
+
+ var thead3 = document.getElementById("thead3");
+ t.deleteTHead();
+
+ assert_equals(t.tHead, thead3);
+
+ var thead = t.createTHead();
+ assert_equals(t.tHead, thead);
+ assert_equals(thead, thead3);
+
+ t.deleteTHead();
+ assert_equals(t.tHead, null);
+
+ var tcaption = document.getElementById("tcaption");
+ var tbody1 = document.getElementById("tbody1");
+
+ thead = t.createTHead();
+ assert_equals(t.tHead, thead);
+
+ assert_equals(t.tHead.previousSibling, tcaption);
+ assert_equals(t.tHead.nextSibling, tbody1);
+
+ assert_throws_js(TypeError, function(){
+ t.tHead = document.createElement("div");
+ });
+
+ assert_throws_dom("HierarchyRequestError", function(){
+ t.tHead = document.createElement("tbody");
+ });
+
+});
+
+test(function() {
+ var t2 = document.getElementById("t2");
+ var t2thead = document.getElementById("t2thead");
+
+ assert_throws_dom("HierarchyRequestError", function() {
+ t2.tHead = t2thead;
+ });
+});
+
+test(function () {
+ var table = document.createElementNS("http://www.w3.org/1999/xhtml", "foo:table")
+ var thead = table.createTHead();
+
+ assert_equals(table.tHead, thead);
+ assert_equals(thead.prefix, null);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/table-insertRow.html b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/table-insertRow.html
new file mode 100644
index 0000000000..8a9574ecdd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/table-insertRow.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>HTMLTableElement.insertRow</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var HTMLNS = "http://www.w3.org/1999/xhtml"
+ var parentEl = document.createElementNS(HTMLNS, "html:table")
+ assert_equals(parentEl.namespaceURI, HTMLNS, "Parent should be in the HTML namespace")
+ assert_equals(parentEl.prefix, "html", "Parent prefix should be html")
+ assert_equals(parentEl.localName, "table", "Parent local name should be table")
+ assert_equals(parentEl.tagName, "HTML:TABLE", "Parent tag name should be HTML:TABLE")
+
+ var row = parentEl.insertRow(-1)
+ assert_equals(row.namespaceURI, HTMLNS, "Row should be in the HTML namespace")
+ assert_equals(row.prefix, null, "Row prefix should be null")
+ assert_equals(row.localName, "tr", "Row local name should be tr")
+ assert_equals(row.tagName, "TR", "Row tag name should be TR")
+
+ var body = row.parentNode
+ assert_equals(body.namespaceURI, HTMLNS, "Body should be in the HTML namespace")
+ assert_equals(body.prefix, null, "Body prefix should be null")
+ assert_equals(body.localName, "tbody", "Body local name should be tr")
+ assert_equals(body.tagName, "TBODY", "Body tag name should be TR")
+
+ assert_array_equals(parentEl.childNodes, [body])
+ assert_array_equals(body.childNodes, [row])
+ assert_array_equals(parentEl.rows, [row])
+}, "insertRow should not copy prefixes")
+test(function() {
+ var table = document.createElement("table")
+ var head = table.appendChild(document.createElement("thead"))
+ assert_array_equals(table.rows, [])
+
+ var row = table.insertRow(-1)
+ var body = row.parentNode
+ assert_array_equals(table.childNodes, [head, body])
+ assert_array_equals(head.childNodes, [])
+ assert_array_equals(body.childNodes, [row])
+ assert_array_equals(table.rows, [row])
+}, "insertRow should insert into a tbody, not into a thead, if table.rows is empty")
+test(function() {
+ var table = document.createElement("table")
+ var foot = table.appendChild(document.createElement("tfoot"))
+ assert_array_equals(table.rows, [])
+
+ var row = table.insertRow(-1)
+ var body = row.parentNode
+ assert_array_equals(table.childNodes, [foot, body])
+ assert_array_equals(foot.childNodes, [])
+ assert_array_equals(body.childNodes, [row])
+ assert_array_equals(table.rows, [row])
+}, "insertRow should insert into a tbody, not into a tfoot, if table.rows is empty")
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/table-rows.html b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/table-rows.html
new file mode 100644
index 0000000000..8bc23d5a7c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-table-element/table-rows.html
@@ -0,0 +1,234 @@
+<!DOCTYPE html>
+<title>HTMLTableElement.rows</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function assert_nodelist_equals(actual, expected) {
+ assert_equals(actual.length, expected.length);
+
+ for (var i = 0; i < actual.length; ++i) {
+ assert_true(i in actual);
+ assert_true(actual.hasOwnProperty(i),
+ "property " + i + " expected to be present on the object");
+ assert_equals(actual.item(i), expected[i]);
+ assert_equals(actual[i], expected[i]);
+ }
+}
+
+function test_table_simple(group, table) {
+ var foo1 = group.appendChild(document.createElement("tr"));
+ foo1.id = "foo";
+ var bar1 = group.appendChild(document.createElement("tr"));
+ bar1.id = "bar";
+ var foo2 = group.appendChild(document.createElement("tr"));
+ foo2.id = "foo";
+ var bar2 = group.appendChild(document.createElement("tr"));
+ bar2.id = "bar";
+
+ assert_true(table.rows instanceof HTMLCollection, "table.rows should be a HTMLCollection.");
+ assert_nodelist_equals(table.rows, [foo1, bar1, foo2, bar2]);
+ assert_equals(table.rows.foo, foo1);
+ assert_equals(table.rows["foo"], foo1);
+ assert_equals(table.rows.namedItem("foo"), foo1);
+ assert_equals(table.rows.bar, bar1);
+ assert_equals(table.rows["bar"], bar1);
+ assert_equals(table.rows.namedItem("bar"), bar1);
+ assert_array_equals(Object.getOwnPropertyNames(table.rows), [
+ "0",
+ "1",
+ "2",
+ "3",
+ "foo",
+ "bar"
+ ]);
+}
+test(function() {
+ var table = document.createElement("table");
+ test_table_simple(table, table);
+}, "Children of table");
+test(function() {
+ var table = document.createElement("table");
+ var group = table.appendChild(document.createElement("thead"));
+ test_table_simple(group, table);
+}, "Children of thead");
+test(function() {
+ var table = document.createElement("table");
+ var group = table.appendChild(document.createElement("tbody"));
+ test_table_simple(group, table);
+}, "Children of tbody");
+test(function() {
+ var table = document.createElement("table");
+ var group = table.appendChild(document.createElement("tfoot"));
+ test_table_simple(group, table);
+}, "Children of tfoot");
+test(function() {
+ var table = document.createElement("table");
+ var orphan1 = table.appendChild(document.createElement("tr"));
+ orphan1.id = "orphan1";
+ var foot1 = table.appendChild(document.createElement("tfoot"));
+ var orphan2 = table.appendChild(document.createElement("tr"));
+ orphan2.id = "orphan2";
+ var foot2 = table.appendChild(document.createElement("tfoot"));
+ var orphan3 = table.appendChild(document.createElement("tr"));
+ orphan3.id = "orphan3";
+ var body1 = table.appendChild(document.createElement("tbody"));
+ var orphan4 = table.appendChild(document.createElement("tr"));
+ orphan4.id = "orphan4";
+ var body2 = table.appendChild(document.createElement("tbody"));
+ var orphan5 = table.appendChild(document.createElement("tr"));
+ orphan5.id = "orphan5";
+ var head1 = table.appendChild(document.createElement("thead"));
+ var orphan6 = table.appendChild(document.createElement("tr"));
+ orphan6.id = "orphan6";
+ var head2 = table.appendChild(document.createElement("thead"));
+ var orphan7 = table.appendChild(document.createElement("tr"));
+ orphan7.id = "orphan7";
+
+ var foot1row1 = foot1.appendChild(document.createElement("tr"));
+ foot1row1.id = "foot1row1";
+ var foot1row2 = foot1.appendChild(document.createElement("tr"));
+ foot1row2.id = "foot1row2";
+ var foot2row1 = foot2.appendChild(document.createElement("tr"));
+ foot2row1.id = "foot2row1";
+ var foot2row2 = foot2.appendChild(document.createElement("tr"));
+ foot2row2.id = "foot2row2";
+
+ var body1row1 = body1.appendChild(document.createElement("tr"));
+ body1row1.id = "body1row1";
+ var body1row2 = body1.appendChild(document.createElement("tr"));
+ body1row2.id = "body1row2";
+ var body2row1 = body2.appendChild(document.createElement("tr"));
+ body2row1.id = "body2row1";
+ var body2row2 = body2.appendChild(document.createElement("tr"));
+ body2row2.id = "body2row2";
+
+ var head1row1 = head1.appendChild(document.createElement("tr"));
+ head1row1.id = "head1row1";
+ var head1row2 = head1.appendChild(document.createElement("tr"));
+ head1row2.id = "head1row2";
+ var head2row1 = head2.appendChild(document.createElement("tr"));
+ head2row1.id = "head2row1";
+ var head2row2 = head2.appendChild(document.createElement("tr"));
+ head2row2.id = "head2row2";
+
+ // These elements should not end up in any collection.
+ table.appendChild(document.createElement("div"))
+ .appendChild(document.createElement("tr"));
+ foot1.appendChild(document.createElement("div"))
+ .appendChild(document.createElement("tr"));
+ body1.appendChild(document.createElement("div"))
+ .appendChild(document.createElement("tr"));
+ head1.appendChild(document.createElement("div"))
+ .appendChild(document.createElement("tr"));
+ table.appendChild(document.createElementNS("http://example.com/test", "tr"));
+ foot1.appendChild(document.createElementNS("http://example.com/test", "tr"));
+ body1.appendChild(document.createElementNS("http://example.com/test", "tr"));
+ head1.appendChild(document.createElementNS("http://example.com/test", "tr"));
+
+ assert_true(table.rows instanceof HTMLCollection, "table.rows should be a HTMLCollection.");
+ assert_nodelist_equals(table.rows, [
+ // thead
+ head1row1,
+ head1row2,
+ head2row1,
+ head2row2,
+
+ // tbody + table
+ orphan1,
+ orphan2,
+ orphan3,
+ body1row1,
+ body1row2,
+ orphan4,
+ body2row1,
+ body2row2,
+ orphan5,
+ orphan6,
+ orphan7,
+
+ // tfoot
+ foot1row1,
+ foot1row2,
+ foot2row1,
+ foot2row2
+ ]);
+ assert_array_equals(Object.getOwnPropertyNames(table.rows), [
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "10",
+ "11",
+ "12",
+ "13",
+ "14",
+ "15",
+ "16",
+ "17",
+ "18",
+ "head1row1",
+ "head1row2",
+ "head2row1",
+ "head2row2",
+ "orphan1",
+ "orphan2",
+ "orphan3",
+ "body1row1",
+ "body1row2",
+ "orphan4",
+ "body2row1",
+ "body2row2",
+ "orphan5",
+ "orphan6",
+ "orphan7",
+ "foot1row1",
+ "foot1row2",
+ "foot2row1",
+ "foot2row2"
+ ]);
+
+ var ids = [
+ "orphan1",
+ "orphan2",
+ "orphan3",
+ "orphan4",
+ "orphan5",
+ "orphan6",
+ "orphan7",
+ "foot1row1",
+ "foot1row2",
+ "foot2row1",
+ "foot2row2",
+ "body1row1",
+ "body1row2",
+ "body2row1",
+ "body2row2",
+ "head1row1",
+ "head1row2",
+ "head2row1",
+ "head2row2"
+ ];
+ ids.forEach(function(id) {
+ assert_equals(table.rows.namedItem(id).id, id);
+ assert_true(id in table.rows);
+ assert_equals(table.rows[id].id, id);
+ assert_true(id in table.rows);
+ });
+ while (table.firstChild) {
+ table.removeChild(table.firstChild);
+ }
+ ids.forEach(function(id) {
+ assert_equals(table.rows.namedItem(id), null);
+ assert_false(id in table.rows);
+ assert_equals(table.rows[id], undefined);
+ assert_false(id in table.rows);
+ });
+}, "Complicated case");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-tbody-element/deleteRow.html b/testing/web-platform/tests/html/semantics/tabular-data/the-tbody-element/deleteRow.html
new file mode 100644
index 0000000000..695c1ea50b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-tbody-element/deleteRow.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLTableSectionElement#deleteRow</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>
+
+<table>
+ <tbody id="testBody">
+ <tr><td>ABCDEF</td></tr>
+ <tr><td>12345</td></tr>
+ <tr><td>ABC12345DEF</td></tr>
+ </tbody>
+</table>
+
+<script>
+
+var tbody = document.getElementById("testBody");
+
+test(function () {
+ tbody.deleteRow(0);
+ assert_equals(tbody.rows.length, 2);
+ assert_equals(tbody.rows[0].childNodes[0].innerHTML, "12345");
+}, "HTMLTableSectionElement deleteRow(0)");
+
+test(function () {
+ tbody.deleteRow(-1);
+ assert_equals(tbody.rows.length, 1);
+ assert_equals(tbody.rows[0].childNodes[0].innerHTML, "12345");
+}, "HTMLTableSectionElement deleteRow(-1)");
+
+test(function () {
+ assert_throws_dom("IndexSizeError", function () {
+ tbody.deleteRow(tbody.rows.length);
+ });
+}, "HTMLTableSectionElement deleteRow(rows.length)");
+
+test(function () {
+ assert_throws_dom("IndexSizeError", function () {
+ tbody.deleteRow(-2);
+ });
+}, "HTMLTableSectionElement deleteRow(-2)");
+
+test(function () {
+ assert_equals(tbody.rows.length, 1);
+ tbody.deleteRow(-1);
+ assert_equals(tbody.rows.length, 0);
+ tbody.deleteRow(-1);
+ assert_equals(tbody.rows.length, 0);
+}, "HTMLTableSectionElement deleteRow(-1) with no rows");
+
+test(function () {
+ assert_equals(tbody.rows.length, 0);
+ assert_throws_dom("IndexSizeError", function () {
+ tbody.deleteRow(0);
+ });
+}, "HTMLTableSectionElement deleteRow(0) with no rows");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-tbody-element/insertRow.html b/testing/web-platform/tests/html/semantics/tabular-data/the-tbody-element/insertRow.html
new file mode 100644
index 0000000000..f5c2227ca6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-tbody-element/insertRow.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLTableSectionElement#insertRow</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>
+
+<table>
+ <tbody id="testBody">
+ <tr><td>ABCDEF</td></tr>
+ </tbody>
+</table>
+
+<script>
+
+var tbody = document.getElementById("testBody");
+
+test(function () {
+ var trEle = tbody.insertRow(0);
+ assert_equals(tbody.rows[0], trEle);
+ assert_equals(tbody.rows.length, 2);
+}, "HTMLTableSectionElement insertRow(0)");
+
+test(function () {
+ var trEle = tbody.insertRow(-1);
+ assert_equals(tbody.rows[tbody.rows.length - 1], trEle);
+ assert_equals(tbody.rows.length, 3);
+}, "HTMLTableSectionElement insertRow(-1)");
+
+test(function () {
+ var trEle = tbody.insertRow();
+ assert_equals(tbody.rows[tbody.rows.length - 1], trEle);
+ assert_equals(tbody.rows.length, 4);
+}, "HTMLTableSectionElement insertRow()");
+
+test(function () {
+ var trEle = tbody.insertRow(tbody.rows.length);
+ assert_equals(tbody.rows[tbody.rows.length - 1], trEle);
+ assert_equals(tbody.rows.length, 5);
+}, "HTMLTableSectionElement insertRow(rows.length)");
+
+test(function () {
+ assert_throws_dom("IndexSizeError", function () {
+ tbody.insertRow(-2);
+ });
+}, "HTMLTableSectionElement insertRow(-2)");
+
+test(function () {
+ assert_throws_dom("IndexSizeError", function () {
+ tbody.insertRow(tbody.rows.length + 1);
+ });
+}, "HTMLTableSectionElement insertRow(rows.length + 1)");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-tbody-element/rows.html b/testing/web-platform/tests/html/semantics/tabular-data/the-tbody-element/rows.html
new file mode 100644
index 0000000000..eb155de774
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-tbody-element/rows.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>'tbody' element, 'rows' attribute</title>
+<link rel="author" title="Corey Farwell" href="mailto:coreyf@rwell.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/semantics/tabular-data/html-table-section-element.js"></script>
+
+<div id ="log"></div>
+
+<script>
+test(function () {
+ testRowsAttribute('tbody');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-tfoot-element/rows.html b/testing/web-platform/tests/html/semantics/tabular-data/the-tfoot-element/rows.html
new file mode 100644
index 0000000000..fe70d6f286
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-tfoot-element/rows.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>'tfoot' element, 'rows' attribute</title>
+<link rel="author" title="Corey Farwell" href="mailto:coreyf@rwell.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/semantics/tabular-data/html-table-section-element.js"></script>
+
+<div id ="log"></div>
+
+<script>
+test(function () {
+ testRowsAttribute('tfoot');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-thead-element/rows.html b/testing/web-platform/tests/html/semantics/tabular-data/the-thead-element/rows.html
new file mode 100644
index 0000000000..7830281a01
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-thead-element/rows.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>'thead' element, 'rows' attribute</title>
+<link rel="author" title="Corey Farwell" href="mailto:coreyf@rwell.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/semantics/tabular-data/html-table-section-element.js"></script>
+
+<div id ="log"></div>
+
+<script>
+test(function () {
+ testRowsAttribute('thead');
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/cells.html b/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/cells.html
new file mode 100644
index 0000000000..2678d3b1c2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/cells.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLTableRowElement#cells</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<table>
+ <tr id="testTr">
+ <td>First</td>
+ <div><td>Second</td></div>
+ <td>Third
+ <table>
+ <tr><td>Nested first</td></tr>
+ </table>
+ </td>
+ <img>
+ </tr>
+</table>
+<script>
+var tr = document.getElementById("testTr");
+
+test(function () {
+ tr.insertBefore(document.createElementNS("foo", "td"), tr.children[1]);
+ assert_array_equals(tr.cells, [tr.children[0], tr.children[2], tr.children[3]]);
+}, "HTMLTableRowElement cells ignores nested tables and non-HTML elements");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/deleteCell.html b/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/deleteCell.html
new file mode 100644
index 0000000000..9962617a71
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/deleteCell.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLTableRowElement#deleteCell</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>
+
+<table>
+ <tr id="testTr">
+ <td>ABCDE</td>
+ <td>12345</td>
+ <td>ABC12</td>
+ </tr>
+</table>
+
+<script>
+
+var tr = document.getElementById("testTr");
+
+test(function () {
+ tr.deleteCell(0);
+ assert_equals(tr.cells[0].innerHTML, "12345");
+ assert_equals(tr.cells.length, 2);
+}, "HTMLTableRowElement deleteCell(0)");
+
+test(function () {
+ tr.deleteCell(-1);
+ assert_equals(tr.cells[tr.cells.length - 1].innerHTML, "12345");
+ assert_equals(tr.cells.length, 1);
+}, "HTMLTableRowElement deleteCell(-1)");
+
+test(function () {
+ assert_throws_dom("IndexSizeError", function () {
+ tr.deleteCell(-2);
+ });
+}, "HTMLTableRowElement deleteCell(-2)");
+
+test(function () {
+ assert_throws_dom("IndexSizeError", function () {
+ tr.deleteCell(tr.cells.length);
+ });
+}, "HTMLTableRowElement deleteCell(cells.length)");
+
+test(function () {
+ assert_equals(tr.cells.length, 1);
+ tr.deleteCell(-1);
+ assert_equals(tr.cells.length, 0);
+ tr.deleteCell(-1);
+ assert_equals(tr.cells.length, 0);
+}, "HTMLTableRowElement deleteCell(-1) with no cells");
+
+test(function () {
+ assert_equals(tr.cells.length, 0);
+ assert_throws_dom("IndexSizeError", function () {
+ tr.deleteCell(0);
+ });
+}, "HTMLTableRowElement deleteCell(0) with no cells");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/insertCell.html b/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/insertCell.html
new file mode 100644
index 0000000000..11cd213fe7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/insertCell.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLTableRowElement#insertCell</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>
+
+<table>
+ <tr id="testTr"></tr>
+</table>
+
+<script>
+
+var tr = document.getElementById("testTr");
+
+test(function () {
+ var tdEle = tr.insertCell(0);
+ assert_equals(tr.cells[0], tdEle);
+ assert_equals(tr.cells.length, 1);
+}, "HTMLTableRowElement insertCell(0)");
+
+test(function () {
+ var tdEle = tr.insertCell(-1);
+ assert_equals(tr.cells[tr.cells.length - 1], tdEle);
+ assert_equals(tr.cells.length, 2);
+}, "HTMLTableRowElement insertCell(-1)");
+
+
+test(function () {
+ var tdEle = tr.insertCell(tr.cells.length);
+ assert_equals(tr.cells[tr.cells.length - 1], tdEle);
+ assert_equals(tr.cells.length, 3);
+}, "HTMLTableRowElement insertCell(cells.length)");
+
+test(function () {
+ var tdEle = tr.insertCell();
+ assert_equals(tr.cells[tr.cells.length - 1], tdEle);
+ assert_equals(tr.cells.length, 4);
+}, "HTMLTableRowElement insertCell()");
+
+test(function () {
+ assert_throws_dom("IndexSizeError", function () {
+ tr.insertCell(-2);
+ });
+}, "HTMLTableRowElement insertCell(-2)");
+
+test(function () {
+ assert_throws_dom("IndexSizeError", function () {
+ tr.insertCell(tr.cells.length + 1);
+ });
+}, "HTMLTableRowElement insertCell(cells.length + 1)");
+
+test(function () {
+ var table = document.createElementNS("http://www.w3.org/1999/xhtml", "foo:table")
+ var row = table.insertRow(0);
+ var cell = row.insertCell(0);
+
+ assert_equals(row.cells[0], cell);
+ assert_equals(cell.prefix, null);
+}, "HTMLTableRowElement insertCell will not copy table's prefix");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/rowIndex.html b/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/rowIndex.html
new file mode 100644
index 0000000000..117712563d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/rowIndex.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<title>HTMLTableRowElement.rowIndex</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var row = document.createElement("table")
+ .appendChild(document.createElement("div"))
+ .appendChild(document.createElement("tr"));
+ assert_equals(row.rowIndex, -1);
+});
+test(function() {
+ var row = document.createElement("table")
+ .appendChild(document.createElement("thead"))
+ .appendChild(document.createElement("tr"));
+ assert_equals(row.rowIndex, 0);
+});
+test(function() {
+ var row = document.createElement("table")
+ .appendChild(document.createElement("tbody"))
+ .appendChild(document.createElement("tr"));
+ assert_equals(row.rowIndex, 0);
+});
+test(function() {
+ var row = document.createElement("table")
+ .appendChild(document.createElement("tfoot"))
+ .appendChild(document.createElement("tr"));
+ assert_equals(row.rowIndex, 0);
+});
+test(function() {
+ var row = document.createElement("table")
+ .appendChild(document.createElement("tr"));
+ assert_equals(row.rowIndex, 0);
+});
+test(function() {
+ var row = document.createElementNS("", "table")
+ .appendChild(document.createElement("thead"))
+ .appendChild(document.createElement("tr"));
+ assert_equals(row.rowIndex, -1);
+});
+test(function() {
+ var row = document.createElementNS("", "table")
+ .appendChild(document.createElement("tbody"))
+ .appendChild(document.createElement("tr"));
+ assert_equals(row.rowIndex, -1);
+});
+test(function() {
+ var row = document.createElementNS("", "table")
+ .appendChild(document.createElement("tfoot"))
+ .appendChild(document.createElement("tr"));
+ assert_equals(row.rowIndex, -1);
+});
+test(function() {
+ var row = document.createElementNS("", "table")
+ .appendChild(document.createElement("tr"));
+ assert_equals(row.rowIndex, -1);
+});
+test(function() {
+ var row = document.createElement("table")
+ .appendChild(document.createElementNS("", "thead"))
+ .appendChild(document.createElement("tr"));
+ assert_equals(row.rowIndex, -1);
+});
+test(function() {
+ var row = document.createElement("table")
+ .appendChild(document.createElementNS("", "tbody"))
+ .appendChild(document.createElement("tr"));
+ assert_equals(row.rowIndex, -1);
+});
+test(function() {
+ var row = document.createElement("table")
+ .appendChild(document.createElementNS("", "tfoot"))
+ .appendChild(document.createElement("tr"));
+ assert_equals(row.rowIndex, -1);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/sectionRowIndex.html b/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/sectionRowIndex.html
new file mode 100644
index 0000000000..ef5366739e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/tabular-data/the-tr-element/sectionRowIndex.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>HTMLTableRowElement.sectionRowIndex</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<table>
+ <thead>
+ <tr id="ht1"></tr>
+ </thead>
+ <tr id="t1"></tr>
+ <tr id="t2">
+ <td>
+ <table>
+ <thead>
+ <tr id="nht1"></tr>
+ </thead>
+ <tr></tr>
+ <tr id="nt1"></tr>
+ <tbody>
+ <tr id="nbt1"></tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ <tbody>
+ <tr></tr>
+ <tr id="bt1"></tr>
+ </tbody>
+ <tfoot>
+ <tr></tr>
+ <tr></tr>
+ <tr id="ft1"></tr>
+ </tfoot>
+</table>
+
+<script>
+test(function() {
+ var tHeadRow = document.getElementById('ht1');
+ assert_equals(tHeadRow.sectionRowIndex, 0);
+}, "Row in thead in HTML");
+
+test(function() {
+ var tRow1 = document.getElementById('t1');
+ assert_equals(tRow1.sectionRowIndex, 0);
+}, "Row in implicit tbody in HTML");
+
+test(function() {
+ var tRow2 = document.getElementById('t2');
+ assert_equals(tRow2.sectionRowIndex, 1);
+}, "Other row in implicit tbody in HTML");
+
+test(function() {
+ var tBodyRow = document.getElementById('bt1');
+ assert_equals(tBodyRow.sectionRowIndex, 1);
+}, "Row in explicit tbody in HTML");
+
+test(function() {
+ var tFootRow = document.getElementById('ft1');
+ assert_equals(tFootRow.sectionRowIndex, 2);
+}, "Row in tfoot in HTML");
+
+test(function() {
+ var childHeadRow = document.getElementById('nht1');
+ assert_equals(childHeadRow.sectionRowIndex, 0);
+}, "Row in thead in nested table in HTML");
+
+test(function() {
+ var childRow = document.getElementById('nt1');
+ assert_equals(childRow.sectionRowIndex, 1);
+}, "Row in implicit tbody in nested table in HTML");
+
+test(function() {
+ var childBodyRow = document.getElementById('nbt1');
+ assert_equals(childBodyRow.sectionRowIndex, 0);
+}, "Row in explicit tbody in nested table in HTML");
+
+/* script create element test */
+var mkTrElm = function (elst) {
+ var elm = document.createElement("table");
+ elst.forEach(function(item) {
+ elm = elm.appendChild(document.createElement(item));
+ });
+ return elm.appendChild(document.createElement("tr"));
+};
+
+test(function() {
+ assert_equals(mkTrElm([]).sectionRowIndex, 0);
+}, "Row in script-created table");
+
+test(function() {
+ assert_equals(mkTrElm(["div"]).sectionRowIndex, -1);
+}, "Row in script-created div in table");
+
+test(function() {
+ assert_equals(mkTrElm(["thead"]).sectionRowIndex, 0);
+}, "Row in script-created thead in table");
+
+test(function() {
+ assert_equals(mkTrElm(["tbody"]).sectionRowIndex, 0);
+}, "Row in script-created tbody in table");
+
+test(function() {
+ assert_equals(mkTrElm(["tfoot"]).sectionRowIndex, 0);
+}, "Row in script-created tfoot in table");
+
+test(function() {
+ assert_equals(mkTrElm(["tbody", "tr"]).sectionRowIndex, -1);
+}, "Row in script-created tr in tbody in table");
+
+test(function() {
+ assert_equals(mkTrElm(["tbody", "tr", "td"]).sectionRowIndex, -1);
+}, "Row in script-created td in tr in tbody in table");
+
+test(function() {
+ assert_equals(mkTrElm(["tbody", "tr", "td", "table"]).sectionRowIndex, 0);
+}, "Row in script-created nested table");
+
+test(function() {
+ assert_equals(mkTrElm(["tbody", "tr", "td", "table", "thead"]).sectionRowIndex, 0);
+}, "Row in script-created thead in nested table");
+
+test(function() {
+ assert_equals(mkTrElm(["tbody", "tr", "td", "table", "tbody"]).sectionRowIndex, 0);
+}, "Row in script-created tbody in nested table");
+
+test(function() {
+ assert_equals(mkTrElm(["tbody", "tr", "td", "table", "tfoot"]).sectionRowIndex, 0);
+}, "Row in script-created tfoot in nested table");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/historical.html b/testing/web-platform/tests/html/semantics/text-level-semantics/historical.html
new file mode 100644
index 0000000000..7fe83a95ed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/historical.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<title>Historical text-level element features should not be supported</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+function t(property, tagNames) {
+ if (typeof tagNames === "string") {
+ tagNames = [tagNames];
+ }
+ tagNames.forEach(function(tagName) {
+ test(function() {
+ assert_false(property in document.createElement(tagName));
+ }, tagName + '.' + property + ' should not be supported');
+ });
+}
+
+// <area> and <link> are in other sections in the spec, but we'll test them here together with <a>
+
+// removed in https://github.com/whatwg/html/commit/790479ab1ba143efc27d1f92cd0465627df48fb0
+t('hreflang', 'area');
+t('type', 'area');
+
+// renamed to dateTime in https://github.com/whatwg/html/commit/8b6732237c7021cd61e3c3463146234ca8ce5bad
+t('datetime', 'time');
+
+// removed in https://github.com/whatwg/html/commit/66fcb2357f205448fe2f40d7834a1e8ea2ed283b
+t('media', ['a', 'area']);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-click-handler-with-null-browsing-context-crash.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-click-handler-with-null-browsing-context-crash.html
new file mode 100644
index 0000000000..a9fd6b82bd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-click-handler-with-null-browsing-context-crash.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>HTMLAnchorElement.onclick with null browsing conext</title>
+<link rel="author" title="Nate Chapin" href="mailto:japhet@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/C/#the-a-element">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1083721">
+<meta name="assert" content="An <a> from a discarded browsing context that is cloned into a new document should not crash when clicked"/>
+<body>
+<iframe id="i" src="resources/a-onclick-handler-iframe.html"></iframe>
+<script>
+window.onload = () => {
+ var range = i.contentDocument.createRange();
+ range.selectNodeContents(i.contentDocument.body);
+ i.remove();
+
+ // Clone the <a> into this document, and ensure clicking it does not crash.
+ document.body.appendChild(range.cloneContents());
+ var a = document.getElementsByTagName('a')[0];
+ a.click();
+};
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-404.py b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-404.py
new file mode 100644
index 0000000000..abb85139b3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-404.py
@@ -0,0 +1,2 @@
+def main(request, response):
+ return 404, [(b"Content-Type", b"text/html")], b'Some content for the masses.' * 100
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-click-404.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-click-404.html
new file mode 100644
index 0000000000..3c8adc0b97
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-click-404.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Clicking on an &lt;a> element with a download attribute and href that leads to 404 should not navigate</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-a-element:activation-behaviour">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/links.html#attr-hyperlink-download">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+"use strict";
+async_test(t => {
+ const errorFrame = document.createElement("iframe");
+
+ errorFrame.addEventListener("load", t.step_func(function () {
+ errorFrame.contentWindow.addEventListener(
+ "beforeunload", t.unreached_func("Navigated instead of downloading"));
+
+ errorFrame.contentDocument.querySelector("#error-url").click();
+ t.step_timeout(() => t.done(), 1000);
+ }));
+ errorFrame.src = "resources/a-download-404.html";
+ document.body.appendChild(errorFrame);
+}, "Do not navigate to 404 for anchor with download");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-click-redirect-to-javascript.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-click-redirect-to-javascript.html
new file mode 100644
index 0000000000..09f63b6f4d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-click-redirect-to-javascript.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Clicking on an &lt;a> element with a download attribute and href that redirects to 'javascript:' should not navigate or execute</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-a-element:activation-behaviour">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/links.html#attr-hyperlink-download">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+"use strict";
+async_test(t => {
+ const errorFrame = document.createElement("iframe");
+
+ errorFrame.addEventListener("load", t.step_func(function () {
+ assert_false(errorFrame.contentWindow.executed);
+ errorFrame.contentWindow.addEventListener(
+ "beforeunload", t.unreached_func("Page should not navigate."));
+
+ errorFrame.contentDocument.querySelector("#error-url").click();
+ t.step_timeout(_ => {
+ assert_false(errorFrame.contentWindow.executed, "Redirecting to javascript: was suppressed.");
+ t.done();
+ }, 1000);
+ }));
+ errorFrame.src = "resources/a-download-redirect-to-javascript.html";
+ document.body.appendChild(errorFrame);
+}, "Do not navigate or execute JS when redirecting a download to 'javascript:..'");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-click.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-click.html
new file mode 100644
index 0000000000..22d329f245
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-download-click.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Clicking on an &lt;a> element with a download attribute must not throw an exception</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-a-element:activation-behaviour">
+<link rel="help" href="https://github.com/whatwg/html/issues/2116">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+async_test(t => {
+ const frame = document.createElement("iframe");
+
+ frame.addEventListener("load", t.step_func(function () {
+ frame.contentWindow.addEventListener(
+ "beforeunload", t.unreached_func("Navigated instead of downloading"));
+ const string = "test";
+ const blob = new Blob([string], { type: "text/html" });
+
+ const link = frame.contentDocument.querySelector("#blob-url");
+ link.href = URL.createObjectURL(blob);
+
+ link.click();
+
+ t.step_timeout(() => t.done(), 1000);
+ }));
+ frame.src = "resources/a-download-click.html";
+ document.body.appendChild(frame);
+}, "Clicking on an <a> element with a download attribute must not throw an exception");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-stringifier.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-stringifier.html
new file mode 100644
index 0000000000..1085a74aa6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a-stringifier.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>HTMLAnchorElement stringifier</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://webidl.spec.whatwg.org/#es-stringifier">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/common/stringifiers.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ test_stringifier_attribute(document.createElement("a"), "href", false);
+ var a = document.createElement("a");
+ a.setAttribute("href", "foo");
+ test_stringifier_attribute(a, "href", false);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a.text-getter-01.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a.text-getter-01.html
new file mode 100644
index 0000000000..e0bb73be0f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a.text-getter-01.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>HTMLAnchorElement.text getting</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-a-text">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>var b</script>
+<div id="test">
+<a href="a">a b c </a>
+<a href="b">a <!--b-->b c </a>
+<a href="c">a <b>b</b> c </a>
+<a href="d">a <script>b</script> c </a>
+<script>
+var e = document.getElementById("test")
+ .appendChild(document.createElement("a"));
+e.href = "d";
+e.appendChild(document.createTextNode("a "));
+e.appendChild(document.createTextNode("b "));
+e.appendChild(document.createTextNode("c "));
+</script>
+</div>
+<script>
+test(function() {
+ var list = document.getElementById("test")
+ .getElementsByTagName("a");
+ for (var i = 0, il = list.length; i < il; ++i) {
+ test(function() {
+ assert_equals(list[i].text, list[i].textContent);
+ assert_equals(list[i].text, "a b c ");
+ }, "Test for anchor " + i);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a.text-setter-01.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a.text-setter-01.html
new file mode 100644
index 0000000000..879a9e3d08
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/a.text-setter-01.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>HTMLAnchorElement.text setting</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-a-text">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<div id="test">
+<a href="a">a b c</a>
+<a href="b">a <!--b--> c</a>
+<a href="c">a <b>b</b> c</a>
+<script>
+var d = document.getElementById("test")
+ .appendChild(document.createElement("a"));
+d.href = "d";
+d.appendChild(document.createTextNode("a "));
+d.appendChild(document.createTextNode("b "));
+d.appendChild(document.createTextNode("c "));
+</script>
+</div>
+<script>
+test(function() {
+ var list = document.getElementById("test")
+ .getElementsByTagName("a");
+ for (var i = 0, il = list.length; i < il; ++i) {
+ test(function() {
+ list[i].text = "x";
+ assert_equals(list[i].text, "x");
+ assert_equals(list[i].textContent, "x");
+ assert_equals(list[i].firstChild.data, "x");
+ assert_equals(list[i].childNodes.length, 1);
+
+ list[i].textContent = "y";
+ assert_equals(list[i].text, "y");
+ assert_equals(list[i].textContent, "y");
+ assert_equals(list[i].firstChild.data, "y");
+ assert_equals(list[i].childNodes.length, 1);
+ }, "Test for anchor " + i);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-download-404.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-download-404.html
new file mode 100644
index 0000000000..8c5d8f4565
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-download-404.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<a id="error-url" href="../a-download-404.py" download="html.html">Click me</a>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-download-click.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-download-click.html
new file mode 100644
index 0000000000..7d36c21d1e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-download-click.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<a id="blob-url" download="foo.html">Click me</a>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-download-redirect-to-javascript.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-download-redirect-to-javascript.html
new file mode 100644
index 0000000000..4ff8b61e3b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-download-redirect-to-javascript.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<script>
+ window.executed = false;
+</script>
+<a id="error-url" href="/common/redirect.py?location=javascript:window.executed=true" download="html.html">Click me</a>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-onclick-handler-iframe.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-onclick-handler-iframe.html
new file mode 100644
index 0000000000..711e40f9d4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-a-element/resources/a-onclick-handler-iframe.html
@@ -0,0 +1 @@
+<a id="a" href="about:blank" onclick="return false">link</a>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-b-element/b-usage-notref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-b-element/b-usage-notref.html
new file mode 100644
index 0000000000..3d3c46a281
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-b-element/b-usage-notref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Reference File</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+
+<p>You enter a small room. Your sword glows brighter. A rat scurries past the corner wall.</p>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-b-element/b-usage.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-b-element/b-usage.html
new file mode 100644
index 0000000000..ff2105dcae
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-b-element/b-usage.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML test: b - highlight keywords</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="mismatch" href="b-usage-notref.html">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-b-element"/>
+
+<p>You enter a small room. Your <b>sword</b> glows brighter. A <b>rat</b> scurries past the corner wall.</p>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-auto-dir-default-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-auto-dir-default-ref.html
new file mode 100644
index 0000000000..eff61bb419
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-auto-dir-default-ref.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 500px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;[:)], [+- a &#x05D1;], [d &#x05D2; 1]...&#x202C;</div>
+ <div dir="rtl">&#x202D;...[d &#x05D2; 1] ,[+- a &#x05D1;] ,[:)]&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;[:)], [+- a &#x05D1;], [d &#x05D2; 1]...&#x202C;</div>
+ <div dir="rtl">&#x202D;...[d &#x05D2; 1] ,[+- a &#x05D1;] ,[:)]&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-auto-dir-default.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-auto-dir-default.html
new file mode 100644
index 0000000000..3a9d90c76b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-auto-dir-default.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: has dir=auto by default</title>
+ <link rel="match" href="bdi-auto-dir-default-ref.html"/>
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'The dir global attribute defaults to auto on this element (it never inherits from the parent
+ element like with other elements).'"/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 500px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ In each DIV of the test:
+ - the first BDI, having no characters with strong direction, should inherit the parent direction;
+ - the second BDI, having an LTR character first, should be LTR by default;
+ - the third BDI, having an RTL character first, should be RTL by default.
+ </div>
+ <div class="test">
+ <div dir="ltr"><bdi>[:)]</bdi>, <bdi>[+- a &#x05D1;]</bdi>, <bdi>[1 &#x05D2; d]</bdi>...</div>
+ <div dir="rtl"><bdi>[(:]</bdi>, <bdi>[+- a &#x05D1;]</bdi>, <bdi>[1 &#x05D2; d]</bdi>...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;[:)], [+- a &#x05D1;], [d &#x05D2; 1]...&#x202C;</div>
+ <div dir="rtl">&#x202D;...[d &#x05D2; 1] ,[+- a &#x05D1;] ,[:)]&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-missing-pdf-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-missing-pdf-ref.html
new file mode 100644
index 0000000000..b4d44c5101
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-missing-pdf-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;cb&#x05D0;de...&#x202C;</div>
+ <div dir="ltr">&#x202D;cb&#x05D0;de...&#x202C;</div>
+ <div dir="ltr">&#x202D;cb&#x05D0;de...&#x202C;</div>
+ <div dir="rtl">&#x202D;...&#x05D4;&#x05D3;a&#x05D1;&#x05D2;&#x202C;</div>
+ <div dir="rtl">&#x202D;...&#x05D4;&#x05D3;a&#x05D1;&#x05D2;&#x202C;</div>
+ <div dir="rtl">&#x202D;...&#x05D4;&#x05D3;a&#x05D1;&#x05D2;&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;cb&#x05D0;de...&#x202C;</div>
+ <div dir="ltr">&#x202D;cb&#x05D0;de...&#x202C;</div>
+ <div dir="ltr">&#x202D;cb&#x05D0;de...&#x202C;</div>
+ <div dir="rtl">&#x202D;...&#x05D4;&#x05D3;a&#x05D1;&#x05D2;&#x202C;</div>
+ <div dir="rtl">&#x202D;...&#x05D4;&#x05D3;a&#x05D1;&#x05D2;&#x202C;</div>
+ <div dir="rtl">&#x202D;...&#x05D4;&#x05D3;a&#x05D1;&#x05D2;&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-missing-pdf.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-missing-pdf.html
new file mode 100644
index 0000000000..1ce9da6b76
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-missing-pdf.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: neutral when contains LRO or RLO without PDF</title>
+ <link rel="match" href="bdi-neutral-missing-pdf-ref.html"/>
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'
+ Thus, if a BDI contains LRO or RLO characters lacking a matching PDF, these must not affect
+ the visual ordering of the content outside the BDI."/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202E; - the RLO (right-to-left-override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO and RLO.
+ If the BDI in the test's first DIV were a SPAN, the RLO it contains, not being closed by a
+ PDF, would visually reorder the de into ed.
+ </div>
+ <div class="test">
+ <div dir="ltr"><bdi>&#x05D0;&#x202E;bc</bdi>de...</div>
+ <div dir="ltr"><bdi dir="ltr">&#x05D0;&#x202E;bc</bdi>de...</div>
+ <div dir="ltr"><bdi dir="rtl">&#x05D0;&#x202E;bc</bdi>de...</div>
+ <div dir="rtl"><bdi>a&#x202D;&#x05D1;&#x05D2;</bdi>&#x05D3;&#x05D4;...</div>
+ <div dir="rtl"><bdi dir="ltr">a&#x202D;&#x05D1;&#x05D2;</bdi>&#x05D3;&#x05D4;...</div>
+ <div dir="rtl"><bdi dir="rtl">a&#x202D;&#x05D1;&#x05D2;</bdi>&#x05D3;&#x05D4;...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;cb&#x05D0;de...&#x202C;</div>
+ <div dir="ltr">&#x202D;cb&#x05D0;de...&#x202C;</div>
+ <div dir="ltr">&#x202D;cb&#x05D0;de...&#x202C;</div>
+ <div dir="rtl">&#x202D;...&#x05D4;&#x05D3;a&#x05D1;&#x05D2;&#x202C;</div>
+ <div dir="rtl">&#x202D;...&#x05D4;&#x05D3;a&#x05D1;&#x05D2;&#x202C;</div>
+ <div dir="rtl">&#x202D;...&#x05D4;&#x05D3;a&#x05D1;&#x05D2;&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-nested-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-nested-ref.html
new file mode 100644
index 0000000000..d5d7674a45
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-nested-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 500px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;1 + [a + [3 + [b + 4] + &#x05D1;] + 2] + &#x05D0;&#x202C;</div>
+ <div dir="ltr">&#x202D;1 + [a + [3 + [b + 4] + &#x05D1;] + 2] + &#x05D0;&#x202C;</div>
+ <div dir="ltr">&#x202D;1 + [a + [3 + [b + 4] + &#x05D1;] + 2] + &#x05D0;&#x202C;</div>
+ <div dir="rtl">&#x202D;a + [1 + [b + [3 + &#x05D1;] + 2] + &#x05D0;] + 0&#x202C;</div>
+ <div dir="rtl">&#x202D;a + [1 + [b + [3 + &#x05D1;] + 2] + &#x05D0;] + 0&#x202C;</div>
+ <div dir="rtl">&#x202D;a + [1 + [b + [3 + &#x05D1;] + 2] + &#x05D0;] + 0&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;1 + [a + [3 + [b + 4] + &#x05D1;] + 2] + &#x05D0;&#x202C;</div>
+ <div dir="ltr">&#x202D;1 + [a + [3 + [b + 4] + &#x05D1;] + 2] + &#x05D0;&#x202C;</div>
+ <div dir="ltr">&#x202D;1 + [a + [3 + [b + 4] + &#x05D1;] + 2] + &#x05D0;&#x202C;</div>
+ <div dir="rtl">&#x202D;a + [1 + [b + [3 + &#x05D1;] + 2] + &#x05D0;] + 0&#x202C;</div>
+ <div dir="rtl">&#x202D;a + [1 + [b + [3 + &#x05D1;] + 2] + &#x05D0;] + 0&#x202C;</div>
+ <div dir="rtl">&#x202D;a + [1 + [b + [3 + &#x05D1;] + 2] + &#x05D0;] + 0&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-nested.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-nested.html
new file mode 100644
index 0000000000..158576885c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-nested.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: neutral when nested</title>
+ <link rel="match" href="bdi-neutral-nested-ref.html"/>
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'
+ This must apply when a BDI is nested within a BDI."/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 500px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x05D0; + <bdi>[a + <bdi>[&#x05D1; + <bdi>[b + 4]</bdi> + 3]</bdi> + 2]</bdi> + 1</div>
+ <div dir="ltr">&#x05D0; + <bdi dir="rtl">[a + <bdi dir="ltr">[&#x05D1; + <bdi dir="rtl">[b + 4]</bdi> + 3]</bdi> + 2]</bdi> + 1</div>
+ <div dir="ltr">&#x05D0; + <bdi dir="ltr">[a + <bdi dir="rtl">[&#x05D1; + <bdi dir="ltr">[b + 4]</bdi> + 3]</bdi> + 2]</bdi> + 1</div>
+ <div dir="rtl">a + <bdi>[&#x05D0; + <bdi>[b + <bdi>[&#x05D1; + 3]</bdi> + 2]</bdi> + 1]</bdi> + 0</div>
+ <div dir="rtl">a + <bdi dir="ltr">[&#x05D0; + <bdi dir="rtl">[b + <bdi dir="ltr">[&#x05D1; + 3]</bdi> + 2]</bdi> + 1]</bdi> + 0</div>
+ <div dir="rtl">a + <bdi dir="rtl">[&#x05D0; + <bdi dir="ltr">[b + <bdi dir="rtl">[&#x05D1; + 3]</bdi> + 2]</bdi> + 1]</bdi> + 0</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;1 + [a + [3 + [b + 4] + &#x05D1;] + 2] + &#x05D0;&#x202C;</div>
+ <div dir="ltr">&#x202D;1 + [a + [3 + [b + 4] + &#x05D1;] + 2] + &#x05D0;&#x202C;</div>
+ <div dir="ltr">&#x202D;1 + [a + [3 + [b + 4] + &#x05D1;] + 2] + &#x05D0;&#x202C;</div>
+ <div dir="rtl">&#x202D;a + [1 + [b + [3 + &#x05D1;] + 2] + &#x05D0;] + 0&#x202C;</div>
+ <div dir="rtl">&#x202D;a + [1 + [b + [3 + &#x05D1;] + 2] + &#x05D0;] + 0&#x202C;</div>
+ <div dir="rtl">&#x202D;a + [1 + [b + [3 + &#x05D1;] + 2] + &#x05D0;] + 0&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-number-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-number-ref.html
new file mode 100644
index 0000000000..df7af7778a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-number-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;&#x05D0; - [1]...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0; - [1]...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0; - [1]...&#x202C;</div>
+ <div dir="rtl">&#x202D;...[1] - a&#x202C;</div>
+ <div dir="rtl">&#x202D;...[1] - a&#x202C;</div>
+ <div dir="rtl">&#x202D;...[1] - a&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0; - [1]...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0; - [1]...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0; - [1]...&#x202C;</div>
+ <div dir="rtl">&#x202D;...[1] - a&#x202C;</div>
+ <div dir="rtl">&#x202D;...[1] - a&#x202C;</div>
+ <div dir="rtl">&#x202D;...[1] - a&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-number.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-number.html
new file mode 100644
index 0000000000..37e467c173
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-number.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: neutral when number</title>
+ <link rel="match" href="bdi-neutral-number-ref.html"/>
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'"/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ If the BDI in the test's first DIV were a SPAN, the 1 inside it would be visually ordered
+ to the left of the &#x05D0;.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x05D0; - <bdi>[1]</bdi>...</div>
+ <div dir="ltr">&#x05D0; - <bdi dir="ltr">[1]</bdi>...</div>
+ <div dir="ltr">&#x05D0; - <bdi dir="rtl">[1]</bdi>...</div>
+ <div dir="rtl">a - <bdi>[1]</bdi>...</div>
+ <div dir="rtl">a - <bdi dir="ltr">[1]</bdi>...</div>
+ <div dir="rtl">a - <bdi dir="rtl">[1]</bdi>...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0; - [1]...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0; - [1]...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0; - [1]...&#x202C;</div>
+ <div dir="rtl">&#x202D;...[1] - a&#x202C;</div>
+ <div dir="rtl">&#x202D;...[1] - a&#x202C;</div>
+ <div dir="rtl">&#x202D;...[1] - a&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-separate-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-separate-ref.html
new file mode 100644
index 0000000000..ec8e34627d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-separate-ref.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 500px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;&#x05D0; [1 b] c [d &#x05D4;] &#x05D5;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...f [e &#x05D3;] &#x05D2; [&#x05D1; 1] a&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0; [1 b] c [d &#x05D4;] &#x05D5;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...f [e &#x05D3;] &#x05D2; [&#x05D1; 1] a&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-separate.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-separate.html
new file mode 100644
index 0000000000..7bb8a20811
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-separate.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: paragraph-level container</title>
+ <link rel="match" href="bdi-neutral-separate-ref.html"/>
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'
+ Thus, under no circumstances should any part of the content outside a BDI be visually
+ reordered inside the BDI's content."/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 500px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ If the BDIs in the test's first DIV were just SPANs, the &#x05D0; would appear between the 1
+ and the b, and the &#x05D5; between the d and the &#x05D4;.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x05D0; <bdi>[1 b]</bdi> c <bdi>[d &#x05D4;]</bdi> &#x05D5;...</div>
+ <div dir="rtl">a <bdi>[1 &#x05D1;]</bdi> &#x05D2; <bdi>[&#x05D3; e]</bdi> f...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0; [1 b] c [d &#x05D4;] &#x05D5;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...f [e &#x05D3;] &#x05D2; [&#x05D1; 1] a&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-1-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-1-ref.html
new file mode 100644
index 0000000000..c0f323ea2c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-1-ref.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com" />
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com" />
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com" />
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments {
+ display: none;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="instructions">Test passes if the two boxes below look exactly the same.</div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; [a]&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; [a]&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-1.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-1.html
new file mode 100644
index 0000000000..822120721f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-1.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>HTML Test: BDI: neutral to another BDI</title>
+ <link rel="match" href="bdi-neutral-to-another-bdi-1-ref.html" />
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com" />
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com" />
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element" />
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'
+ Thus, when a BDI contains text of the same strong direction as another BDI following it, the
+ two must not form a directional run as would be the case if the BDIs were just SPANs." />
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments {
+ display: none;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="instructions">Test passes if the two boxes below look exactly the same.</div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ If the BDIs in the test's first DIV were SPANs, the &#x05D1; would be rendered to the left
+ of the &#x05D0;.
+ </div>
+ <div class="test">
+ <div dir="ltr"><bdi>[&#x05D0;]</bdi> &gt; <bdi>[&#x05D1;]</bdi>...</div>
+ <div dir="ltr"><bdi dir="rtl">[&#x05D0;]</bdi> &gt; <bdi dir="rtl">[&#x05D1;]</bdi>...</div>
+ <div dir="ltr"><bdi dir="ltr">[&#x05D0;]</bdi> &gt; <bdi dir="ltr">[&#x05D1;]</bdi>...</div>
+ <div dir="rtl"><bdi>[a]</bdi> &gt; <bdi>[b]</bdi>...</div>
+ <div dir="rtl"><bdi dir="ltr">[a]</bdi> &gt; <bdi dir="ltr">[b]</bdi>...</div>
+ <div dir="rtl"><bdi dir="rtl">[a]</bdi> &gt; <bdi dir="rtl">[b]</bdi>...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; [a]&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-2-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-2-ref.html
new file mode 100644
index 0000000000..9aef97c0ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-2-ref.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com" />
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com" />
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com" />
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments {
+ display: none;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="instructions">Test passes if the two boxes below look exactly the same.</div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-2.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-2.html
new file mode 100644
index 0000000000..85aec46686
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-2.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>HTML Test: BDI: neutral to another immediately following BDI</title>
+ <link rel="match" href="bdi-neutral-to-another-bdi-2-ref.html">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com" />
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com" />
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element" />
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'
+ Thus, when a BDI contains text of the same strong direction as another BDI following it, the
+ two must not form a directional run as would be the case if the BDIs were just SPANs, even if
+ the two BDIs are not separated by anything at all." />
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments {
+ display: none;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="instructions">Test passes if the two boxes below look exactly the same.</div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ If the BDIs in the test's first DIV were SPANs, the &#x05D1; would be rendered to the left of
+ the &#x05D0;.
+ </div>
+ <div class="test">
+ <div dir="ltr"><bdi>&#x05D0;</bdi><bdi>&#x05D1;</bdi>...</div>
+ <div dir="ltr"><bdi dir="rtl">&#x05D0;</bdi><bdi dir="rtl">&#x05D1;</bdi>...</div>
+ <div dir="ltr"><bdi dir="ltr">&#x05D0;</bdi><bdi dir="ltr">&#x05D1;</bdi>...</div>
+ <div dir="rtl"><bdi>a</bdi><bdi>b</bdi>...</div>
+ <div dir="rtl"><bdi dir="ltr">a</bdi><bdi dir="ltr">b</bdi>...</div>
+ <div dir="rtl"><bdi dir="rtl">a</bdi><bdi dir="rtl">b</bdi>...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-1-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-1-ref.html
new file mode 100644
index 0000000000..a34d09bd24
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-1-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; &#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; &#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; &#x05D1;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...b &lt; [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...b &lt; [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...b &lt; [a]&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; &#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; &#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; &#x05D1;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...b &lt; [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...b &lt; [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...b &lt; [a]&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-1.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-1.html
new file mode 100644
index 0000000000..76da57c2b9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-1.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: neutral to following letter</title>
+ <link rel="match" href="bdi-neutral-to-letter-following-1-ref.html"/>
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'"/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ If the BDI in the test's first DIV were a SPAN, the &#x05D1; would be rendered to the left
+ of the &#x05D0;
+ </div>
+ <div class="test">
+ <div dir="ltr"><bdi>[&#x05D0;]</bdi> &gt; &#x05D1;...</div>
+ <div dir="ltr"><bdi dir="rtl">[&#x05D0;]</bdi> &gt; &#x05D1;...</div>
+ <div dir="ltr"><bdi dir="ltr">[&#x05D0;]</bdi> &gt; &#x05D1;...</div>
+ <div dir="rtl"><bdi>[a]</bdi> &gt; b...</div>
+ <div dir="rtl"><bdi dir="ltr">[a]</bdi> &gt; b...</div>
+ <div dir="rtl"><bdi dir="rtl">[a]</bdi> &gt; b...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; &#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; &#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] &gt; &#x05D1;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...b &lt; [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...b &lt; [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...b &lt; [a]&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-2-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-2-ref.html
new file mode 100644
index 0000000000..80f36183b0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-2-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-2.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-2.html
new file mode 100644
index 0000000000..ce41983f00
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-2.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: neutral to immediately following letter</title>
+ <link rel="match" href="bdi-neutral-to-letter-following-2-ref.html">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'"/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ If the BDI in the following DIV were a SPAN, the &#x05D1; would be rendered to the left
+ of the &#x05D0;
+ </div>
+ <div class="test">
+ <div dir="ltr"><bdi>&#x05D0;</bdi>&#x05D1;...</div>
+ <div dir="ltr"><bdi dir="rtl">&#x05D0;</bdi>&#x05D1;...</div>
+ <div dir="ltr"><bdi dir="ltr">&#x05D0;</bdi>&#x05D1;...</div>
+ <div dir="rtl"><bdi>a</bdi>b...</div>
+ <div dir="rtl"><bdi dir="ltr">a</bdi>b...</div>
+ <div dir="rtl"><bdi dir="rtl">a</bdi>b...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-1-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-1-ref.html
new file mode 100644
index 0000000000..5e39eabd28
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-1-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;&#x05D0; &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0; &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0; &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; a&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; a&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; a&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0; &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0; &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0; &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; a&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; a&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; a&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-1.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-1.html
new file mode 100644
index 0000000000..46772de642
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-1.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: neutral to preceding letter</title>
+ <link rel="match" href="bdi-neutral-to-letter-preceding-1-ref.html">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'"/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ If the BDI in the test's first DIV were a SPAN, the &#x05D1; would be rendered to the left
+ of the &#x05D0;
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x05D0; &gt; <bdi>[&#x05D1;]</bdi>...</div>
+ <div dir="ltr">&#x05D0; &gt; <bdi dir="rtl">[&#x05D1;]</bdi>...</div>
+ <div dir="ltr">&#x05D0; &gt; <bdi dir="ltr">[&#x05D1;]</bdi>...</div>
+ <div dir="rtl">a &gt; <bdi>[b]</bdi>...</div>
+ <div dir="rtl">a &gt; <bdi dir="ltr">[b]</bdi>...</div>
+ <div dir="rtl">a &gt; <bdi dir="rtl">[b]</bdi>...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0; &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0; &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0; &gt; [&#x05D1;]...&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; a&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; a&#x202C;</div>
+ <div dir="rtl">&#x202D;...[b] &lt; a&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-2-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-2-ref.html
new file mode 100644
index 0000000000..80f36183b0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-2-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-2.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-2.html
new file mode 100644
index 0000000000..192115775c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-2.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: neutral to immediately preceding letter</title>
+ <link rel="match" href="bdi-neutral-to-letter-preceding-2-ref.html"/>
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'"/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ If the BDI in the test's first DIV were a SPAN, the &#x05D1; would be rendered to the left
+ of the &#x05D0;
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x05D0;<bdi>&#x05D1;</bdi>...</div>
+ <div dir="ltr">&#x05D0;<bdi dir="rtl">&#x05D1;</bdi>...</div>
+ <div dir="ltr">&#x05D0;<bdi dir="ltr">&#x05D1;</bdi>...</div>
+ <div dir="rtl">a<bdi>b</bdi>...</div>
+ <div dir="rtl">a<bdi dir="ltr">b</bdi>...</div>
+ <div dir="rtl">a<bdi dir="rtl">b</bdi>...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;&#x05D1;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ <div dir="rtl">&#x202D;...ba&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-1-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-1-ref.html
new file mode 100644
index 0000000000..ad15d468b8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-1-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;[&#x05D0;] (3 reviews)...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] (3 reviews)...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] (3 reviews)...&#x202C;</div>
+ <div dir="rtl">&#x202D;...(3) [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...(3) [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...(3) [a]&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;[&#x05D0;] (3 reviews)...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] (3 reviews)...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] (3 reviews)...&#x202C;</div>
+ <div dir="rtl">&#x202D;...(3) [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...(3) [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...(3) [a]&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-1.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-1.html
new file mode 100644
index 0000000000..ff566737f2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-1.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: neutral to following number</title>
+ <link rel="match" href="bdi-neutral-to-number-following-1-ref.html"/>
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'"/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ If the BDI in the test's first DIV were a SPAN, the 3 would be rendered to the left
+ of the &#x05D0;
+ </div>
+ <div class="test">
+ <div dir="ltr"><bdi>[&#x05D0;]</bdi> (3 reviews)...</div>
+ <div dir="ltr"><bdi dir="rtl">[&#x05D0;]</bdi> (3 reviews)...</div>
+ <div dir="ltr"><bdi dir="ltr">[&#x05D0;]</bdi> (3 reviews)...</div>
+ <div dir="rtl"><bdi>[a]</bdi> (3)...</div>
+ <div dir="rtl"><bdi dir="ltr">[a]</bdi> (3)...</div>
+ <div dir="rtl"><bdi dir="rtl">[a]</bdi> (3)...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;[&#x05D0;] (3 reviews)...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] (3 reviews)...&#x202C;</div>
+ <div dir="ltr">&#x202D;[&#x05D0;] (3 reviews)...&#x202C;</div>
+ <div dir="rtl">&#x202D;...(3) [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...(3) [a]&#x202C;</div>
+ <div dir="rtl">&#x202D;...(3) [a]&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-2-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-2-ref.html
new file mode 100644
index 0000000000..d0f1097ade
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-2-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="Shai Berger" href="mailto:shai@platonix.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;&#x05D0;1...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;1...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;1...&#x202C;</div>
+ <div dir="rtl">&#x202D;...1a&#x202C;</div>
+ <div dir="rtl">&#x202D;...1a&#x202C;</div>
+ <div dir="rtl">&#x202D;...1a&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0;1...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;1...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;1...&#x202C;</div>
+ <div dir="rtl">&#x202D;...1a&#x202C;</div>
+ <div dir="rtl">&#x202D;...1a&#x202C;</div>
+ <div dir="rtl">&#x202D;...1a&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-2.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-2.html
new file mode 100644
index 0000000000..62a3b50ffe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-2.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: neutral to immediately following number</title>
+ <link rel="match" href="bdi-neutral-to-number-following-2-ref.html"/>
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'"/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ If the BDI in the test's first DIV were a SPAN, the 1 would be rendered to the left
+ of the &#x05D0;
+ </div>
+ <div class="test">
+ <div dir="ltr"><bdi>&#x05D0;</bdi>1...</div>
+ <div dir="ltr"><bdi dir="rtl">&#x05D0;</bdi>1...</div>
+ <div dir="ltr"><bdi dir="ltr">&#x05D0;</bdi>1...</div>
+ <div dir="rtl"><bdi>a</bdi>1...</div>
+ <div dir="rtl"><bdi dir="ltr">a</bdi>1...</div>
+ <div dir="rtl"><bdi dir="rtl">a</bdi>1...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0;1...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;1...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D0;1...&#x202C;</div>
+ <div dir="rtl">&#x202D;...1a&#x202C;</div>
+ <div dir="rtl">&#x202D;...1a&#x202C;</div>
+ <div dir="rtl">&#x202D;...1a&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-surrounding-run-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-surrounding-run-ref.html
new file mode 100644
index 0000000000..d7967c77fc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-surrounding-run-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;&#x05D2; &lt; [b] &lt; &#x05D0;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D2; &lt; [b] &lt; &#x05D0;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D2; &lt; [b] &lt; &#x05D0;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...a &gt; [&#x05D1;] &gt; c&#x202C;</div>
+ <div dir="rtl">&#x202D;...a &gt; [&#x05D1;] &gt; c&#x202C;</div>
+ <div dir="rtl">&#x202D;...a &gt; [&#x05D1;] &gt; c&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D2; &lt; [b] &lt; &#x05D0;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D2; &lt; [b] &lt; &#x05D0;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D2; &lt; [b] &lt; &#x05D0;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...a &gt; [&#x05D1;] &gt; c&#x202C;</div>
+ <div dir="rtl">&#x202D;...a &gt; [&#x05D1;] &gt; c&#x202C;</div>
+ <div dir="rtl">&#x202D;...a &gt; [&#x05D1;] &gt; c&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-surrounding-run.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-surrounding-run.html
new file mode 100644
index 0000000000..bff339ec34
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-surrounding-run.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: neutral to surrounding letters</title>
+ <link rel="match" href="bdi-neutral-to-surrounding-run-ref.html">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'
+ Thus, regardless of its content and its dir attribute (if any), a BDI will not prevent
+ a strongly RTL (or LTR) character preceding it from forming a single directional run with
+ another strongly RTL (LTR) character following it."/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ If the BDI in the test's first DIV were a SPAN, its b would prevent the &#x05D0; and the &#x05D1;
+ from forming a single RTL run and thus keep the &gt;s between from being mirrored into &lt;s.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x05D0; &gt; <bdi>[b]</bdi> &gt; &#x05D2;...</div>
+ <div dir="ltr">&#x05D0; &gt; <bdi dir="ltr">[b]</bdi> &gt; &#x05D2;...</div>
+ <div dir="ltr">&#x05D0; &gt; <bdi dir="rtl">[b]</bdi> &gt; &#x05D2;...</div>
+ <div dir="rtl">a &gt; <bdi>[&#x05D1;]</bdi> &gt; c...</div>
+ <div dir="rtl">a &gt; <bdi dir="ltr">[&#x05D1;]</bdi> &gt; c...</div>
+ <div dir="rtl">a &gt; <bdi dir="rtl">[&#x05D1;]</bdi> &gt; c...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D2; &lt; [b] &lt; &#x05D0;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D2; &lt; [b] &lt; &#x05D0;...&#x202C;</div>
+ <div dir="ltr">&#x202D;&#x05D2; &lt; [b] &lt; &#x05D0;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...a &gt; [&#x05D1;] &gt; c&#x202C;</div>
+ <div dir="rtl">&#x202D;...a &gt; [&#x05D1;] &gt; c&#x202C;</div>
+ <div dir="rtl">&#x202D;...a &gt; [&#x05D1;] &gt; c&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-wrapped-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-wrapped-ref.html
new file mode 100644
index 0000000000..9859de4747
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-wrapped-ref.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">
+ &#x202D;b &lt; &#x05D0;&#x202C;<br/>
+ &#x202D;&gt;&gt;&gt;&#x202C;<br/>
+ &#x202D;&#x05D3; &lt; c...&#x202C;
+ </div>
+ <div dir="rtl">
+ &#x202D;a &gt; &#x05D1;&#x202C;<br/>
+ &#x202D;&lt;&lt;&lt;&#x202C;<br/>
+ &#x202D;...&#x05D2; &gt; d&#x202C;
+ </div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">
+ &#x202D;b &lt; &#x05D0;&#x202C;<br/>
+ &#x202D;&gt;&gt;&gt;&#x202C;<br/>
+ &#x202D;&#x05D3; &lt; c...&#x202C;
+ </div>
+ <div dir="rtl">
+ &#x202D;a &gt; &#x05D1;&#x202C;<br/>
+ &#x202D;&lt;&lt;&lt;&#x202C;<br/>
+ &#x202D;...&#x05D2; &gt; d&#x202C;
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-wrapped.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-wrapped.html
new file mode 100644
index 0000000000..3e21fcb0fd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-wrapped.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: neutral when wrapped</title>
+ <link rel="match" href="bdi-neutral-wrapped-ref.html">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the paragraph-level
+ container that a bdi element finds itself within, the bdi element must be treated
+ like a U+FFFC OBJECT REPLACEMENT CHARACTER.'
+ This should hold even if the BDI's content is wrapped over more than one line."/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#xA0; - Non-breaking space.
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ In the test below, the non-breaking spaces in the BDI's middle "word" make it so long that it
+ must be displayed on a line of its own, with the BDI wrapped before and after it. At the same
+ time, the content surrounding the BDI is supposed to form a single directional run, despite
+ the containing element and the BDI both having the opposite direction, because the BDI must be
+ treated as a neutral. Thus, on the line containing the first part of the BDI, the BDI's
+ content must appear after the content preceding it, and on the line containing the last part
+ of the BDI, the BDI content must appear before the content following it, where both 'before'
+ and 'after' are defined relative to the surrounding directional run.
+ </div>
+ <div class="test">
+ <div dir="ltr">
+ &#x05D0; &gt;
+ <bdi>b
+&gt;&gt;&gt;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
+ c</bdi>
+ &gt; &#x05D3;...
+ </div>
+ <div dir="rtl">
+ a &gt;
+ <bdi>&#x05D1;
+&gt;&gt;&gt;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
+ &#x05D2;</bdi>
+ &gt; d...
+ </div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">
+ &#x202D;b &lt; &#x05D0;&#x202C;<br/>
+ &#x202D;&gt;&gt;&gt;&#x202C;<br/>
+ &#x202D;&#x05D3; &lt; c...&#x202C;
+ </div>
+ <div dir="rtl">
+ &#x202D;a &gt; &#x05D1;&#x202C;<br/>
+ &#x202D;&lt;&lt;&lt;&#x202C;<br/>
+ &#x202D;...&#x05D2; &gt; d&#x202C;
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-paragraph-level-container-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-paragraph-level-container-ref.html
new file mode 100644
index 0000000000..0c74ecf68a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-paragraph-level-container-ref.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com">
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com">
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 500px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x202D;&#x05D0; [1 2 3 b] c [d &#x05D4;?!] &#x05D5;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...f [!?e &#x05D3;] &#x05D2; [&#x05D1; 3 2 1] a&#x202C;</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0; [1 2 3 b] c [d &#x05D4;?!] &#x05D5;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...f [!?e &#x05D3;] &#x05D2; [&#x05D1; 3 2 1] a&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-paragraph-level-container.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-paragraph-level-container.html
new file mode 100644
index 0000000000..f133a95772
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdi-element/bdi-paragraph-level-container.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BDI: paragraph-level container</title>
+ <link rel="match" href="bdi-paragraph-level-container-ref.html"/>
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdi-element"/>
+ <meta name="assert" content="
+ 'For the purposes of applying the bidirectional algorithm to the contents of a bdi element,
+ user agents must treat the element as a paragraph-level container.'
+ Thus, under no circumstances should the content outside a BDI affect the visual
+ ordering of the BDI's content."/>
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 500px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D5; - The first six Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ If the BDIs in the test's first DIV were just SPANs, the &#x05D0; would order the 1 2 3 as
+ 3 2 1, and the &#x05D5; would (with the &#x05D4;) order the ?! as !?.
+ </div>
+ <div class="test">
+ <div dir="ltr">&#x05D0; <bdi>[1 2 3 b]</bdi> c <bdi>[d &#x05D4;?!]</bdi> &#x05D5;...</div>
+ <div dir="rtl">a <bdi>[1 2 3 &#x05D1;]</bdi> &#x05D2; <bdi>[&#x05D3; e?!]</bdi> f...</div>
+ </div>
+ <div class="ref">
+ <div dir="ltr">&#x202D;&#x05D0; [1 2 3 b] c [d &#x05D4;?!] &#x05D5;...&#x202C;</div>
+ <div dir="rtl">&#x202D;...f [!?e &#x05D3;] &#x05D2; [&#x05D1; 3 2 1] a&#x202C;</div>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bdo-child.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bdo-child.html
new file mode 100644
index 0000000000..feadc26d7b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bdo-child.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>HTML Test: bdo - text directionality formatting control for its children</title>
+ <link rel="author" title="Intel" href="http://www.intel.com/">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdo-element">
+ <link rel="match" href="bidi-001-ref.html">
+ <meta name="assert" content="Check if the bdo element represents explicit text directionality formatting control for its children.">
+ </head>
+ <body>
+ <p>Test passes if there is text 'WERBEH'.</p>
+ <bdo dir="rtl">
+ <span>HEBREW</span>
+ </bdo>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bdo-ltr.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bdo-ltr.html
new file mode 100644
index 0000000000..8a7861086a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bdo-ltr.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>HTML Test: The value 'ltr' of dir attribute specifies a left-to-right override</title>
+ <link rel="author" title="Intel" href="http://www.intel.com/">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdo-element">
+ <link rel="match" href="bidi-001-ref.html">
+ <meta name="assert" content="Check if the value ltr of dir attribute specifies a left-to-right override">
+ </head>
+ <body>
+ <p>Test passes if there is text 'WERBEH'.</p>
+ <bdo dir="ltr">WERBEH</bdo>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bdo-override.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bdo-override.html
new file mode 100644
index 0000000000..75a45e198f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bdo-override.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>HTML Test: bdo - override the Unicode bidirectional algorithm</title>
+ <link rel="author" title="Intel" href="http://www.intel.com/">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-bdo-element">
+ <link rel="match" href="bidi-001-ref.html">
+ <meta name="assert" content="Check if authors could override the Unicode bidirectional algorithm
+ by explicitly specifying a direction override of bdo element">
+ </head>
+ <body>
+ <p>Test passes if there is text 'WERBEH'.</p>
+ <p>
+ &#x202E;<bdo dir="ltr">WERBEH</bdo>&#x202C;
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bidi-001-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bidi-001-ref.html
new file mode 100644
index 0000000000..83d2dc4a16
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bidi-001-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>directional type reference</title>
+</head>
+<body>
+<p>Test passes if there is text 'WERBEH'.</p>
+<div>WERBEH</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bidi-001.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bidi-001.html
new file mode 100644
index 0000000000..772dcf43b3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-bdo-element/bidi-001.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>directional type</title>
+<meta content="W3C" name="author">
+<link rel="match" href="bidi-001-ref.html">
+<meta name="assert" content="Test text bidirectionality using the bdo element">
+</head>
+<body dir='ltr'>
+<p>Test passes if there is text 'WERBEH'.</p>
+<bdo dir="rtl">HEBREW</bdo>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi-in-inline-ancestors-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi-in-inline-ancestors-ref.html
new file mode 100644
index 0000000000..c2dd4daa7a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi-in-inline-ancestors-ref.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com" />
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D8; - The first nine Hebrew letters (strongly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ &#x202D;&#x05D0;&#x05D1; &#x05D2;&#x05D3; 1. I like &#x05D4;. fg hi&#x202C;
+ <br/>
+ &#x202D;ab cd 2. &#x05D4; is great! &#x05D5;&#x05D6; &#x05D7;&#x05D8;&#x202C;
+ </div>
+ <div class="ref">
+ &#x202D;&#x05D0;&#x05D1; &#x05D2;&#x05D3; 1. I like &#x05D4;. fg hi&#x202C;
+ <br/>
+ &#x202D;ab cd 2. &#x05D4; is great! &#x05D5;&#x05D6; &#x05D7;&#x05D8;&#x202C;
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi-in-inline-ancestors.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi-in-inline-ancestors.html
new file mode 100644
index 0000000000..89e7f2f1aa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi-in-inline-ancestors.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>HTML Test: BR in inline ancestors</title>
+ <link rel="match" href="br-bidi-in-inline-ancestors-ref.html">
+ <link rel="author" title="Aharon Lanin" href="mailto:aharon@google.com"/>
+ <link rel="author" title="HTML5 bidi test WG" href="mailto:html5bidi@googlegroups.com" />
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-br-element"/>
+ <link rel="help" href="http://www.w3.org/TR/css3-writing-modes/#unicode-bidi" />
+ <meta name="assert" content="
+ 'A br element should separate paragraphs for the purposes of the Unicode bidirectional
+ algorithm.'
+ 'If an inline element is broken around a bidi paragraph boundary (e.g. if split by a block or
+ forced paragraph break), then the bidi control codes corresponding to the end of the element
+ are added before the interruption and the codes corresponding to the start of the element are
+ added after it. (In other words, any embedding levels or overrides started by the element are
+ closed at the paragraph break and reopened on the other side of it.)'" />
+ <style>
+ body{
+ font-size:2em;
+ }
+ .test, .ref {
+ border: medium solid gray;
+ width: 400px;
+ margin: 20px;
+ }
+ .comments { display: none; }
+ </style>
+ </head>
+ <body>
+ <div class="instructions"><p>Test passes if the two boxes below look exactly the same.</p></div>
+ <div class="comments">
+ Key to entities used below:
+ &#x05D0; ... &#x05D8; - The first nine Hebrew letters (strongly RTL).
+ &#x200E; - The LRM (left-to-right mark) formatting character (invisible, stronly LTR).
+ &#x200F; - The RLM (right-to-left mark) formatting character (invisible, stronly RTL).
+ &#x202D; - The LRO (left-to-right override) formatting character.
+ &#x202C; - The PDF (pop directional formatting) formatting character; closes LRO.
+ </div>
+ <div class="test">
+ &#x05D1;&#x05D0;&#x200E;
+ <bdo dir="rtl">ih
+ <bdo dir="ltr">&#x05D2;&#x05D3;
+ <span dir="rtl">fg&#x200F;
+ <span dir="ltr">1. I like &#x05D4;.<br/>
+ 2. &#x05D4; is great!</span>
+ &#x200F;cd</span>
+ &#x05D5;&#x05D6;</bdo>
+ ba</bdo>
+ &#x200E;&#x05D8;&#x05D7;
+ </div>
+ <div class="ref">
+ &#x202D;&#x05D0;&#x05D1; &#x05D2;&#x05D3; 1. I like &#x05D4;. fg hi&#x202C;
+ <br/>
+ &#x202D;ab cd 2. &#x05D4; is great! &#x05D5;&#x05D6; &#x05D7;&#x05D8;&#x202C;
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi-ref.html
new file mode 100644
index 0000000000..f07c077917
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>HTML Test reference: BR separates bidi paragraph</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-br-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">
+ A Hebrew letter and a full stop: &#x05d0;.&lrm;
+ <br />
+ &#x05d0; this line begins with a Hebrew letter.
+ </div>
+</p>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi.html
new file mode 100644
index 0000000000..1dfa6836f3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-br-element/br-bidi.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>HTML Test: BR separates bidi paragraph</title>
+ <link rel="match" href="br-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-br-element"/>
+ <meta name="assert"
+ content="A br element 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">
+ A Hebrew letter and a full stop: &#x05d0;.
+ <br />
+ &#x05d0; this line begins with a Hebrew letter.
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-ruby-element/rt-without-ruby-crash.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-ruby-element/rt-without-ruby-crash.html
new file mode 100644
index 0000000000..3caed3a02e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-ruby-element/rt-without-ruby-crash.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=967255">
+<rt style="display:block;">
+ <div></div>
+</rt>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ test(()=> { }, "No crash");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-ruby-element/ruby-usage-notref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-ruby-element/ruby-usage-notref.html
new file mode 100644
index 0000000000..f5747811ae
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-ruby-element/ruby-usage-notref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Reference File</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+
+<p>君くん子しは和わして同どうぜず</p>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-ruby-element/ruby-usage.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-ruby-element/ruby-usage.html
new file mode 100644
index 0000000000..59c076cd09
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-ruby-element/ruby-usage.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML test: ruby - mark phrasing content</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="mismatch" href="ruby-usage-notref.html">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-ruby-element"/>
+
+<p><ruby>君<rt>くん</ruby><ruby>子<rt>し</ruby>は<ruby>和<rt>わ</ruby>して<ruby>同<rt>どう</ruby>ぜず</p>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-time-element/001.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-time-element/001.html
new file mode 100644
index 0000000000..e1cd0480ac
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-time-element/001.html
@@ -0,0 +1,68 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>HTML time element API</title>
+ <style>
+#time { visibility: hidden; }
+ </style>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-time-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+ <!-- intentionally nested to test parsing rules -->
+ <p id="time"><time pubdate datetime="2000-02-01T03:04:05Z">Dummy text <time>2001-06-07T<time>08:09<time></time></time>Z</time></time></p>
+ <script type="text/javascript">
+function makeTime(dateTime,contents,dateTimeProp) {
+ var timeEl = document.createElement('time');
+ if( dateTime ) {
+ timeEl.setAttribute('datetime',dateTime);
+ }
+ if( contents ) {
+ timeEl.innerHTML = contents;
+ }
+ if( dateTimeProp ) {
+ timeEl.dateTime = dateTimeProp;
+ }
+ return timeEl;
+}
+
+var timep = document.getElementById('time');
+var times = timep.getElementsByTagName('time');
+
+//TIME elements
+test(function () {
+ assert_equals( times.length, 4 );
+}, 'HTML parsing should locate 4 time elements in this document');
+test(function () {
+ assert_true( !!window.HTMLTimeElement );
+}, 'HTMLTimeElement should be exposed for prototyping');
+test(function () {
+ assert_true( makeTime() instanceof window.HTMLTimeElement, 'createElement variant' );
+ assert_true( times[0] instanceof window.HTMLTimeElement, 'HTML parsing variant' );
+}, 'the time elements should be instanceof HTMLTimeElement');
+
+//dateTime
+test(function () {
+ assert_equals( makeTime('2000-02-01T03:04:05Z','2001-02-01T03:04:05Z').dateTime, '2000-02-01T03:04:05Z' );
+}, 'the datetime attribute should be reflected by the .dateTime property');
+test(function () {
+ assert_equals( typeof makeTime().dateTime, 'string', 'typeof test' );
+ assert_equals( makeTime().dateTime, '', 'value test' );
+}, 'the dateTime IDL property should default to an empty string');
+test(function () {
+ assert_equals( makeTime(false,false,'2000-02-01T03:04:05Z').dateTime, '2000-02-01T03:04:05Z' );
+}, 'the dateTime property should be read/write');
+test(function () {
+ assert_equals( makeTime('go fish').dateTime, 'go fish' );
+}, 'the datetime attribute should be reflected by the .dateTime property even if it is invalid');
+test(function () {
+ assert_equals( makeTime(false,'2000-02-01T03:04:05Z').dateTime, '' );
+}, 'the datetime attribute should not reflect the textContent');
+
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-wbr-element/wbr-element-ref.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-wbr-element/wbr-element-ref.html
new file mode 100644
index 0000000000..bedb6d62e8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-wbr-element/wbr-element-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>The wbr element</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<meta name="flags" content="ahem">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+ p {font:15px/1 Ahem;}
+</style>
+<p>Loremipsumdolorsit<br>amet,consectetur<br>adipisicingelit,sed<br>doeiusmodtempor<br>incididuntutlaboreet<br>doloremagnaaliqua.Ut<br>enimadminimveniam,<br>quisnostrud<br>exercitationullamco<br>laborisnisiutaliquip<br>exeacommodo<br>consequat.Duisaute<br>iruredolorin<br>reprehenderitin<br>voluptatevelitesse<br>cillumdoloreeufugiat<br>nullapariatur.<br>Excepteursint<br>occaecatcupidatatnon<br>proident,suntinculpa<br>quiofficiadeserunt<br>mollitanimidest<br>laborum.</p>
diff --git a/testing/web-platform/tests/html/semantics/text-level-semantics/the-wbr-element/wbr-element.html b/testing/web-platform/tests/html/semantics/text-level-semantics/the-wbr-element/wbr-element.html
new file mode 100644
index 0000000000..aa3912028e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/text-level-semantics/the-wbr-element/wbr-element.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>The wbr element</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#rendering">
+<link rel="match" href="wbr-element-ref.html">
+<meta name="flags" content="ahem">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+ p {max-width: 300px; font:15px/1 Ahem;}
+</style>
+<p>AHEM_<wbr>ipsum<wbr>dolor<wbr>sit<wbr>amet,<wbr>consectetur<wbr>adipisicing<wbr>elit,<wbr>sed<wbr>do<wbr>eiusmod<wbr>tempor<wbr>incididunt<wbr>ut<wbr>labore<wbr>et<wbr>dolore<wbr>magna<wbr>aliqua.<wbr>Ut<wbr>enim<wbr>ad<wbr>minim<wbr>veniam,<wbr>quis<wbr>nostrud<wbr>exercitation<wbr>ullamco<wbr>laboris<wbr>nisi<wbr>ut<wbr>aliquip<wbr>ex<wbr>ea<wbr>commodo<wbr>consequat.<wbr>Duis<wbr>aute<wbr>irure<wbr>dolor<wbr>in<wbr>reprehenderit<wbr>in<wbr>voluptate<wbr>velit<wbr>esse<wbr>cillum<wbr>dolore<wbr>eu<wbr>fugiat<wbr>nulla<wbr>pariatur.<wbr>Excepteur<wbr>sint<wbr>occaecat<wbr>cupidatat<wbr>non<wbr>proident,<wbr>sunt<wbr>in<wbr>culpa<wbr>qui<wbr>officia<wbr>deserunt<wbr>mollit<wbr>anim<wbr>id<wbr>est<wbr>laborum.</p>
diff --git a/testing/web-platform/tests/html/semantics/the-link-element/attr-link-fetchpriority.html b/testing/web-platform/tests/html/semantics/the-link-element/attr-link-fetchpriority.html
new file mode 100644
index 0000000000..8aa515fa4c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/the-link-element/attr-link-fetchpriority.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Fetch Priority - Link element</title>
+<meta name="author" title="Dominic Farolino" href="mailto:domfarolino@gmail.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<link id=link1 href=resources/stylesheet.css fetchpriority=high>
+<link id=link2 href=resources/stylesheet.css fetchpriority=low>
+<link id=link3 href=resources/stylesheet.css fetchpriority=auto>
+<link id=link4 href=resources/stylesheet.css fetchpriority=xyz>
+<link id=link5 href=resources/stylesheet.css>
+
+<script>
+ test(() => {
+ assert_equals(link1.fetchPriority, "high", "high fetchPriority is a valid IDL value on the link element");
+ assert_equals(link2.fetchPriority, "low", "low fetchPriority is a valid IDL value on the link element");
+ assert_equals(link3.fetchPriority, "auto", "auto fetchPriority is a valid IDL value on the link element");
+ assert_equals(link4.fetchPriority, "auto", "invalid fetchPriority reflects as 'auto' IDL attribute on the link element");
+ assert_equals(link5.fetchPriority, "auto", "missing fetchPriority reflects as 'auto' IDL attribute on the link element");
+ }, "fetchpriority attribute on <link> elements should reflect valid IDL values");
+
+ test(() => {
+ const link = document.createElement("link");
+ assert_equals(link.fetchPriority, "auto");
+ }, "default fetchpriority attribute on <link> elements should be 'auto'");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/the-link-element/resources/stylesheet.css b/testing/web-platform/tests/html/semantics/the-link-element/resources/stylesheet.css
new file mode 100644
index 0000000000..9d9d772fb4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/the-link-element/resources/stylesheet.css
@@ -0,0 +1,3 @@
+body {
+ background-color: green;
+}