summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/webappapis
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/html/webappapis')
-rw-r--r--testing/web-platform/tests/html/webappapis/animation-frames/callback-cross-realm-report-exception.html29
-rw-r--r--testing/web-platform/tests/html/webappapis/animation-frames/callback-exception.html27
-rw-r--r--testing/web-platform/tests/html/webappapis/animation-frames/callback-handle.html16
-rw-r--r--testing/web-platform/tests/html/webappapis/animation-frames/callback-invoked.html18
-rw-r--r--testing/web-platform/tests/html/webappapis/animation-frames/callback-multicalls.html26
-rw-r--r--testing/web-platform/tests/html/webappapis/animation-frames/callback-timestamp.html17
-rw-r--r--testing/web-platform/tests/html/webappapis/animation-frames/cancel-handle-manual.html51
-rw-r--r--testing/web-platform/tests/html/webappapis/animation-frames/cancel-invoked.html18
-rw-r--r--testing/web-platform/tests/html/webappapis/animation-frames/cancel-pending.html26
-rw-r--r--testing/web-platform/tests/html/webappapis/animation-frames/same-dispatch-time.html31
-rw-r--r--testing/web-platform/tests/html/webappapis/atob/base64.any.js163
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document-close-with-pending-script.html67
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document.close-01.xhtml19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/load-event-after-location-set-during-write.window.js19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/001.html12
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/002.html13
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/003.html14
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/004.html14
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.html14
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.js1
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.html14
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.js1
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.html15
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.js4
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008-1.js3
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.html15
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.js4
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/009.html15
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010-1.js4
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.html15
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.js4
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011-1.js5
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.html15
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.js5
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.html15
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.js5
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.html15
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.js1
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/014.html15
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/015.html16
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/016.html16
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/017.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/018.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/019.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/020.html18
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/021.html18
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/022.html18
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/023.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/024.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/025.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/026.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/027.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/028.html21
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/029.html21
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/030.html21
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/031.html21
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/032.html22
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/033.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/034.html21
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/035.html21
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/036.html21
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/037.html21
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/038.html21
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/039.html21
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/040.html10
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/041.html13
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/042.html16
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/043.html16
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/044.html17
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/045.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/046.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047-1.html7
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047.html11
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/049.html18
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/050.html25
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/051.html22
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/contentType.window.js28
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-01.xhtml19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-02.html27
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/during-readystatechange.window.js24
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/empty.html1
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_001.html14
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_002.html22
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_003.html23
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_004.html22
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.html25
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.js3
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_006.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_007.html17
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_008.html18
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_009.html21
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_010.html23
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed-iframe.html9
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed.html29
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import-iframe.html7
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.html27
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.mjs4
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-iframe.html7
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed-iframe.html5
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.html28
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.mjs5
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-iframe.html5
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.html26
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.mjs4
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed-iframe.html14
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed.html30
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise-iframe.html10
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise.html29
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import-iframe.html5
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.html27
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.mjs4
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise-iframe.html11
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise.html24
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module.html22
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-1.html2
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-2.html7
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-external.js1
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/original-id.json1
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_001.html10
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_002.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_003.html10
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_004.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_005.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_006.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_007.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_008.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_009.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_010.html22
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_011.html22
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_012.html22
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_013.html24
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/write-active-document.html35
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-01.xhtml19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-02.html27
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-03.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/original-id.json1
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-encoding.html35
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-style-attribute.html54
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-base-pushstate.html15
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-base.html10
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-moretests.html42
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-pushstate.html14
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url.html9
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe.html77
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Element-setHTMLUnsafe-04.html25
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-base-pushstate.html10
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-base.html6
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-pushstate.html9
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe.html4
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe.js3
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-url-tests.js36
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/setHTMLUnsafe-CEReactions.html71
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/setHTMLUnsafe-xml.html27
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/setHTMLUnsafe.html55
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/002.html12
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/004.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/006.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011-1.html5
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011.html9
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012-1.html7
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012.html9
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013-1.html7
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013.html9
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014-1.html9
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014.html9
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015-1.html17
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015.html14
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016-1.html41
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016.html15
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-immediate.window.js119
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-header.window.js69
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-meta.window.js69
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-while-navigating.window.js179
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort.sub.window.js104
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.js31
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/active.window.js98
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-origin.sub.window.js117
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-xml.window.js26
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-ignore-opens-during-unload.window.js18
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-same-origin-domain.sub.window.js14
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-synchronous-script.window.js19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-xml.window.js20
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/beforeunload.window.js18
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/crbug-583445-regression.window.js127
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/custom-element.window.js39
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document-open-cancels-javascript-url-navigation.html17
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-01.xhtml19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-02.html27
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03-frame.html10
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/encoding.window.js12
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/event-listeners.window.js308
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/form-control-state.html61
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history-state.window.js29
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history.window.js29
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/ignore-opens-during-unload.window.js60
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/location-set-and-document-open.html31
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-events.window.js22
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-observer.window.js19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/no-new-global.window.js57
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-basic.html26
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-same-origin-domain.sub.html24
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/quirks.window.js74
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/readiness.window.js25
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/reload.window.js71
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/remove-initial-about-blankness.window.js65
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-async-frame.html9
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-frame.html7
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-custom-element-with-domain-frame.sub.html13
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-frame.html4
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-with-domain-frame.sub.html5
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-domain-frame.sub.xhtml11
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-synchronous-script-frame.xhtml11
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/document-open-side-effects.js8
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/dummy.html2
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/encoding-frame.html3
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/global-variables-frame.html4
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/history-frame.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/http-refresh.py5
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/meta-refresh.py3
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/page-with-frame.html1
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/set-document-domain.html4
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/slow-png.py8
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-incumbent-frame.html4
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-timer-frame.html3
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-frame.html9
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/tasks.window.js106
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext-subframe.txt1
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext.window.js23
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument.window.js20
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/unload.window.js19
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document-sync-call.window.js13
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document.window.js18
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-fragment.window.js26
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url.window.js93
-rw-r--r--testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-cross-realm-callback-report-exception.html28
-rw-r--r--testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-exceptions.any.js15
-rw-r--r--testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.any.js39
-rw-r--r--testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.window.js45
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/event-loops/fully_active_document.window.js29
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_raf.html57
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_script.html55
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/common.js20
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/iframe.html7
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/page-with-frame.html1
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering-manual.html64
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering.html85
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/body-onload.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes-form-owner.html48
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes.html167
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-settings-objects.html69
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-symbol-unscopables.html71
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/contextmenu-event-manual.htm21
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-all-global-events.html88
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-body-window.html18
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-frameset-window.html30
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-windowless-body.html71
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-handleEvent-ignored.html38
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-javascript.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onmove-01.tentative.html17
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onmove-02.tentative.html24
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onresize.html38
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-errorevent.html57
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-event.html29
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-errorevent.html60
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-event.html30
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-errorevent.html64
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-event.html36
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/frameset-frame.html4
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/no-op-worker.js1
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/worker-with-syntax-error.js1
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/script-element.html67
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.html78
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.worker.js22
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-runtime-error.html48
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-errorevent.html57
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-event.html30
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/worker.html63
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-runtime-error.worker.js40
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-errorevent.worker.js49
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-event.worker.js22
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-manual.html62
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm.html60
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-removal.window.js76
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-sourcetext.html40
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/event-handler-spec-example.window.js55
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/eventhandler-cancellation.html76
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/inline-event-handler-ordering.html53
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-late.window.js16
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-once.window.js14
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-keeps-position.window.js20
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/messageevent-constructor.https.html114
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler-frame.html56
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler.html11
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/resources/compiled-event-handler-settings-objects-support.html12
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/resources/event-handler-body.js61
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/resources/open-window.html4
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/events/uncompiled_event_handler_with_scripting_disabled.html21
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/addEventListener.html32
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error-data-url.html37
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error.html39
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-runtime-error.html39
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setInterval.html25
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setTimeout.html23
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin.html38
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-data-url.html36
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-attribute.html39
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-body-onerror.html28
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setInterval.html39
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setTimeout.html36
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin-with-hash.html36
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin.html36
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error.html38
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js25
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js.headers2
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-failure.https.any.js11
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-success.any.js9
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js5
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js.headers1
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry-different-function-realm.html112
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry.html101
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-incumbent.html164
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/README.md5
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/current.html4
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/resources/window-to-open.html3
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/function.html3
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/resources/window-to-open.html3
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-entry-incumbent.html15
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-incumbent.html27
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-resolver.html9
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/relevant.html14
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/resources/window-to-open.html3
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/resources/window-to-open.html3
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/window-to-open.html3
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setInterval.html25
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setTimeout.html23
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin.html38
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-data-url.html36
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-attribute.html39
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-body-onerror.html25
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setInterval.html39
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setTimeout.html36
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-window-onerror.html29
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin-with-hash.html36
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin.html36
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error.html38
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setInterval.js8
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setTimeout.js7
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error.js1
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setInterval.js8
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setTimeout.js7
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable.js1
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/allow-crossorigin.html30
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/disallow-crossorigin.html96
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-constructor.html50
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-during-parse.html44
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-attached-in-event.html31
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-iframe.html146
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-onerror.html47
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.dedicatedworker.html11
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.html8
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.serviceworker.https.html12
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.sharedworker.html11
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-resolution-order.html70
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-access-control.py18
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-rejection-events.js961
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-parse-error.html40
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error-throw.html43
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error.html43
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-1.html33
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-2.html33
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-3.html33
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-4.html33
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-5.html25
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/reporterror-cross-realm-method.html30
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/reporterror-in-detached-window-crash.html11
-rw-r--r--testing/web-platform/tests/html/webappapis/scripting/reporterror.any.js49
-rw-r--r--testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js45
-rw-r--r--testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js169
-rw-r--r--testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests.js753
-rw-r--r--testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-cross-realm-method.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-detached-window-crash.html11
-rw-r--r--testing/web-platform/tests/html/webappapis/structured-clone/structured-clone.any.js14
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/clientinformation.window.js8
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/get-navigatorlanguage-manual.html16
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/historical.https.window.js16
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-indexed.html28
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-window-controls-overlay.html41
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator.any.js106
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.https.html53
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.tentative.html8
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-false-manual.html17
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-true.html13
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorlanguage.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/per-global.window.js4
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/plugins-and-mimetypes.html83
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-manual.https.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw-manual.https.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw.https.html25
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment.https.html25
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path-manual.https.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path.https.html25
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-manual.https.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw-manual.https.html20
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw.https.html25
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query.https.html25
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html218
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.tentative.https.html30
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-sw.js3
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-tools.js53
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler.html23
-rw-r--r--testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/secure_context.html18
-rw-r--r--testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/README.md1
-rw-r--r--testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/Worker_Self_Origin.html45
-rw-r--r--testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginSharedWorker.js5
-rw-r--r--testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginWorker.js4
-rw-r--r--testing/web-platform/tests/html/webappapis/timers/clearinterval-from-callback.any.js19
-rw-r--r--testing/web-platform/tests/html/webappapis/timers/cleartimeout-clearinterval.any.js29
-rw-r--r--testing/web-platform/tests/html/webappapis/timers/evil-spec-example.any.js12
-rw-r--r--testing/web-platform/tests/html/webappapis/timers/missing-timeout-setinterval.any.js34
-rw-r--r--testing/web-platform/tests/html/webappapis/timers/negative-setinterval.any.js12
-rw-r--r--testing/web-platform/tests/html/webappapis/timers/negative-settimeout.any.js3
-rw-r--r--testing/web-platform/tests/html/webappapis/timers/setinterval-cross-realm-callback-report-exception.html32
-rw-r--r--testing/web-platform/tests/html/webappapis/timers/settimeout-cross-realm-callback-report-exception.html28
-rw-r--r--testing/web-platform/tests/html/webappapis/timers/type-long-setinterval.any.js8
-rw-r--r--testing/web-platform/tests/html/webappapis/timers/type-long-settimeout.any.js3
-rw-r--r--testing/web-platform/tests/html/webappapis/update-rendering/child-document-raf-order.html118
-rw-r--r--testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html24
-rw-r--r--testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html24
-rw-r--r--testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/confirm.html11
-rw-r--r--testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/prompt.html11
-rw-r--r--testing/web-platform/tests/html/webappapis/user-prompts/newline-normalization-manual.html21
-rw-r--r--testing/web-platform/tests/html/webappapis/user-prompts/print-during-beforeunload.html29
-rw-r--r--testing/web-platform/tests/html/webappapis/user-prompts/print-during-unload.html29
-rw-r--r--testing/web-platform/tests/html/webappapis/user-prompts/print-in-detached-frame.html19
-rw-r--r--testing/web-platform/tests/html/webappapis/user-prompts/print-manual.html93
-rw-r--r--testing/web-platform/tests/html/webappapis/user-prompts/resources/destination.html8
-rw-r--r--testing/web-platform/tests/html/webappapis/user-prompts/resources/print-during-event.sub.html17
438 files changed, 14384 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/callback-cross-realm-report-exception.html b/testing/web-platform/tests/html/webappapis/animation-frames/callback-cross-realm-report-exception.html
new file mode 100644
index 0000000000..1b8aa41a6d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/animation-frames/callback-cross-realm-report-exception.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>requestAnimationFrame() 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></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(() => {
+ frames[0].requestAnimationFrame(new frames[1].Function(`throw new parent.frames[2].Error("PASS");`));
+ document.querySelector("iframe").height = 200;
+
+ t.step_timeout(() => {
+ assert_array_equals(onerrorCalls, ["frame1"]);
+ t.done();
+ }, 100);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/callback-exception.html b/testing/web-platform/tests/html/webappapis/animation-frames/callback-exception.html
new file mode 100644
index 0000000000..3867f0c41d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/animation-frames/callback-exception.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html>
+ <head>
+ <title>requestAnimationFrame callback exception reported to error handler</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="help" href="https://w3c.github.io/web-performance/specs/RequestAnimationFrame/Overview.html#dom-windowanimationtiming-requestanimationframe"/>
+ </head>
+ <body>
+ <div id="log"></div>
+ <script>
+ var custom_exception = 'requestAnimationFrameException';
+ setup({allow_uncaught_exception : true});
+ async_test(function (t) {
+ addEventListener("error",function(e) {
+ t.step(function() {
+ assert_equals(e.error.message, custom_exception);
+ t.done();
+ })
+ });
+ window.requestAnimationFrame(function () {
+ throw new Error(custom_exception);
+ });
+ }, "requestAnimationFrame callback exceptions are reported to error handler");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/callback-handle.html b/testing/web-platform/tests/html/webappapis/animation-frames/callback-handle.html
new file mode 100644
index 0000000000..f1b8830031
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/animation-frames/callback-handle.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>AnimationTiming Test: FrameRequestCallback - valid callback handle</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#animation-frames">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+
+ test(() => {
+ let requestId = window.requestAnimationFrame(() => {});
+ assert_greater_than(requestId, 0, "callback handle is a integer greater than zero");
+ }, "Check window.requestAnimationFrame can return a valid callback handle");
+
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/callback-invoked.html b/testing/web-platform/tests/html/webappapis/animation-frames/callback-invoked.html
new file mode 100644
index 0000000000..ca34e455a2
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/animation-frames/callback-invoked.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+ <head>
+ <title>requestAnimationFrame must be triggered once</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="help" href="https://w3c.github.io/web-performance/specs/RequestAnimationFrame/Overview.html#dom-windowanimationtiming-requestanimationframe"/>
+ </head>
+ <body>
+ <div id="log"></div>
+ <script>
+ async_test(function (t) {
+ assert_false(document.hidden, "document.hidden must exist and be false to run this test properly");
+ window.requestAnimationFrame(t.step_func_done());
+ }, "requestAnimationFrame callback is invoked at least once before the timeout");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/callback-multicalls.html b/testing/web-platform/tests/html/webappapis/animation-frames/callback-multicalls.html
new file mode 100644
index 0000000000..38f34171ea
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/animation-frames/callback-multicalls.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>AnimationTiming Test: multiple calls to requestAnimationFrame with the same callback</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+
+ async_test(function(t) {
+ var counter = 0;
+ window.requestAnimationFrame(callback);
+
+ function callback() {
+ ++counter;
+ if (counter == 2) {
+ t.done();
+ } else {
+ window.requestAnimationFrame(callback);
+ }
+ };
+
+ }, "Check that multiple calls to requestAnimationFrame with the same callback will result in multiple entries being in the list with that same callback.");
+
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/callback-timestamp.html b/testing/web-platform/tests/html/webappapis/animation-frames/callback-timestamp.html
new file mode 100644
index 0000000000..8e61db61b8
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/animation-frames/callback-timestamp.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>AnimationTiming Test: FrameRequestCallback - timestamp argument</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#animation-frames">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+
+ async_test(t => {
+ requestAnimationFrame(t.step_func_done(time => {
+ assert_equals(typeof time, "number", "callback contains a number argument");
+ }))
+ }, "Check FrameRequestCallback has a DOMHighResTimeStamp argument");
+
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/cancel-handle-manual.html b/testing/web-platform/tests/html/webappapis/animation-frames/cancel-handle-manual.html
new file mode 100644
index 0000000000..0328272522
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/animation-frames/cancel-handle-manual.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>AnimationTiming Test: cancelAnimationFrame used to cancel request callback</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#animation-frames">
+
+<style>
+ #animated {
+ background: blue;
+ color: white;
+ height: 100px;
+ width: 100px;
+ position: absolute;
+ }
+</style>
+
+<p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which moves from left to right repeatly, when click the 'stop' button,
+ the square stops.
+</p>
+<button onclick="stop()">stop</button>
+<div id="animated">Filler Text</div>
+
+<script>
+
+let requestId = 0;
+let requestAnimation = window.requestAnimationFrame;
+let cancelAnimation = window.cancelAnimationFrame;
+
+function animate(time) {
+ let div = document.getElementById("animated");
+ div.style.left = (time - animationStartTime) % 2000 / 4 + "px";
+ requestId = requestAnimation(animate);
+}
+
+function start() {
+ animationStartTime = window.performance.now();
+ requestId = requestAnimation(animate);
+}
+
+function stop() {
+ if (requestId) {
+ cancelAnimation(requestId);
+ requestId = 0;
+ }
+}
+
+start();
+
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/cancel-invoked.html b/testing/web-platform/tests/html/webappapis/animation-frames/cancel-invoked.html
new file mode 100644
index 0000000000..d075c0fdac
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/animation-frames/cancel-invoked.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+ <head>
+ <title>cancelAnimationFrame does nothing</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="help" href="https://w3c.github.io/web-performance/specs/RequestAnimationFrame/Overview.html#dom-windowanimationtiming-cancelanimationframe"/>
+ </head>
+ <body>
+ <div id="log"></div>
+ <script>
+ test(function (t) {
+ window.cancelAnimationFrame(42);
+ assert_true(true);
+ }, "cancelAnimationFrame does nothing if there is no callback with the given handle");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/cancel-pending.html b/testing/web-platform/tests/html/webappapis/animation-frames/cancel-pending.html
new file mode 100644
index 0000000000..9c9aff511d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/animation-frames/cancel-pending.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<title>cancelAnimationFrame cancels a pending animation frame callback</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#run-the-animation-frame-callbacks">
+<div id="log"></div>
+<script>
+async_test(t => {
+ let didCall = false;
+
+ function callbackOne() {
+ cancelAnimationFrame(twoHandle);
+ requestAnimationFrame(t.step_func(() => {
+ assert_false(didCall, 'Should NOT have called the second callback');
+ t.done();
+ }));
+ }
+
+ function callbackTwo() {
+ didCall = true;
+ }
+
+ requestAnimationFrame(callbackOne);
+ const twoHandle = requestAnimationFrame(callbackTwo);
+}, 'cancelAnimationFrame cancels a pending animation frame callback');
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/same-dispatch-time.html b/testing/web-platform/tests/html/webappapis/animation-frames/same-dispatch-time.html
new file mode 100644
index 0000000000..28e94f1e33
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/animation-frames/same-dispatch-time.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+ <head>
+ <title>requestAnimationFrame in queue get the same timestamp</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="help" href="http://w3c.github.io/animation-timing/#dfn-invoke-callbacks-algorithm"/>
+ </head>
+ <body>
+ <div id="log"></div>
+ <script>
+ async_test(function (t) {
+ var a = 0, b = 0;
+
+ /* REASONING:
+ * These two methods that will be called with a timestamp. Because
+ * they execute right after eachother, they're added to the same
+ * queue and SHOULD be timestamped with the same value.
+ */
+ requestAnimationFrame(t.step_func(function() { a = arguments[0]; }));
+ requestAnimationFrame(t.step_func(function() {
+ b = arguments[0];
+ assert_not_equals(a, 0);
+ assert_not_equals(b, 0);
+ assert_equals(a, b);
+ t.done();
+ }));
+ }, "requestAnimationFrame will timestamp events in the same queue with the same time");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/atob/base64.any.js b/testing/web-platform/tests/html/webappapis/atob/base64.any.js
new file mode 100644
index 0000000000..7f433f4d8a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/atob/base64.any.js
@@ -0,0 +1,163 @@
+/**
+ * btoa() as defined by the HTML5 spec, which mostly just references RFC4648.
+ */
+function mybtoa(s) {
+ // String conversion as required by WebIDL.
+ s = String(s);
+
+ // "The btoa() method must throw an INVALID_CHARACTER_ERR exception if the
+ // method's first argument contains any character whose code point is
+ // greater than U+00FF."
+ for (var i = 0; i < s.length; i++) {
+ if (s.charCodeAt(i) > 255) {
+ return "INVALID_CHARACTER_ERR";
+ }
+ }
+
+ var out = "";
+ for (var i = 0; i < s.length; i += 3) {
+ var groupsOfSix = [undefined, undefined, undefined, undefined];
+ groupsOfSix[0] = s.charCodeAt(i) >> 2;
+ groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4;
+ if (s.length > i + 1) {
+ groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4;
+ groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2;
+ }
+ if (s.length > i + 2) {
+ groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6;
+ groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f;
+ }
+ for (var j = 0; j < groupsOfSix.length; j++) {
+ if (typeof groupsOfSix[j] == "undefined") {
+ out += "=";
+ } else {
+ out += btoaLookup(groupsOfSix[j]);
+ }
+ }
+ }
+ return out;
+}
+
+/**
+ * Lookup table for mybtoa(), which converts a six-bit number into the
+ * corresponding ASCII character.
+ */
+function btoaLookup(idx) {
+ if (idx < 26) {
+ return String.fromCharCode(idx + 'A'.charCodeAt(0));
+ }
+ if (idx < 52) {
+ return String.fromCharCode(idx - 26 + 'a'.charCodeAt(0));
+ }
+ if (idx < 62) {
+ return String.fromCharCode(idx - 52 + '0'.charCodeAt(0));
+ }
+ if (idx == 62) {
+ return '+';
+ }
+ if (idx == 63) {
+ return '/';
+ }
+ // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests.
+}
+
+function btoaException(input) {
+ input = String(input);
+ for (var i = 0; i < input.length; i++) {
+ if (input.charCodeAt(i) > 255) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function testBtoa(input) {
+ // "The btoa() method must throw an INVALID_CHARACTER_ERR exception if the
+ // method's first argument contains any character whose code point is
+ // greater than U+00FF."
+ var normalizedInput = String(input);
+ for (var i = 0; i < normalizedInput.length; i++) {
+ if (normalizedInput.charCodeAt(i) > 255) {
+ assert_throws_dom("InvalidCharacterError", function() { btoa(input); },
+ "Code unit " + i + " has value " + normalizedInput.charCodeAt(i) + ", which is greater than 255");
+ return;
+ }
+ }
+ assert_equals(btoa(input), mybtoa(input));
+ assert_equals(atob(btoa(input)), String(input), "atob(btoa(input)) must be the same as String(input)");
+}
+
+var tests = ["עברית", "", "ab", "abc", "abcd", "abcde",
+ // This one is thrown in because IE9 seems to fail atob(btoa()) on it. Or
+ // possibly to fail btoa(). I actually can't tell what's happening here,
+ // but it doesn't hurt.
+ "\xff\xff\xc0",
+ // Is your DOM implementation binary-safe?
+ "\0a", "a\0b",
+ // WebIDL tests.
+ undefined, null, 7, 12, 1.5, true, false, NaN, +Infinity, -Infinity, 0, -0,
+ {toString: function() { return "foo" }},
+];
+for (var i = 0; i < 258; i++) {
+ tests.push(String.fromCharCode(i));
+}
+tests.push(String.fromCharCode(10000));
+tests.push(String.fromCharCode(65534));
+tests.push(String.fromCharCode(65535));
+
+// This is supposed to be U+10000.
+tests.push(String.fromCharCode(0xd800, 0xdc00));
+tests = tests.map(
+ function(elem) {
+ var expected = mybtoa(elem);
+ if (expected === "INVALID_CHARACTER_ERR") {
+ return ["btoa(" + format_value(elem) + ") must raise INVALID_CHARACTER_ERR", elem];
+ }
+ return ["btoa(" + format_value(elem) + ") == " + format_value(mybtoa(elem)), elem];
+ }
+);
+
+var everything = "";
+for (var i = 0; i < 256; i++) {
+ everything += String.fromCharCode(i);
+}
+tests.push(["btoa(first 256 code points concatenated)", everything]);
+
+generate_tests(testBtoa, tests);
+
+promise_test(() => fetch("../../../fetch/data-urls/resources/base64.json").then(res => res.json()).then(runAtobTests), "atob() setup.");
+
+const idlTests = [
+ [undefined, null],
+ [null, [158, 233, 101]],
+ [7, null],
+ [12, [215]],
+ [1.5, null],
+ [true, [182, 187]],
+ [false, null],
+ [NaN, [53, 163]],
+ [+Infinity, [34, 119, 226, 158, 43, 114]],
+ [-Infinity, null],
+ [0, null],
+ [-0, null],
+ [{toString: function() { return "foo" }}, [126, 138]],
+ [{toString: function() { return "abcd" }}, [105, 183, 29]]
+];
+
+function runAtobTests(tests) {
+ const allTests = tests.concat(idlTests);
+ for(let i = 0; i < allTests.length; i++) {
+ const input = allTests[i][0],
+ output = allTests[i][1];
+ test(() => {
+ if(output === null) {
+ assert_throws_dom("InvalidCharacterError", () => globalThis.atob(input));
+ } else {
+ const result = globalThis.atob(input);
+ for(let ii = 0; ii < output.length; ii++) {
+ assert_equals(result.charCodeAt(ii), output[ii]);
+ }
+ }
+ }, "atob(" + format_value(input) + ")");
+ }
+}
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document-close-with-pending-script.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document-close-with-pending-script.html
new file mode 100644
index 0000000000..1584ca5f97
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document-close-with-pending-script.html
@@ -0,0 +1,67 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>document.close called while a script is pending</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+ <script>
+ window.t = async_test();
+ // We want start a document load, create an non-blocking script load inside
+ // it, have the parser complete, then call document.open()/document.close()
+ // after the parser is done but before the non-blocking script load
+ // completes. After we do that, the document should reach the 'complete'
+ // ready state and the iframe's load event should fire.
+ var loadFired = false;
+ var i;
+
+ var finish = t.step_func_done(() => {
+ assert_equals(loadFired, true, "Should have fired a load event");
+ assert_equals(i.contentDocument.readyState, "complete",
+ "Should be fully loaded");
+ });
+
+ var checkForLoad = t.step_func(() => {
+ if (loadFired) {
+ finish();
+ } else {
+ i.addEventListener("load", finish);
+ }
+ });
+
+ window.parserDone = t.step_func(() => {
+ var doc = i.contentDocument;
+ i.onload = () => { loadFired = true; }
+ doc.open();
+ doc.close();
+ // It's not very clearly specced whether the document is
+ // supposed to be fully loaded at this point or not, so allow
+ // that to be the case, or to happen soonish.
+ assert_true(doc.readyState == "interactive" ||
+ doc.readyState == "complete", "Should be almost loaded");
+ if (doc.readyState == "complete") {
+ checkForLoad();
+ } else {
+ doc.addEventListener("readystatechange", checkForLoad);
+ }
+ });
+
+ t.step(() => {
+ i = document.createElement("iframe");
+ i.srcdoc = `
+ <script>
+ parent.t.step(() => {
+ var s = document.createElement("script");
+ s.src = "/common/slow.py";
+ document.documentElement.appendChild(s);
+ // Call into the parent async, so we finish our "end of parse"
+ // work before it runs.
+ document.addEventListener(
+ "DOMContentLoaded",
+ () => parent.t.step_timeout(parent.parserDone, 0));
+ });
+ <\/script>
+ `;
+ document.body.appendChild(i);
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document.close-01.xhtml b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document.close-01.xhtml
new file mode 100644
index 0000000000..164d71d191
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document.close-01.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>document.close in XHTML</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"/>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#closing-the-input-stream"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_throws_dom("INVALID_STATE_ERR", function() {
+ document.close();
+ }, "document.close in XHTML should throw an INVALID_STATE_ERR ");
+}, "document.close in XHTML");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/load-event-after-location-set-during-write.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/load-event-after-location-set-during-write.window.js
new file mode 100644
index 0000000000..d5c8469baf
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/load-event-after-location-set-during-write.window.js
@@ -0,0 +1,19 @@
+// Make sure that the load event for an iframe doesn't fire at the
+// point when a navigation triggered by document.write() starts in it,
+// but rather when that navigation completes.
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ const doc = frame.contentDocument;
+ const url = URL.createObjectURL(new Blob(["PASS"], { type: "text/html"}));
+
+ frame.onload = t.step_func_done(() => {
+ assert_equals(frame.contentDocument.body.textContent, "PASS",
+ "Why is our load event firing before the new document loaded?");
+ });
+
+ doc.open();
+ doc.write(`FAIL<script>location = "${url}"</` + "script>");
+ doc.close();
+}, "Setting location from document.write() call should not trigger load event until that load completes");
+
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/001.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/001.html
new file mode 100644
index 0000000000..3ac6423f4a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/001.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("PASS");
+ assert_equals(document.body.textContent, "PASS");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/002.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/002.html
new file mode 100644
index 0000000000..08975bca7b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/002.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i>Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/003.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/003.html
new file mode 100644
index 0000000000..915e1f6d61
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/003.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<");
+ document.write("i>Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/004.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/004.html
new file mode 100644
index 0000000000..dd01725860
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/004.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i");
+ document.write(">Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.html
new file mode 100644
index 0000000000..4c161c4d47
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i>");
+ document.write("Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.js
new file mode 100644
index 0000000000..ebfd7e2585
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.js
@@ -0,0 +1 @@
+order.push(3); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.html
new file mode 100644
index 0000000000..92bfb44c3a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i id='test'>Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.getAttribute("id"), "test");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.js
new file mode 100644
index 0000000000..ebfd7e2585
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.js
@@ -0,0 +1 @@
+order.push(3); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.html
new file mode 100644
index 0000000000..753316b89c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i ");
+ document.write("id='test'>Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.getAttribute("id"), "test");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.js
new file mode 100644
index 0000000000..31fcf18d49
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.js
@@ -0,0 +1,4 @@
+t.step(function() {
+ order.push(2);
+ document.write("<script>t.step(function() {order.push(3)})</script>");
+ }); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008-1.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008-1.js
new file mode 100644
index 0000000000..ef90c722b7
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008-1.js
@@ -0,0 +1,3 @@
+t.step(function() {
+ order.push(3);
+ }); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.html
new file mode 100644
index 0000000000..4818bc388f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i i");
+ document.write("d='test'>Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.getAttribute("id"), "test");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.js
new file mode 100644
index 0000000000..367597515d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.js
@@ -0,0 +1,4 @@
+t.step(function() {
+ order.push(2);
+ document.write("<script src=\"008-1.js\"></script>");
+ }); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/009.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/009.html
new file mode 100644
index 0000000000..d7b78333b8
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/009.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i id");
+ document.write("='test'>Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.getAttribute("id"), "test");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010-1.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010-1.js
new file mode 100644
index 0000000000..fd815bab77
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010-1.js
@@ -0,0 +1,4 @@
+t.step(function() {
+ order.push(4);
+ assert_equals(document.getElementsByTagName("meta").length, 1);
+ }); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.html
new file mode 100644
index 0000000000..c8b9958258
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i id=");
+ document.write("'test'>Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.getAttribute("id"), "test");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.js
new file mode 100644
index 0000000000..bb328ad55a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.js
@@ -0,0 +1,4 @@
+t.step(function() {
+ order.push(3);
+ assert_equals(document.getElementsByTagName("meta").length, 0);
+ });
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011-1.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011-1.js
new file mode 100644
index 0000000000..944b70d2d0
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011-1.js
@@ -0,0 +1,5 @@
+t.step(function() {
+ order.push(4);
+ document.write("<meta>");
+ assert_equals(document.getElementsByTagName("meta").length, 1);
+ }); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.html
new file mode 100644
index 0000000000..33464429e6
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i id='");
+ document.write("test'>Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.getAttribute("id"), "test");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.js
new file mode 100644
index 0000000000..ce47bcd283
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.js
@@ -0,0 +1,5 @@
+t.step(function() {
+ order.push(3);
+ document.write("<script src='011-1.js'></script" + "><meta>");
+ assert_equals(document.getElementsByTagName("meta").length, 0);
+ }); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.html
new file mode 100644
index 0000000000..c9902a4875
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i id='te");
+ document.write("st'>Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.getAttribute("id"), "test");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.js
new file mode 100644
index 0000000000..7ab4c6b386
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.js
@@ -0,0 +1,5 @@
+t.step(
+function() {
+ order.push(5);
+ assert_equals(document.getElementsByTagName("meta").length, 0);
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.html
new file mode 100644
index 0000000000..7b87d28976
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i id='test");
+ document.write("'>Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.getAttribute("id"), "test");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.js
new file mode 100644
index 0000000000..b5ce5f27da
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.js
@@ -0,0 +1 @@
+document.write('<svg><![CDATA['); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/014.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/014.html
new file mode 100644
index 0000000000..75518a8981
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/014.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i id='test'");
+ document.write(">Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.getAttribute("id"), "test");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/015.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/015.html
new file mode 100644
index 0000000000..3dd79a63ef
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/015.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i id='test'");
+ document.write("class='a'>Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.getAttribute("id"), "test");
+ assert_equals(document.body.firstChild.getAttribute("class"), "a");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/016.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/016.html
new file mode 100644
index 0000000000..4c2f58912a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/016.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<i>Filler Text");
+ document.write("</i><b>Filler Text");
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+ assert_equals(document.body.childNodes[1].localName, "b");
+ assert_equals(document.body.childNodes[1].textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/017.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/017.html
new file mode 100644
index 0000000000..8d1b24b06e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/017.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ var s = "<i id=test>Filler Text</i><b>Filler Text"
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ }
+ assert_equals(document.body.firstChild.localName, "i");
+ assert_equals(document.body.firstChild.getAttribute('id'), "test");
+ assert_equals(document.body.firstChild.textContent, "Filler Text");
+ assert_equals(document.body.childNodes[1].localName, "b");
+ assert_equals(document.body.childNodes[1].textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/018.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/018.html
new file mode 100644
index 0000000000..cf8dddbc54
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/018.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+function() {
+ document.write("<body>");
+ var s = "<!--comment--><i>Filler Text</i>"
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ }
+ assert_equals(document.body.firstChild.nodeType, document.COMMENT_NODE);
+ assert_equals(document.body.firstChild.data, "comment");
+ assert_equals(document.body.childNodes[1].localName, "i");
+ assert_equals(document.body.childNodes[1].textContent, "Filler Text");
+}
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/019.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/019.html
new file mode 100644
index 0000000000..5e988f79ef
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/019.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<i");
+});
+</script>
+>Filler Text</i>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "i");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/020.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/020.html
new file mode 100644
index 0000000000..1d31bbf35d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/020.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><");
+});
+</script>!--comment-->
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].nodeType, document.COMMENT_NODE);
+ assert_equals(document.body.childNodes[0].data, "comment");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/021.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/021.html
new file mode 100644
index 0000000000..500bb19398
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/021.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><sp");
+});
+</script>an>Filler Text</span>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "span");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/022.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/022.html
new file mode 100644
index 0000000000..53ba299012
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/022.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><span>");
+});
+</script>Filler Text</span>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "span");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/023.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/023.html
new file mode 100644
index 0000000000..ca89e0e0bc
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/023.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><span ");
+});
+</script>id=a>Filler Text</span>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "span");
+ assert_equals(document.body.childNodes[0].getAttribute("id"), "a");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/024.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/024.html
new file mode 100644
index 0000000000..2a47d76cb3
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/024.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><span i");
+});
+</script>d=a>Filler Text</span>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "span");
+ assert_equals(document.body.childNodes[0].getAttribute("id"), "a");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/025.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/025.html
new file mode 100644
index 0000000000..31c68cf7df
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/025.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><span id");
+});
+</script>=a>Filler Text</span>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "span");
+ assert_equals(document.body.childNodes[0].getAttribute("id"), "a");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/026.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/026.html
new file mode 100644
index 0000000000..a9bce7743e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/026.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><span id=");
+});
+</script>a>Filler Text</span>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "span");
+ assert_equals(document.body.childNodes[0].getAttribute("id"), "a");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/027.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/027.html
new file mode 100644
index 0000000000..dcfd67c0f7
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/027.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><span id=a");
+});
+</script>>Filler Text</span>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "span");
+ assert_equals(document.body.childNodes[0].getAttribute("id"), "a");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/028.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/028.html
new file mode 100644
index 0000000000..f5b7e9ef2b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/028.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><span id=a>Filler Text<");
+});
+</script>/span><b>Filler Text</b></span>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "span");
+ assert_equals(document.body.childNodes[0].getAttribute("id"), "a");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+ assert_equals(document.body.childNodes[1].localName, "b");
+ assert_equals(document.body.childNodes[1].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/029.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/029.html
new file mode 100644
index 0000000000..f005a72227
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/029.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><span id=a>Filler Text</");
+});
+</script>span><b>Filler Text</b></span>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "span");
+ assert_equals(document.body.childNodes[0].getAttribute("id"), "a");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+ assert_equals(document.body.childNodes[1].localName, "b");
+ assert_equals(document.body.childNodes[1].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/030.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/030.html
new file mode 100644
index 0000000000..cc361d3aaf
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/030.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><span id=a>Filler Text</sp");
+});
+</script>an><b>Filler Text</b></span>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "span");
+ assert_equals(document.body.childNodes[0].getAttribute("id"), "a");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+ assert_equals(document.body.childNodes[1].localName, "b");
+ assert_equals(document.body.childNodes[1].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/031.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/031.html
new file mode 100644
index 0000000000..32c97c5056
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/031.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><span id=a>Filler Text</span");
+});
+</script>><b>Filler Text</b></span>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "span");
+ assert_equals(document.body.childNodes[0].getAttribute("id"), "a");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+ assert_equals(document.body.childNodes[1].localName, "b");
+ assert_equals(document.body.childNodes[1].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/032.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/032.html
new file mode 100644
index 0000000000..1a33408f1b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/032.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ var tag_name_length = 100000;
+ var tag_name = "";
+ for (var i=0; i<tag_name_length; i++) {
+ tag_name += "a";
+ }
+ document.write("<body><" + tag_name + ">Filler Text</" + tag_name + ">");
+});
+</script>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/033.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/033.html
new file mode 100644
index 0000000000..1b8e1c2706
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/033.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+ function() {
+ document.writeln("<i");
+ var s = " b='a'>Filler"
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]+"\n");
+ }
+ document.writeln("</i");
+ document.writeln(">");
+ assert_equals(document.body.childNodes[0].localName, "i");
+ assert_equals(document.body.childNodes[0].getAttribute("b"), "\na\n");
+ assert_equals(document.body.childNodes[0].textContent, "\nF\ni\nl\nl\ne\nr\n");
+ }
+);
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/034.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/034.html
new file mode 100644
index 0000000000..abd481a64d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/034.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ var s = "<svg><![CDATA[Filler Text]]></svg>";
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ }
+});
+</script>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "svg");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/035.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/035.html
new file mode 100644
index 0000000000..a1e7f9ee67
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/035.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ var s = "<svg><!";
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ }
+});
+</script>[CDATA[Filler Text]]></svg>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "svg");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/036.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/036.html
new file mode 100644
index 0000000000..8719e0598d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/036.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ var s = "<svg><![CDATA[Filler Text]";
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ }
+});
+</script>]></svg>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "svg");
+ assert_equals(document.body.childNodes[0].textContent, "Filler Text");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/037.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/037.html
new file mode 100644
index 0000000000..cf0787ce76
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/037.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ var s = "<body><!DOCTYPE html>";
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ }
+});
+</script><script>
+t.step(function() {
+ //Nothing should be inserted into the DOM for the doctype node so
+ //just checking nothing odd happens
+ assert_equals(document.body.childNodes[0].localName, "script");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/038.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/038.html
new file mode 100644
index 0000000000..4ae9d32b23
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/038.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ var s = "<body><";
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ }
+});
+</script>!DOCTYPE html><script>
+t.step(function() {
+ //Nothing should be inserted into the DOM for the doctype node so
+ //just checking nothing odd happens
+ assert_equals(document.body.childNodes[0].localName, "script");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/039.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/039.html
new file mode 100644
index 0000000000..611a01390c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/039.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ var s = "<body><!";
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ }
+});
+</script>DOCTYPE html><script>
+t.step(function() {
+ //Nothing should be inserted into the DOM for the doctype node so
+ //just checking nothing odd happens
+ assert_equals(document.body.childNodes[0].localName, "script");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/040.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/040.html
new file mode 100644
index 0000000000..d76deffa40
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/040.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>document.write entity</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = test(function() {
+ document.write("<body><span>&notin;abc");
+ assert_equals(document.body.childNodes[0].textContent, "\u2209abc");
+});
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/041.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/041.html
new file mode 100644
index 0000000000..592711c94f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/041.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>document.write entity</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = test(function() {
+ var s = "<body><span>&notin;abc";
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ }
+ assert_equals(document.body.childNodes[0].textContent, "\u2209abc");
+});
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/042.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/042.html
new file mode 100644
index 0000000000..e15f1d0c0f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/042.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>document.write entity</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><span>&not");
+});
+</script>in;abc</span>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].textContent, "\u2209abc");
+})
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/043.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/043.html
new file mode 100644
index 0000000000..4058e7a823
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/043.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>document.write entity</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><span>&");
+});
+</script>notabc</span>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].textContent, "\u00ACabc");
+})
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/044.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/044.html
new file mode 100644
index 0000000000..4c9f50273c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/044.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<body><textarea><span>Filler</span></textarea>");
+});
+</script>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "textarea");
+ assert_equals(document.body.childNodes[0].textContent, "<span>Filler</span>");
+})
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/045.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/045.html
new file mode 100644
index 0000000000..987eabf0f4
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/045.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ var s = "<body><textarea><span>Filler</span></textarea>";
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ }
+});
+</script>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "textarea");
+ assert_equals(document.body.childNodes[0].textContent, "<span>Filler</span>");
+})
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/046.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/046.html
new file mode 100644
index 0000000000..e87e9cc825
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/046.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ var s = "<body><textarea>";
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ }
+});
+</script><span>Filler</span></textarea>
+<script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].localName, "textarea");
+ assert_equals(document.body.childNodes[0].textContent, "<span>Filler</span>");
+})
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047-1.html
new file mode 100644
index 0000000000..6a43faec51
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047-1.html
@@ -0,0 +1,7 @@
+<script>
+onload = opener.t.step_func_done(function() {
+ document.write("<body>Filler Text<div id='log'></div>");
+ opener.assert_equals(document.body.textContent, "Filler Text");
+});
+</script>
+<body>FAIL
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047.html
new file mode 100644
index 0000000000..677d3e1786
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var win;
+var t = async_test(() => {
+ win = window.open("047-1.html");
+});
+t.add_cleanup(() => win.close());
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/049.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/049.html
new file mode 100644
index 0000000000..0ec282f2bc
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/049.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>document.write plaintext</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<div id="log"></div><script>
+test(function() {
+ var s = "<table><tr><td>Text</tr><plaintext><tr><td>Filler ";
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ }
+ document.close();
+ assert_equals(document.body.childNodes[2].nodeType, document.ELEMENT_NODE);
+ assert_equals(document.body.childNodes[2].localName, "plaintext");
+ assert_equals(document.body.childNodes[2].textContent, "<tr><td>Filler ");
+ assert_equals(document.body.childNodes[3].nodeType, document.ELEMENT_NODE);
+ assert_equals(document.body.childNodes[3].localName, "table");
+ assert_equals(document.body.childNodes[3].textContent, "Text");
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/050.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/050.html
new file mode 100644
index 0000000000..0a37fa4c5f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/050.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>document.write plaintext</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<div id="log"></div><script>
+var t = async_test();
+
+t.step(function() {
+ document.write("<plaintext>");
+ assert_equals(document.body.childNodes[2].nodeType, document.ELEMENT_NODE);
+ assert_equals(document.body.childNodes[2].localName, "plaintext");
+ var s = "Filler ";
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ assert_equals(document.body.childNodes[2].textContent, s.slice(0,i+1));
+ }
+ document.close();
+});
+
+onload = function() {
+ t.step(function() {
+ assert_equals(document.body.childNodes[2].textContent, "Filler Text\n");
+ });
+ t.done();
+}
+</script>Text
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/051.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/051.html
new file mode 100644
index 0000000000..80ea279dad
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/051.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>document.write \r\n</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script>
+var t = async_test();
+
+t.step(function() {
+ document.write("\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nA");
+})
+
+onload = function() {
+ t.step(function() {
+ const lastNode = document.getElementById('after');
+ assert_equals(lastNode.previousSibling.data, "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nAB");
+ });
+ t.done();
+};
+</script>B<div id=after></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/contentType.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/contentType.window.js
new file mode 100644
index 0000000000..5a91203874
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/contentType.window.js
@@ -0,0 +1,28 @@
+// META: script=/common/media.js
+
+const videoURL = getVideoURI("/images/pattern"),
+ videoMIMEType = getMediaContentType(videoURL);
+
+[
+ [videoURL, videoMIMEType, "video"],
+ ["/images/red.png", "image/png", "image"],
+ ["/common/text-plain.txt", "text/plain", "text"],
+ ["/common/blank.html", "text/html", "HTML"]
+].forEach(val => {
+ async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.src = val[0];
+ frame.onload = t.step_func_done(() => {
+ assert_equals(frame.contentDocument.contentType, val[1]);
+ frame.contentDocument.write("<b>Heya</b>");
+ assert_equals(frame.contentDocument.body.firstChild.localName, "b");
+ assert_equals(frame.contentDocument.body.firstChild.textContent, "Heya");
+ assert_equals(frame.contentDocument.contentType, val[1]);
+
+ // Make sure a load event is fired across browsers
+ // https://github.com/web-platform-tests/wpt/pull/10239
+ frame.contentDocument.close();
+ });
+ }, "document.write(): " + val[2] + " document");
+});
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-01.xhtml b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-01.xhtml
new file mode 100644
index 0000000000..fc21d4e2bf
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-01.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>document.write in XHTML</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"/>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#document.write%28%29"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_throws_dom("INVALID_STATE_ERR", function() {
+ document.write("Failure: document.write actually worked");
+ }, "document.write in XHTML should throw an INVALID_STATE_ERR ");
+}, "document.write in XHTML");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-02.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-02.html
new file mode 100644
index 0000000000..4c25da8b68
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-02.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>document.write and null/undefined</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-document-write%28%29">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#documents-in-the-dom">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ doc = iframe.contentDocument;
+ test(function() {
+ doc.open();
+ doc.write(null);
+ doc.close();
+ assert_equals(doc.documentElement.textContent, "null");
+ }, "document.write(null)");
+ test(function() {
+ doc.open();
+ doc.write(undefined);
+ doc.close();
+ assert_equals(doc.documentElement.textContent, "undefined");
+ }, "document.write(undefined)");
+}, "Calling document.write with null and undefined");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/during-readystatechange.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/during-readystatechange.window.js
new file mode 100644
index 0000000000..49d5051c25
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/during-readystatechange.window.js
@@ -0,0 +1,24 @@
+// This tests whether the insertion point gets reset before or after the readystatechange event.
+// See https://github.com/whatwg/html/pull/6613#discussion_r620171070.
+// Recall that resetting the insertion point means that document.write() performs the document open
+// steps and blows away previous content in the document.
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => { frame.remove(); });
+ frame.src = "../opening-the-input-stream/resources/dummy.html";
+ frame.onload = t.step_func_done(() => {
+ const states = [];
+ frame.contentDocument.onreadystatechange = t.step_func(() => {
+ if (frame.contentDocument.readyState === "interactive") {
+ assert_not_equals(frame.contentDocument.textContent, "", "Precondition check: dummy document is not empty");
+
+ frame.contentDocument.write("Some text");
+
+ // If the insertion point is reset before the readystatechange handler, then the
+ // document.write() call above will blow away the text originally in dummy.html, leaving only what we wrote.
+ assert_equals(frame.contentDocument.textContent, "Some text");
+ }
+ });
+ });
+}, "document.write() during readystatechange to interactive");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/empty.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/empty.html
new file mode 100644
index 0000000000..0dc101b533
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/empty.html
@@ -0,0 +1 @@
+<html><body></body></html>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_001.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_001.html
new file mode 100644
index 0000000000..8b54560c6c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_001.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>document.write into iframe</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<iframe id="test"></iframe>
+<script>
+test(
+function() {
+ var iframe = document.getElementById("test");
+ iframe.contentDocument.write("Filler Text");
+ iframe.contentDocument.close();
+ assert_equals(iframe.contentDocument.body.textContent, "Filler Text");
+});
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_002.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_002.html
new file mode 100644
index 0000000000..f77819adb6
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_002.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>document.write into iframe</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<iframe id="test"></iframe>
+<script>
+test(
+function() {
+ var iframe = document.getElementById("test");
+ var s = "<i id='a'>Filler Text</i><b id=b>Filler Text</b>"
+ for (var i=0; i<s.length; i++) {
+ iframe.contentDocument.write(s[i]);
+ }
+ iframe.contentDocument.close();
+ assert_equals(iframe.contentDocument.body.childNodes[0].textContent, "Filler Text");
+ assert_equals(iframe.contentDocument.body.childNodes[0].localName, "i");
+ assert_equals(iframe.contentDocument.body.childNodes[0].getAttribute('id'), "a");
+ assert_equals(iframe.contentDocument.body.childNodes[1].textContent, "Filler Text");
+ assert_equals(iframe.contentDocument.body.childNodes[1].localName, "b");
+ assert_equals(iframe.contentDocument.body.childNodes[1].getAttribute('id'), "b");
+});
+</script>
+<div id="log"></div> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_003.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_003.html
new file mode 100644
index 0000000000..9865874da4
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_003.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<title>document.write script into iframe</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<iframe id="test"></iframe>
+<script>
+test(
+function() {
+ var iframe = document.getElementById("test");
+ var s = "<script>document.write(\"<i id='a'>Filler Text</i>\")</script" + "><b id=b>Filler Text</b>"
+ for (var i=0; i<s.length; i++) {
+ iframe.contentDocument.write(s[i]);
+ }
+ iframe.contentDocument.close();
+ //Note: <script> ends up in <head>
+ assert_equals(iframe.contentDocument.body.childNodes[0].textContent, "Filler Text");
+ assert_equals(iframe.contentDocument.body.childNodes[0].localName, "i");
+ assert_equals(iframe.contentDocument.body.childNodes[0].getAttribute('id'), "a");
+ assert_equals(iframe.contentDocument.body.childNodes[1].textContent, "Filler Text");
+ assert_equals(iframe.contentDocument.body.childNodes[1].localName, "b");
+ assert_equals(iframe.contentDocument.body.childNodes[1].getAttribute('id'), "b");
+});
+</script>
+<div id="log"></div> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_004.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_004.html
new file mode 100644
index 0000000000..a4d7b1ddad
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_004.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>document.write script into iframe write back into parent</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<iframe id="test"></iframe>
+<script>
+var t = async_test();
+var iframe = document.getElementById("test");
+var order = [];
+t.step(function() {
+ order.push(1);
+ var s = "<script>parent.order.push(2); parent.document.write('<script>order.push(3);</script'+'>'); parent.order.push(4)</script" + ">";
+ for (var i=0; i<s.length; i++) {
+ iframe.contentDocument.write(s[i]);
+ }
+ iframe.contentDocument.close();
+ order.push(5);
+ assert_array_equals(order, [1,2,3,4,5])
+}
+);
+t.done();
+</script>
+<div id="log"></div> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.html
new file mode 100644
index 0000000000..7bc3ed6c29
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>document.write external script into iframe write back into parent</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<iframe id="test"></iframe>
+<script>
+var t = async_test();
+var iframe = document.getElementById("test");
+var order = [];
+t.step(function() {
+ order.push(1);
+ var s = "<script src='iframe_005.js'></script" + ">";
+ iframe.contentDocument.write(s);
+ iframe.contentDocument.close();
+ order.push(2);
+ assert_array_equals(order, [1,2])
+}
+);
+addEventListener("load", function() {
+ t.step(function() {
+ assert_array_equals(order, [1,2,3,4,5])
+ });
+ t.done();
+}, false);
+</script>
+<div id="log"></div> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.js
new file mode 100644
index 0000000000..bf038f7004
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.js
@@ -0,0 +1,3 @@
+parent.order.push(3);
+document.write("<script>parent.order.push(4)</script>");
+parent.order.push(5); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_006.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_006.html
new file mode 100644
index 0000000000..d080ee3673
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_006.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>document.write external script into iframe write back into parent</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<iframe id="test"></iframe>
+<script>
+var t = async_test();
+var iframe = document.getElementById("test");
+var order = [];
+t.step(function() {
+ order.push(1);
+ var s = "<script>parent.order.push(2); parent.document.write('<script>order.push(3); iframe.contentDocument.write(\"<script>parent.order.push(4)</script\"+\">\");order.push(5);</script' + '>'); parent.order.push(6)</script"+">";
+ iframe.contentDocument.write(s);
+ iframe.contentDocument.close();
+ order.push(7);
+ assert_array_equals(order, [1,2,3,4,5,6,7]);
+});
+t.done();
+</script>
+<div id="log"></div> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_007.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_007.html
new file mode 100644
index 0000000000..c00aa7062d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_007.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<title>document.write comment into iframe</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<iframe id="test"></iframe>
+<script>
+test(function() {
+ var iframe = document.getElementById("test");
+ var s = "<!--Filler-->";
+ for (var i=0; i<s.length; i++) {
+ iframe.contentDocument.write(s);
+ }
+ iframe.contentDocument.close();
+ assert_equals(iframe.contentDocument.childNodes[0].nodeType, document.COMMENT_NODE);
+ assert_equals(iframe.contentDocument.childNodes[0].data, "Filler");
+});
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_008.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_008.html
new file mode 100644
index 0000000000..c814958d19
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_008.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>document.write plaintext into iframe</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<iframe id="test"></iframe>
+<script>
+test(function() {
+ var iframe = document.getElementById("test");
+ var s = "<plaintext><span>Filler Text";
+ for (var i=0; i<s.length; i++) {
+ iframe.contentDocument.write(s[i]);
+ }
+ iframe.contentDocument.close();
+ assert_equals(iframe.contentDocument.body.childNodes[0].nodeType, document.ELEMENT_NODE);
+ assert_equals(iframe.contentDocument.body.childNodes[0].localName, "plaintext");
+ assert_equals(iframe.contentDocument.body.childNodes[0].textContent, "<span>Filler Text");
+});
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_009.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_009.html
new file mode 100644
index 0000000000..8b271c7a03
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_009.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>document.write plaintext into iframe</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<iframe id="test"></iframe>
+<script>
+test(function() {
+ var iframe = document.getElementById("test");
+ var s = "<table><tr><td>Text</tr><plaintext><tr><td>Filler ";
+ for (var i=0; i<s.length; i++) {
+ iframe.contentDocument.write(s[i]);
+ }
+ iframe.contentDocument.close();
+ assert_equals(iframe.contentDocument.body.childNodes[0].nodeType, document.ELEMENT_NODE);
+ assert_equals(iframe.contentDocument.body.childNodes[0].localName, "plaintext");
+ assert_equals(iframe.contentDocument.body.childNodes[0].textContent, "<tr><td>Filler ");
+ assert_equals(iframe.contentDocument.body.childNodes[1].nodeType, document.ELEMENT_NODE);
+ assert_equals(iframe.contentDocument.body.childNodes[1].localName, "table");
+ assert_equals(iframe.contentDocument.body.childNodes[1].textContent, "Text");
+});
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_010.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_010.html
new file mode 100644
index 0000000000..8dc21a013a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_010.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<title>document.write plaintext</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<iframe id="test"></iframe>
+<script>
+var t = async_test();
+var iframe = document.getElementById("test");
+
+function check_dom() {
+ assert_equals(iframe.contentDocument.body.childNodes[0].localName, "plaintext")
+ assert_equals(iframe.contentDocument.body.childNodes[0].textContent, "Filler ")
+ assert_equals(iframe.contentDocument.body.childNodes[1].localName, "table")
+}
+
+t.step(function() {
+ var s = "<script>document.write('<table><plaintext>Filler '); document.close(); top.t.step(function() {top.check_dom()})</script" + ">";
+ for (var i=0; i<s.length; i++) {
+ iframe.contentDocument.write(s[i]);
+ }
+ t.done();
+});
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed-iframe.html
new file mode 100644
index 0000000000..f97f597238
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed-iframe.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<script type=module>
+window.parent.document.test.step_timeout(() => {
+ document.write("document.write body contents\n")
+ document.close();
+ window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone"));
+}, 0);
+</script>
+Initial body contents
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed.html
new file mode 100644
index 0000000000..acdeab59ff
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<title>async document.write in a module</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(t => {
+ // Expose {test} in the iframe for using the step_timeout helper.
+ document.test = t;
+ const iframe = document.createElement("iframe");
+
+ iframe.onerror = t.unreached_func("Error loading iframe");
+
+ let onLoadWasCalled = false;
+ iframe.onload = t.step_func(() => {
+ onLoadWasCalled = true;
+ assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n");
+ // Don't call the event handler another time after document.write.
+ iframe.onload = null;
+ });
+ document.addEventListener("documentWriteDone", t.step_func_done(() => {
+ assert_true(onLoadWasCalled, "onload must be called");
+ assert_equals(iframe.contentDocument.body.textContent, "document.write body contents\n");
+ }));
+
+ iframe.src = "module-delayed-iframe.html";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import-iframe.html
new file mode 100644
index 0000000000..672bb953d6
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import-iframe.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<script type=module>
+(async () => {
+ let module = await import("./module-dynamic-import.mjs");
+})();
+</script>
+Initial body contents
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.html
new file mode 100644
index 0000000000..5939968f05
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<title>document.write in an imported module</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(t => {
+ const iframe = document.createElement("iframe");
+
+ iframe.onerror = t.unreached_func("Error loading iframe");
+
+ let onLoadWasCalled = false;
+ iframe.onload = t.step_func(() => {
+ onLoadWasCalled = true;
+ assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n");
+ // Don't call the event handler another time after document.write.
+ iframe.onload = null;
+ });
+ document.addEventListener("documentWriteDone", t.step_func_done(() => {
+ assert_true(onLoadWasCalled, "onload must be called");
+ assert_equals(iframe.contentDocument.body.textContent, "document.write body contents\n");
+ }));
+
+ iframe.src = "module-dynamic-import-iframe.html";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.mjs b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.mjs
new file mode 100644
index 0000000000..74d2427537
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.mjs
@@ -0,0 +1,4 @@
+document.write("document.write body contents\n");
+document.close();
+
+window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone"));
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-iframe.html
new file mode 100644
index 0000000000..f8646df56b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-iframe.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<script type=module>
+document.write("document.write body contents\n");
+document.close();
+window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone"));
+</script>
+Initial body contents
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed-iframe.html
new file mode 100644
index 0000000000..3ae1464653
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed-iframe.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<script type=module>
+import "./module-static-import-delayed.mjs"
+</script>
+Initial body contents
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.html
new file mode 100644
index 0000000000..a6e003907f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<title>document.write in an imported module</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(t => {
+ // Expose {test} in the iframe for using the step_timeout helper.
+ document.test = t;
+ const iframe = document.createElement("iframe");
+ iframe.onerror = t.unreached_func("Error loading iframe");
+
+ let onLoadWasCalled = false;
+ iframe.onload = t.step_func(() => {
+ onLoadWasCalled = true;
+ assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n");
+ // Don't call the event handler another time after document.write.
+ iframe.onload = null;
+ });
+ document.addEventListener("documentWriteDone", t.step_func_done(() => {
+ assert_true(onLoadWasCalled, "onload must be called");
+ assert_equals(iframe.contentDocument.body.textContent, "document.write body contents\n");
+ }));
+
+ iframe.src = "module-static-import-delayed-iframe.html";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.mjs b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.mjs
new file mode 100644
index 0000000000..45478d6f63
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.mjs
@@ -0,0 +1,5 @@
+window.parent.document.test.step_timeout(() => {
+ document.write("document.write body contents\n")
+ document.close();
+ window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone"));
+}, 0);
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-iframe.html
new file mode 100644
index 0000000000..ed4f6d1c6c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-iframe.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<script type=module>
+import "./module-static-import.mjs"
+</script>
+Initial body contents
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.html
new file mode 100644
index 0000000000..3cae88047e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<title>document.write in an imported module</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(t => {
+ const iframe = document.createElement("iframe");
+
+ iframe.onerror = t.unreached_func("Error loading iframe");
+
+ let testEndWasCalled = false;
+ document.addEventListener("documentWriteDone", t.step_func(() => {
+ testEndWasCalled = true;
+ assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n");
+ }));
+ iframe.onload = t.step_func_done(() => {
+ assert_true(testEndWasCalled, "onload must be called");
+ assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n");
+ });
+
+ iframe.src = "module-static-import-iframe.html";
+ document.body.appendChild(iframe);
+});
+</script>
+ß
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.mjs b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.mjs
new file mode 100644
index 0000000000..74d2427537
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.mjs
@@ -0,0 +1,4 @@
+document.write("document.write body contents\n");
+document.close();
+
+window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone"));
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed-iframe.html
new file mode 100644
index 0000000000..5629c47be7
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed-iframe.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<script type=module>
+let delay = new Promise(
+ resolve => window.parent.document.test.step_timeout(resolve, 0));
+
+delay.then(() => {
+ document.write("document.write body contents\n");
+ document.close();
+ window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone"));
+});
+
+await delay;
+</script>
+Initial body contents
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed.html
new file mode 100644
index 0000000000..5fa8216600
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<title>document.write in an imported module</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(t => {
+ // Expose {test} in the iframe for using the step_timeout helper.
+ document.test = t;
+
+ const iframe = document.createElement("iframe");
+
+ iframe.onunhandledrejection = t.unreached_func("Unhandled promise rejection detected");
+ iframe.onerror = t.unreached_func("Error loading iframe");
+
+ let onLoadWasCalled = false;
+ iframe.onload = t.step_func(() => {
+ onLoadWasCalled = true;
+ assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n");
+ iframe.onload = null;
+ });
+ document.addEventListener("documentWriteDone", t.step_func_done(() => {
+ assert_true(onLoadWasCalled, "onload must be called");
+ assert_equals(iframe.contentDocument.body.textContent, "document.write body contents\n");
+ }));
+
+ iframe.src = "module-tla-delayed-iframe.html";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise-iframe.html
new file mode 100644
index 0000000000..3e90fb2ea7
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise-iframe.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<script type=module>
+await new Promise(resolve => {
+ document.write("document.write body contents\n");
+ document.close();
+ window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone"));
+ resolve();
+});
+</script>
+Initial body contents
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise.html
new file mode 100644
index 0000000000..f60aa38e00
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<title>document.write in an imported module</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(t => {
+ // Expose {test} in the iframe for using the step_timeout helper.
+ document.test = t;
+
+ const iframe = document.createElement("iframe");
+ iframe.onerror = t.unreached_func("Error loading iframe");
+
+ let onLoadWasCalled = false;
+ iframe.onload = t.step_func(() => {
+ onLoadWasCalled = true;
+ assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n");
+ // Don't call the event handler another time after document.write.
+ iframe.onload = null;
+ });
+ document.addEventListener("documentWriteDone", t.step_func_done(() => {
+ assert_false(onLoadWasCalled, "onload must not be called yet");
+ assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n");
+ }));
+
+ iframe.src = "module-tla-immediate-promise-iframe.html";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import-iframe.html
new file mode 100644
index 0000000000..ec4a6ed6aa
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import-iframe.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<script type=module>
+await import("./module-tla-import.mjs");
+</script>
+Initial body contents
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.html
new file mode 100644
index 0000000000..20645f4d78
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<title>document.write in an imported module</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(t => {
+ const iframe = document.createElement("iframe");
+ iframe.onerror = t.unreached_func("Error loading iframe");
+
+ let onLoadWasCalled = false;
+
+ iframe.onload = t.step_func(() => {
+ onLoadWasCalled = true;
+ assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n");
+ // Don't call the event handler another time after document.write.
+ iframe.onload = null;
+ });
+ document.addEventListener("documentWriteDone", t.step_func_done(() => {
+ assert_true(onLoadWasCalled, "onload must be called");
+ assert_equals(iframe.contentDocument.body.textContent, "document.write body contents\n");
+ }));
+
+ iframe.src = "module-tla-import-iframe.html";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.mjs b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.mjs
new file mode 100644
index 0000000000..74d2427537
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.mjs
@@ -0,0 +1,4 @@
+document.write("document.write body contents\n");
+document.close();
+
+window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone"));
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise-iframe.html
new file mode 100644
index 0000000000..edc9e80cb3
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise-iframe.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<script type=module>
+await new Promise(resolve => {
+ window.parent.document.test.step_timeout(resolve, 0);
+ document.write("document.write body contents\n");
+ document.close();
+ window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone"));
+});
+</script>
+
+Initial body contents
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise.html
new file mode 100644
index 0000000000..4f1281bcce
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>document.write in an imported module</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(t => {
+ // Expose {test} in the iframe for using the step_timeout helper.
+ document.test = t;
+
+ const iframe = document.createElement("iframe");
+ iframe.onerror = t.unreached_func("Error loading iframe");
+
+ document.addEventListener("documentWriteDone", t.step_func(() => {
+ assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n");
+ }));
+ iframe.onload = t.step_func_done(() => {
+ assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n");
+ });
+
+ iframe.src = "module-tla-promise-iframe.html";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module.html
new file mode 100644
index 0000000000..7e970d3fd9
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>document.write in a module</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(t => {
+ const iframe = document.createElement("iframe");
+
+ iframe.onerror = t.unreached_func("Error loading iframe");
+ document.addEventListener("documentWriteDone", t.step_func(() => {
+ assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n");
+ }));
+ iframe.onload = t.step_func_done(() => {
+ assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n");
+ });
+
+ iframe.src = "module-iframe.html";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-1.html
new file mode 100644
index 0000000000..c7a7a1db4e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<body>You should see the word "worked" below<br><script>document.write("\u003cscript>document.write(\"\\u003cscript src='nested-document-write-external.js'>\\u003c/script>r\"); document.write(\"k\");\u003c/script>e"); document.write("d");</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-2.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-2.html
new file mode 100644
index 0000000000..60b8eae1ef
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-2.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<body>
+You should see the word "worked" in the frame below.<br>
+<iframe></iframe>
+<script>
+var doc = document.getElementsByTagName("iframe")[0].contentDocument;
+doc.open(); doc.write("\u003cscript>document.write(\"\\u003cscript src='nested-document-write-external.js'>\\u003c/script>r\"); document.write(\"k\");\u003c/script>e"); doc.write("d"); doc.close();</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-external.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-external.js
new file mode 100644
index 0000000000..bf91daf986
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-external.js
@@ -0,0 +1 @@
+document.write("w"); document.write("o");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/original-id.json b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/original-id.json
new file mode 100644
index 0000000000..08bd4d0d4e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/original-id.json
@@ -0,0 +1 @@
+{"original_id":"document.write()"} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_001.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_001.html
new file mode 100644
index 0000000000..43c7adb4d4
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_001.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>document.write script</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<script>t.done();<"+"/script>");
+});
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_002.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_002.html
new file mode 100644
index 0000000000..3879d8489f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_002.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>document.write script executed synchronously</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+var order = [];
+t.step(function() {
+ document.write("<script>t.step(function() {order.push(1);});<"+"/script>");
+ order.push(2);
+});
+</script>
+<script>
+t.step(function() {
+ order.push(3);
+ assert_array_equals(order, [1,2,3]);
+})
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_003.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_003.html
new file mode 100644
index 0000000000..e669252f75
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_003.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>document.write script writing a further script</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ document.write("<script>document.write('<script>t.done()</script'+'>')<"+"/script>");
+});
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_004.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_004.html
new file mode 100644
index 0000000000..15fda325b1
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_004.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>document.write script writing script; order of execution</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+var order = [];
+t.step(function() {
+ order.push(1);
+ document.write("<script>order.push(2); document.write('<script>order.push(3);</script'+'>'); order.push(4);<"+"/script>");
+ order.push(5);
+});
+</script>
+<script>
+t.step(function() {
+ assert_array_equals(order, [1,2,3,4,5]);
+});
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_005.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_005.html
new file mode 100644
index 0000000000..b99196c7d0
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_005.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>document.write external script</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+var order = [];
+t.step(function() {
+ order.push(1);
+ document.write("<script src='005.js'><"+"/script>");
+ order.push(2);
+});
+</script>
+<script>
+order.push(4);
+t.step(function() {
+ assert_array_equals(order, [1,2,3,4]);
+});
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_006.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_006.html
new file mode 100644
index 0000000000..c8dd9a5f9a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_006.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>document.write external script followed by internal script</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+var order = [];
+t.step(function() {
+ order.push(1);
+ document.write("<script src='006.js'><"+"/script><script>t.step(function(){order.push(4)})</script"+">");
+ order.push(2);
+});
+</script>
+<script>
+t.step(function() {
+ order.push(5);
+ assert_array_equals(order, [1,2,3,4,5]);
+});
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_007.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_007.html
new file mode 100644
index 0000000000..fbbe5b2f86
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_007.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>document.write external script that document.writes inline script</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+var order = [];
+t.step(function() {
+ order.push(1);
+});
+</script>
+<script src="007.js"></script>
+<script>
+t.step(function() {
+ order.push(4);
+ assert_array_equals(order, [1,2,3,4]);
+});
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_008.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_008.html
new file mode 100644
index 0000000000..c5a44dc700
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_008.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>document.write external script that document.writes external script</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+var order = [];
+t.step(function() {
+ order.push(1);
+});
+</script>
+<script src="008.js"></script>
+<script>
+t.step(function() {
+ order.push(4);
+ assert_array_equals(order, [1,2,3,4]);
+});
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_009.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_009.html
new file mode 100644
index 0000000000..d12d934ea0
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_009.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>document.write script that document.writes script</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+var order = [];
+t.step(function() {
+ order.push(1);
+ document.write("<script>order.push(2); document.write('<script>order.push(3); document.write(\"<script>order.push(4);</script\"+\">\"); order.push(5);</script' + '>'); order.push(6);</script" + ">");
+ order.push(7);
+});
+</script>
+<script>
+t.step(function() {
+ assert_array_equals(order, [1,2,3,4,5,6,7]);
+});
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_010.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_010.html
new file mode 100644
index 0000000000..93728d6f27
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_010.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>document.write external script tokenizer order</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+var order = [];
+t.step(function() {
+ order.push(1);
+ document.write("<script src='010.js'></script" + "><meta><script src='010-1.js'></script" + ">");
+ order.push(2);
+ assert_equals(document.getElementsByTagName("meta").length, 0);
+});
+</script>
+<script>
+t.step(function() {
+ order.push(5);
+ assert_equals(document.getElementsByTagName("meta").length, 1);
+ assert_array_equals(order, [1,2,3,4,5]);
+});
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_011.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_011.html
new file mode 100644
index 0000000000..2bbcaf976e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_011.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>document.write external script that document.writes external script</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+var order = [];
+t.step(function() {
+ order.push(1);
+ document.write("<script src='011.js'></script" + "><meta>");
+ order.push(2);
+ assert_equals(document.getElementsByTagName("meta").length, 0);
+});
+</script>
+<script>
+t.step(function() {
+ order.push(5);
+ assert_equals(document.getElementsByTagName("meta").length, 3, "Number of meta elements at end");
+ assert_array_equals(order, [1,2,3,4,5]);
+});
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_012.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_012.html
new file mode 100644
index 0000000000..57755f4c94
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_012.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>document.write external script tokenizer order</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+var order = [];
+t.step(function() {
+ order.push(1);
+ document.write("<script>order.push(2); document.write('<script src=\"012.js\"></script' + '><meta>'); order.push(3); t.step(function() {assert_equals(document.getElementsByTagName('meta').length, 0)});</script" + "><meta>");
+ order.push(4);
+ assert_equals(document.getElementsByTagName("meta").length, 0);
+});
+</script>
+<script>
+t.step(function() {
+ order.push(6);
+ assert_equals(document.getElementsByTagName("meta").length, 2);
+ assert_array_equals(order, [1,2,3,4,5,6]);
+});
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_013.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_013.html
new file mode 100644
index 0000000000..0e71e5eb0c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_013.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>document.write</title>
+<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test();
+t.step(function() {
+ var s = "<script src='013.js'><" + "/script></svg>]]><path></svg>";
+ for (var i=0; i<s.length; i++) {
+ document.write(s[i]);
+ }
+});
+</script><script>
+t.step(function() {
+ assert_equals(document.body.childNodes[0].nodeType, document.ELEMENT_NODE);
+ assert_equals(document.body.childNodes[0].localName, "svg");
+ assert_equals(document.body.childNodes[0].childNodes[0].nodeType, document.TEXT_NODE);
+ assert_equals(document.body.childNodes[0].childNodes[0].data, "</svg>");
+ assert_equals(document.body.childNodes[0].childNodes[1].nodeType, document.ELEMENT_NODE);
+ assert_equals(document.body.childNodes[0].childNodes[1].localName, "path");
+}
+);
+t.done();
+</script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/write-active-document.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/write-active-document.html
new file mode 100644
index 0000000000..6faffd81de
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/write-active-document.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<title>document.write only writes to active documents</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body><div id="log"></div></body>
+<script>
+ async_test(function(t) {
+ var child = document.createElement("iframe");
+ child.src = "empty.html?1";
+ child.onload = t.step_func(function() {
+ var child1 = child.contentDocument;
+ var link = child1.createElement("a");
+ link.href = "data:text/html,Clicked.";
+ link.innerText = "Link.";
+ child1.body.appendChild(link);
+ var grandchild = child1.createElement("iframe");
+ grandchild.src = "empty.html?2";
+ grandchild.onload = t.step_func(function() {
+ var grandchild1 = grandchild.contentDocument;
+ child.onload = t.step_func(function() {
+ // This is a write to an inactive document
+ child1.write('WRITE HAPPENED');
+ assert_equals(child1.body.lastChild.tagName, "IFRAME");
+ // This is a write to an active but not fully active document
+ grandchild1.write('WRITE HAPPENED');
+ assert_equals(grandchild1.body.innerHTML, "WRITE HAPPENED");
+ t.done();
+ });
+ link.click();
+ });
+ child1.body.appendChild(grandchild);
+ });
+ document.body.appendChild(child);
+ });
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-01.xhtml b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-01.xhtml
new file mode 100644
index 0000000000..cb5ec3a33a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-01.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>document.writeln in XHTML</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"/>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#document.writeln%28%29"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_throws_dom("INVALID_STATE_ERR", function() {
+ document.writeln("Failure: document.writeln actually worked");
+ }, "document.writeln in XHTML should throw an INVALID_STATE_ERR ");
+}, "document.writeln in XHTML");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-02.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-02.html
new file mode 100644
index 0000000000..2a64ac7561
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-02.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>document.writeln and null/undefined</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-document-writeln%28%29">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#documents-in-the-dom">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ doc = iframe.contentDocument;
+ test(function() {
+ doc.open();
+ doc.writeln(null);
+ doc.close();
+ assert_equals(doc.documentElement.textContent, "null\n");
+ }, "document.writeln(null)");
+ test(function() {
+ doc.open();
+ doc.writeln(undefined);
+ doc.close();
+ assert_equals(doc.documentElement.textContent, "undefined\n");
+ }, "document.writeln(undefined)");
+}, "Calling document.writeln with null and undefined");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-03.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-03.html
new file mode 100644
index 0000000000..df9a7a15c2
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-03.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>document.writeln with multiple arguments</title>
+<link rel="author" title="Sebmaster" href="mailto:wpt@smayr.name">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-document-writeln%28%29">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#documents-in-the-dom">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ var doc = iframe.contentDocument;
+ doc.open();
+ doc.writeln('a', 'b');
+ doc.close();
+ assert_equals(doc.documentElement.textContent, "ab\n");
+}, "Calling document.writeln with multiple arguments");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/original-id.json b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/original-id.json
new file mode 100644
index 0000000000..0cc32be6a2
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/original-id.json
@@ -0,0 +1 @@
+{"original_id":"document.writeln()"} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-encoding.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-encoding.html
new file mode 100644
index 0000000000..1debcafa3b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-encoding.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="windows-1252"> <!-- intentional to make sure the results are UTF-8 anyway -->
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/pull/9538">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- This was adapted from DOMParser-parseFromString-encoding.html -->
+
+<script>
+function assertEncoding(doc) {
+ assert_equals(doc.charset, "UTF-8", "document.charset");
+ assert_equals(doc.characterSet, "UTF-8", "document.characterSet");
+ assert_equals(doc.inputEncoding, "UTF-8", "document.characterSet");
+}
+
+setup(() => {
+ assert_equals(document.characterSet, "windows-1252", "the meta charset must be in effect, making the main document windows-1252");
+});
+
+test(() => {
+ const doc = Document.parseHTMLUnsafe('');
+ assertEncoding(doc);
+}, 'Parse empty string');
+
+test(() => {
+ const doc = Document.parseHTMLUnsafe(`<meta charset="latin2">`);
+ assertEncoding(doc);
+}, "meta charset latin2");
+
+test(() => {
+ const doc = Document.parseHTMLUnsafe(`<?xml version="1.0" encoding="latin2"?><x/>`);
+ assertEncoding(doc);
+}, "XML declaration");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-style-attribute.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-style-attribute.html
new file mode 100644
index 0000000000..cd092a6034
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-style-attribute.html
@@ -0,0 +1,54 @@
+<!doctype html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<!-- This test was adapted from style_attribute_html.html -->
+<meta charset=utf-8>
+<title>Style attribute in HTML</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+
+var div;
+setup(function() {
+ var input = '<div style="color: red">Foo</div>';
+ var doc = Document.parseHTMLUnsafe(input);
+ div = doc.querySelector('div');
+});
+
+test(function() {
+ var style = div.style;
+ assert_equals(style.cssText, 'color: red;');
+ assert_equals(style.color, 'red');
+ assert_equals(div.getAttribute("style"), 'color: red',
+ 'Value of style attribute should match the string value that was set');
+}, 'Parsing of initial style attribute');
+
+test(function() {
+ var style = div.style;
+ div.setAttribute('style', 'color:: invalid');
+ assert_equals(style.cssText, '');
+ assert_equals(style.color, '');
+ assert_equals(div.getAttribute('style'), 'color:: invalid',
+ 'Value of style attribute should match the string value that was set');
+}, 'Parsing of invalid style attribute');
+
+test(function() {
+ var style = div.style;
+ div.setAttribute('style', 'color: green');
+ assert_equals(style.cssText, 'color: green;');
+ assert_equals(style.color, 'green');
+ assert_equals(div.getAttribute('style'), 'color: green',
+ 'Value of style attribute should match the string value that was set');
+}, 'Parsing of style attribute');
+
+test(function() {
+ var style = div.style;
+ style.backgroundColor = 'blue';
+ assert_equals(style.cssText, 'color: green; background-color: blue;',
+ 'Should not drop the existing style');
+ assert_equals(style.color, 'green',
+ 'Should not drop the existing style');
+ assert_equals(div.getAttribute('style'), 'color: green; background-color: blue;',
+ 'Should update style attribute');
+}, 'Update style.backgroundColor');
+
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-base-pushstate.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-base-pushstate.html
new file mode 100644
index 0000000000..a8eab59511
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-base-pushstate.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<!-- This test was adapted from DOMParser-parseFromString-url-base-pushstate.html -->
+<title>parseHTMLUnsafe test of how the document's URL is set (base, pushstate)</title>
+<base href="/fake/base-from-outer-frame">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe src="/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-base-pushstate.html" onload="window.resolveLoadPromise();"></iframe>
+
+<script>
+"use strict";
+history.pushState(null, "", "/fake/push-state-from-outer-frame");
+</script>
+<script src="/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-url-tests.js"></script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-base.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-base.html
new file mode 100644
index 0000000000..62b2c09aae
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-base.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<!-- This was adapted from DOMParser-parseFromString-url-base.html -->
+<title>parseHTMLUnsafe test of how the document's URL is set (base, no pushstate)</title>
+<base href="/fake/base-from-outer-frame">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe src="/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-base.html" onload="window.resolveLoadPromise();"></iframe>
+<script src="/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-url-tests.js"></script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-moretests.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-moretests.html
new file mode 100644
index 0000000000..41335ef035
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-moretests.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<!-- This test was adapted from DOMParser-parseFromString-url-moretests.html -->
+<meta charset=utf-8>
+<title>Document.parseHTMLUnsafe: Document's url</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+async_test(function() {
+ var iframe = document.createElement("iframe");
+ iframe.onload = this.step_func(function() {
+ var child = iframe.contentWindow;
+
+ test(function() {
+ var doc = Document.parseHTMLUnsafe("<html></html>");
+ assert_equals(doc.URL, "about:blank");
+ }, "Parent window");
+
+ test(function() {
+ var doc = child.Document.parseHTMLUnsafe("<html></html>");
+ assert_equals(doc.URL, "about:blank");
+ }, "Child window");
+
+ var dpBeforeNavigation = child.Document, urlBeforeNavigation = child.document.URL;
+ iframe.onload = this.step_func_done(function() {
+ test(function() {
+ var doc = dpBeforeNavigation.parseHTMLUnsafe("<html></html>");
+ assert_equals(doc.URL, "about:blank");
+ }, "Child window crossing navigation");
+
+ test(function() {
+ var doc = child.Document.parseHTMLUnsafe("<html></html>");
+ assert_equals(doc.URL, "about:blank");
+ }, "Child window after navigation");
+ });
+ iframe.src = "/common/blank.html?2";
+ });
+ iframe.src = "/common/blank.html?1";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-pushstate.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-pushstate.html
new file mode 100644
index 0000000000..7930bf0ddb
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url-pushstate.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<!-- This test was adapted from DOMParser-parseFromString-url-pushstate.html -->
+<title>parseHTMLUnsafe test of how the document's URL is set (no base, pushstate)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe src="/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-pushstate.html" onload="window.resolveLoadPromise();"></iframe>
+
+<script>
+"use strict";
+history.pushState(null, "", "/fake/push-state-from-outer-frame");
+</script>
+<script src="/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-url-tests.js"></script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url.html
new file mode 100644
index 0000000000..911e183706
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe-url.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<!-- This test was adapted from DOMParser-parseFromString-url.html -->
+<title>parseHTMLUnsafe test of how the document's URL is set (no pushstate, no base)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe src="resources/parseHTMLUnsafe-iframe.html" onload="window.resolveLoadPromise();"></iframe>
+<script src="resources/parseHTMLUnsafe-url-tests.js"></script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe.html
new file mode 100644
index 0000000000..2a89370825
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe.html
@@ -0,0 +1,77 @@
+<!doctype html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<!-- This was adapted from DOMParser-parseFromString-html.html -->
+<title>parseHTMLUnsafe basic test of HTML parsing</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// |expected| should be an object indicating the expected type of node.
+function assert_node(actual, expected) {
+ assert_true(actual instanceof expected.type,
+ 'Node type mismatch: actual = ' + actual.constructor.name + ', expected = ' + expected.type.name);
+ if (typeof(expected.id) !== 'undefined')
+ assert_equals(actual.id, expected.id, expected.idMessage);
+}
+
+var doc;
+setup(function() {
+ doc = Document.parseHTMLUnsafe('<html id="root"><head></head><body></body></html>');
+});
+
+test(function() {
+ var root = doc.documentElement;
+ assert_node(root, { type: HTMLHtmlElement, id: 'root',
+ idMessage: 'documentElement id attribute should be root.' });
+}, 'Parsing of id attribute');
+
+test(function() {
+ assert_equals(doc.contentType, "text/html")
+}, 'contentType');
+
+test(function() {
+ assert_equals(doc.compatMode, "BackCompat")
+}, 'compatMode');
+
+test(function() {
+ doc = Document.parseHTMLUnsafe('<!DOCTYPE html><html id="root"><head></head><body></body></html>');
+ assert_equals(doc.compatMode, "CSS1Compat")
+}, 'compatMode for a proper DOCTYPE');
+
+// URL- and encoding-related stuff tested separately.
+
+test(function() {
+ assert_equals(doc.location, null,
+ 'The document must have a location value of null.');
+}, 'Location value');
+
+test(function() {
+ var htmldoc = Document.parseHTMLUnsafe("<!DOCTYPE foo></><foo></multiple></>");
+ assert_equals(htmldoc.documentElement.localName, "html");
+ assert_equals(htmldoc.documentElement.namespaceURI, "http://www.w3.org/1999/xhtml");
+}, "Document.parseHTMLUnsafe parses HTML tag soup with no problems");
+
+test(function() {
+ const doc = Document.parseHTMLUnsafe('<noembed>&lt;a&gt;</noembed>');
+ assert_equals(doc.querySelector('noembed').textContent, '&lt;a&gt;');
+}, 'Document.parseHTMLUnsafe should handle the content of <noembed> as raw text');
+
+test(() => {
+ const doc = Document.parseHTMLUnsafe(`
+<html><body>
+<style>
+ @import url(/dummy.css)
+</style>
+<script>document.x = 8<\/script>
+</body></html>`);
+
+ assert_not_equals(doc.querySelector('script'), null, 'script must be found');
+ assert_equals(doc.x, undefined, 'script must not be executed on the inner document');
+ assert_equals(document.x, undefined, 'script must not be executed on the outer document');
+}, 'script is found synchronously even when there is a css import');
+
+test(() => {
+ const doc = Document.parseHTMLUnsafe(`<body><noscript><p id="test1">test1<p id="test2">test2</noscript>`);
+ assert_node(doc.body.firstChild.childNodes[0], { type: HTMLParagraphElement, id: 'test1' });
+ assert_node(doc.body.firstChild.childNodes[1], { type: HTMLParagraphElement, id: 'test2' });
+}, 'must be parsed with scripting disabled, so noscript works');
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Element-setHTMLUnsafe-04.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Element-setHTMLUnsafe-04.html
new file mode 100644
index 0000000000..4a9e300afc
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Element-setHTMLUnsafe-04.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>setHTMLUnsafe in HTML</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel=author href="mailto:jarhar@chromium.org">
+<!-- This test was adapted from innerhtml-04.html -->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function testIsChild(p, c) {
+ assert_equals(p.firstChild, c);
+ assert_equals(c.parentNode, p);
+}
+test(function() {
+ var p = document.createElement('p');
+ var b = p.appendChild(document.createElement('b'));
+ var t = b.appendChild(document.createTextNode("foo"));
+ testIsChild(p, b);
+ testIsChild(b, t);
+ assert_equals(t.data, "foo");
+ p.setHTMLUnsafe("");
+ testIsChild(b, t);
+ assert_equals(t.data, "foo");
+}, "setHTMLUnsafe should leave the removed children alone.")
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-base-pushstate.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-base-pushstate.html
new file mode 100644
index 0000000000..53b855968c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-base-pushstate.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>An iframe that does parseHTMLUnsafe stuff with base and pushstates itself</title>
+<base href="/fake/base-from-iframe">
+
+<script>
+"use strict";
+history.pushState(null, "", "/fake/push-state-from-iframe");
+</script>
+<script src="/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe.js"></script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-base.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-base.html
new file mode 100644
index 0000000000..6977e93811
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-base.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>An iframe that does parseHTMLUnsafe stuff with base</title>
+<base href="/fake/base-from-iframe">
+
+<script src="/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe.js"></script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-pushstate.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-pushstate.html
new file mode 100644
index 0000000000..5e1fae0441
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe-pushstate.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>An iframe that does parseHTMLUnsafe stuff and pushstates itself</title>
+
+<script>
+"use strict";
+history.pushState(null, "", "/fake/push-state-from-iframe");
+</script>
+<script src="/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe.js"></script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe.html
new file mode 100644
index 0000000000..f47d9605fc
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>An iframe that does parseHTMLUnsafe stuff</title>
+<script src="parseHTMLUnsafe-iframe.js"></script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe.js
new file mode 100644
index 0000000000..4a0b56869b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-iframe.js
@@ -0,0 +1,3 @@
+window.doParse = (html) => {
+ return Document.parseHTMLUnsafe(html);
+};
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-url-tests.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-url-tests.js
new file mode 100644
index 0000000000..88344d127c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/resources/parseHTMLUnsafe-url-tests.js
@@ -0,0 +1,36 @@
+const loadPromise = new Promise(resolve => { window.resolveLoadPromise = resolve; });
+
+function assertURL(doc) {
+ assert_equals(doc.URL, "about:blank", "document.URL");
+ assert_equals(doc.documentURI, "about:blank", "document.documentURI");
+ assert_equals(doc.baseURI, "about:blank", "document.baseURI");
+}
+
+const inputs = {
+ valid: "<html></html>",
+ "invalid XML": `<span x:test="testing">1</span>`
+};
+
+for (const [inputName, input] of Object.entries(inputs)) {
+ test(() => {
+ const doc = Document.parseHTMLUnsafe(input);
+
+ assertURL(doc);
+ }, `${inputName}: created normally`);
+
+ promise_test(async () => {
+ await loadPromise;
+
+ const doc = frames[0].Document.parseHTMLUnsafe(input);
+
+ assertURL(doc);
+ }, `${inputName}: created using another iframe's parseHTMLUnsafe from this frame`);
+
+ promise_test(async () => {
+ await loadPromise;
+
+ const doc = frames[0].doParse(input);
+
+ assertURL(doc);
+ }, `${inputName}: created using another iframe's parseHTMLUnsafe from that frame`);
+}
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/setHTMLUnsafe-CEReactions.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/setHTMLUnsafe-CEReactions.html
new file mode 100644
index 0000000000..3aa14516f2
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/setHTMLUnsafe-CEReactions.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/9957">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+class MyElement extends HTMLElement {
+ constructor() {
+ super();
+ this.numConnectedCallback = 0;
+ this.numDisconnectedCallback = 0;
+ this.attributeChangedCalls = [];
+ }
+
+ connectedCallback() {
+ this.numConnectedCallback++;
+ }
+
+ disconnectedCallback() {
+ this.numDisconnectedCallback++;
+ }
+
+ static observedAttributes = ['foo'];
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.attributeChangedCalls.push({name, oldValue, newValue});
+ }
+}
+customElements.define('my-element', MyElement);
+
+['Element', 'ShadowRoot'].forEach(containerType => {
+ test(() => {
+ let container = null;
+ if (containerType === 'Element') {
+ container = document.createElement('div');
+ document.body.appendChild(container);
+ } else if (containerType === 'ShadowRoot') {
+ const host = document.createElement('div');
+ document.body.appendChild(host);
+ container = host.attachShadow({mode: 'closed'});
+ }
+
+ container.setHTMLUnsafe('<my-element>');
+ const myElement1 = container.querySelector('my-element');
+ assert_equals(myElement1.numConnectedCallback, 1,
+ 'myElement1.numConnectedCallback after first setHTMLUnsafe.');
+ assert_equals(myElement1.numDisconnectedCallback, 0,
+ 'myElement1.numDisconnectedCallback after first setHTMLUnsafe.');
+ assert_equals(JSON.stringify(myElement1.attributeChangedCalls),
+ JSON.stringify([]),
+ 'myElement1.attributeChangedCalls after first setHTMLUnsafe.');
+
+ container.setHTMLUnsafe('<my-element foo=bar>');
+ const myElement2 = container.querySelector('my-element');
+ assert_equals(myElement1.numConnectedCallback, 1,
+ 'myElement1.numConnectedCallback after second setHTMLUnsafe.');
+ assert_equals(myElement1.numDisconnectedCallback, 1,
+ 'myElement1.numDisconnectedCallback after second setHTMLUnsafe.');
+ assert_array_equals(myElement1.attributeChangedCalls, [],
+ 'myElement1.attributeChangedCalls after second setHTMLUnsafe.');
+ assert_equals(myElement2.numConnectedCallback, 1,
+ 'myElement2.numConnectedCallback after second setHTMLUnsafe.');
+ assert_equals(myElement2.numDisconnectedCallback, 0,
+ 'myElement2.numDisconnectedCallback after second setHTMLUnsafe.');
+ assert_equals(JSON.stringify(myElement2.attributeChangedCalls),
+ JSON.stringify([{name: 'foo', oldValue: null, newValue: 'bar'}]),
+ 'myElement2.attributeChangedCalls after second setHTMLUnsafe.');
+ }, `${containerType}.setHTMLUnsafe should trigger custom element reactions.`);
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/setHTMLUnsafe-xml.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/setHTMLUnsafe-xml.html
new file mode 100644
index 0000000000..d7211e97f1
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/setHTMLUnsafe-xml.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/pull/9538">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+test(() => {
+ const xmlDoc = document.implementation.createDocument(null, 'root', null);
+ assert_equals(xmlDoc.contentType, 'application/xml');
+ // Intentionally unclosed and misnested tags
+ xmlDoc.documentElement.setHTMLUnsafe('<p><foo><b><i>test</b></i>');
+ assert_equals(xmlDoc.documentElement.innerHTML,
+ '<p xmlns="http://www.w3.org/1999/xhtml"><foo><b><i>test</i></b></foo></p>',
+ 'Element.setHTMLUnsafe should use the HTML parser in XML documents.');
+}, 'setHTMLUnsafe should still parse HTML even in XML documents.');
+
+test(() => {
+ const svgDoc = document.implementation.createDocument('http://www.w3.org/2000/svg', 'root', null);
+ assert_equals(svgDoc.contentType, 'image/svg+xml');
+ // Intentionally unclosed and misnested tags
+ svgDoc.documentElement.setHTMLUnsafe('<p><foo><b><i>test</b></i>');
+ assert_equals(svgDoc.documentElement.innerHTML,
+ '<p xmlns="http://www.w3.org/1999/xhtml"><foo><b><i>test</i></b></foo></p>',
+ 'Element.setHTMLUnsafe should use the HTML parser in SVG documents.');
+}, 'setHTMLUnsafe should still parse HTML even in SVG documents.');
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/setHTMLUnsafe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/setHTMLUnsafe.html
new file mode 100644
index 0000000000..1c0b115569
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/html-unsafe-methods/setHTMLUnsafe.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/pull/9538">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+for (const containerType of ['Element', 'ShadowRoot']) {
+ const createContainer = () => {
+ if (containerType == 'ShadowRoot') {
+ return document.createElement('div').attachShadow({mode: 'open'});
+ } else if (containerType == 'Element') {
+ return document.createElement('div');
+ }
+ };
+
+ test(() => {
+ const container = createContainer();
+ container.setHTMLUnsafe('<span title=hello>world</span>');
+
+ assert_equals(container.children.length, 1, 'Only one child node should be created.');
+ assert_equals(container.firstChild.tagName, 'SPAN', 'The child element should be a span.');
+ assert_equals(container.firstChild.getAttribute('title'), 'hello',
+ 'The title attribute should be set to hello.');
+ assert_equals(container.firstChild.childNodes.length, 1,
+ 'The span should have one child.');
+ assert_true(container.firstChild.childNodes[0] instanceof Text,
+ 'The spans child should be a text node.');
+ assert_equals(container.firstChild.textContent, 'world',
+ 'The spans textContent should be world.');
+ }, `${containerType}: setHTMLUnsafe with no shadowdom.`);
+
+ test(() => {
+ const container = createContainer();
+ container.setHTMLUnsafe(`<div><template shadowrootmode=open><div>hello</div></template></div>`);
+
+ assert_equals(container.children.length, 1, 'One child should be created in the container.');
+ const shadowRoot = container.firstChild.shadowRoot;
+ assert_true(!!shadowRoot, 'The containers child should have a ShadowRoot.');
+ assert_equals(shadowRoot.children.length, 1, 'One child should be created in the ShadowRoot.');
+ assert_equals(shadowRoot.firstChild.textContent, 'hello',
+ 'The ShadowRoots childs textContent should be hello.');
+ }, `${containerType}: setHTMLUnsafe with shadowdom.`);
+}
+
+test(() => {
+ const template = document.createElement('template');
+ template.setHTMLUnsafe('<div>hello world</div>');
+
+ assert_equals(template.children.length, 0, 'template should not have any child nodes.');
+ assert_equals(template.content.children.length, 1, 'template content should have a child div.');
+ assert_equals(template.content.children[0].textContent, 'hello world', 'text content should be set.');
+}, 'template.setHTMLUnsafe() should modify template content fragment rather than actual children.');
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/002.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/002.html
new file mode 100644
index 0000000000..5584bf9afb
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/002.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<title>document.open during parsing</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var log = document.getElementById("log");
+ assert_equals(document.open(), document);
+ assert_equals(document.getElementById("log"), log);
+})
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/004.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/004.html
new file mode 100644
index 0000000000..3fb443a993
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/004.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>Reuse of document object after document.open</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<iframe src="/common/blank.html"></iframe>
+<script>
+var t = async_test();
+var iframe;
+onload = t.step_func(function() {
+ var iframe = document.getElementsByTagName("iframe")[0];
+ var handle = iframe.contentDocument;
+ iframe.contentDocument.test_state = 1;
+ assert_equals(iframe.contentDocument.open(), handle);
+ assert_equals(iframe.contentDocument.test_state, 1);
+ assert_equals(iframe.contentDocument, handle);
+ t.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/006.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/006.html
new file mode 100644
index 0000000000..1dcb92615d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/006.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>Cancelling error after document.open</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<iframe src="/common/blank.html"></iframe>
+<script>
+var t = async_test();
+var iframe;
+onload = t.step_func(function() {
+ var iframe = document.getElementsByTagName("iframe")[0];
+ var img = iframe.contentDocument.createElement("img");
+ img.onerror = t.step_func(function() {assert_unreached()})
+ img.src = "missing";
+ iframe.contentDocument.body.appendChild(img);
+ assert_equals(iframe.contentDocument.open(), iframe.contentDocument);
+ setTimeout(function() {t.done();}, 500);
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011-1.html
new file mode 100644
index 0000000000..37973fd52e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011-1.html
@@ -0,0 +1,5 @@
+<script>
+parent.t.step(() => { parent.assert_equals(document.open(), document); });
+setTimeout(parent.t.step_func(function() {parent.t.done()}), 0);
+document.close();
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011.html
new file mode 100644
index 0000000000..2acc884c54
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<title>Timeout after document.open</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var t = async_test();
+</script>
+<iframe src="011-1.html"></iframe>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012-1.html
new file mode 100644
index 0000000000..644b30827d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012-1.html
@@ -0,0 +1,7 @@
+<script>
+onload = parent.t.step_func(function() {
+ parent.assert_equals(document.open(), document);
+ setTimeout(parent.t.step_func(function() {parent.t.done()}), 0);
+ document.close();
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012.html
new file mode 100644
index 0000000000..518454858d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<title>Timeout after document.open in load event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var t = async_test();
+</script>
+<iframe src="012-1.html"></iframe>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013-1.html
new file mode 100644
index 0000000000..ea321238ed
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013-1.html
@@ -0,0 +1,7 @@
+<script>
+addEventListener("DOMContentLoaded", parent.t.step_func(function() {
+ parent.assert_equals(document.open(), document);
+ setTimeout(parent.t.step_func(function() {parent.t.done()}), 0);
+ document.close();
+}), false);
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013.html
new file mode 100644
index 0000000000..5749361aa8
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<title>Timeout after document.open in DOMContentLoaded event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var t = async_test();
+</script>
+<iframe src="013-1.html"></iframe>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014-1.html
new file mode 100644
index 0000000000..0e97808116
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014-1.html
@@ -0,0 +1,9 @@
+<script>
+onload = parent.t.step_func(function() {
+ setTimeout(parent.t.step_func(function() {
+ parent.assert_equals(document.open(), document);
+ setTimeout(parent.t.step_func(function() {parent.t.done()}), 0);
+ document.close();
+ }), 100)
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014.html
new file mode 100644
index 0000000000..b4e4b17cf4
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<title>Timeout after document.open after document is completely loaded</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var t = async_test();
+</script>
+<iframe src="014-1.html"></iframe>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015-1.html
new file mode 100644
index 0000000000..c325bd0801
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015-1.html
@@ -0,0 +1,17 @@
+<script>
+onload = function() {
+ window.test_prop = 1;
+ parent.tests[0].step(function() {parent.assert_equals(test_prop, 1)});
+ parent.tests[0].step(function() {parent.assert_equals(document.open(), document)});
+ document.write("<script>test_prop = 2;<\/script>");
+ document.close();
+ parent.tests[0].step(function() {parent.assert_equals(test_prop, 2)});
+ parent.tests[1].step(function() {parent.assert_equals(window.test_prop, 2)});
+ parent.tests[2].step(function() {parent.assert_equals(get_this(), window)});
+ parent.tests_done();
+};
+
+function get_this() {
+ return this;
+}
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015.html
new file mode 100644
index 0000000000..cce9e65d4c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Window vs global scope after document.open</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var tests = [async_test("global scope unchanged"),
+ async_test("window object unchanged"),
+ async_test("this is the window object")];
+function tests_done() {
+ tests.forEach(function(t) {t.done()});
+}
+</script>
+<iframe src="015-1.html"></iframe>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016-1.html
new file mode 100644
index 0000000000..ceeeb64df6
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016-1.html
@@ -0,0 +1,41 @@
+<script>
+window.test_prop = 1;
+</script>
+<script>
+onload = function() {
+ parent.tests[0].step(function() {
+ parent.assert_equals(document.open(), document);
+ });
+ document.write("<script>test_prop = 2; timeout_fired=false;<\/script>");
+ document.close();
+
+ setTimeout(function() {
+ parent.tests[0].step(function() {
+ parent.assert_equals(test_prop, 2, "Global scope from original window timeout");
+ parent.assert_equals(window.test_prop, 2, "Window property from original window timeout")
+ });
+ parent.tests[1].step(function() {
+ var t = get_this();
+ parent.assert_equals(t.test_prop, 2, "Window property from original window timeout");
+ parent.assert_equals(t, window, "Global scope from original window timeout");
+ });
+ }, 0);
+
+ window.setTimeout(function() {
+ parent.tests[2].step(function() {
+ parent.assert_equals(test_prop, 2, "Global scope from original window timeout");
+ parent.assert_equals(window.test_prop, 2, "Window property from original window timeout")
+ });
+ parent.tests[3].step(function() {
+ var t = get_this();
+ parent.assert_equals(t.test_prop, 2, "Window property from original window timeout");
+ parent.assert_equals(t, window, "Global scope from original window timeout");
+ });
+ parent.tests_done();
+ }, 100);
+};
+
+function get_this() {
+ return this;
+}
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016.html
new file mode 100644
index 0000000000..1c70fce591
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>setTimeout document.open</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var tests = [async_test("Timeout on original window, scope"),
+ async_test("Timeout on original window, this object"),
+ async_test("Timeout on new window, scope"),
+ async_test("Timeout on new window, this object")];
+function tests_done() {
+ tests.forEach(function(t) {t.done()});
+}
+</script>
+<iframe src="016-1.html"></iframe>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-immediate.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-immediate.window.js
new file mode 100644
index 0000000000..8d045b9e0a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-immediate.window.js
@@ -0,0 +1,119 @@
+// The following tests deal with the <meta http-equiv=refresh> pragma and the
+// `Refresh` header. The spec is still hazy on the precise behavior in those
+// cases but we use https://github.com/whatwg/html/issues/4003 as a guideline.
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+
+ const client = new frame.contentWindow.XMLHttpRequest();
+ client.open("GET", "/common/blank.html");
+ client.onabort = t.step_func_done();
+ client.send();
+
+ frame.contentDocument.open();
+ });
+ frame.src = "resources/meta-refresh.py?0";
+}, "document.open() aborts documents that are queued for navigation through <meta> refresh with timeout 0 (XMLHttpRequest)");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+
+ frame.contentWindow.fetch("/common/blank.html").then(
+ t.unreached_func("Fetch should have been aborted"),
+ t.step_func_done());
+
+ frame.contentDocument.open();
+ });
+ frame.src = "resources/meta-refresh.py?0";
+}, "document.open() aborts documents that are queued for navigation through <meta> refresh with timeout 0 (fetch())");
+
+// We cannot test for img element's error event for this test, as Firefox does
+// not fire the event if the fetch is aborted while Chrome does.
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+
+ let happened = false;
+ const img = frame.contentDocument.createElement("img");
+ img.src = new URL("resources/slow-png.py", document.URL);
+ img.onload = t.unreached_func("Image loading should not have succeeded");
+ // The image fetch starts in a microtask, so let's be sure to test after
+ // the fetch has started.
+ t.step_timeout(() => {
+ frame.contentDocument.open();
+ happened = true;
+ });
+ // If 3 seconds have passed and the image has still not loaded, we consider
+ // it aborted. slow-png.py only sleeps for 2 wallclock seconds.
+ t.step_timeout(t.step_func_done(() => {
+ assert_true(happened);
+ }), 3000);
+ });
+ frame.src = "resources/meta-refresh.py?0";
+}, "document.open() aborts documents that are queued for navigation through <meta> refresh with timeout 0 (image loading)");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+
+ const client = new frame.contentWindow.XMLHttpRequest();
+ client.open("GET", "/common/blank.html");
+ client.onabort = t.step_func_done();
+ client.send();
+
+ frame.contentDocument.open();
+ });
+ frame.src = "resources/http-refresh.py?0";
+}, "document.open() aborts documents that are queued for navigation through Refresh header with timeout 0 (XMLHttpRequest)");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+
+ frame.contentWindow.fetch("/common/blank.html").then(
+ t.unreached_func("Fetch should have been aborted"),
+ t.step_func_done());
+
+ frame.contentDocument.open();
+ });
+ frame.src = "resources/http-refresh.py?0";
+}, "document.open() aborts documents that are queued for navigation through Refresh header with timeout 0 (fetch())");
+
+// We cannot test for img element's error event for this test, as Firefox does
+// not fire the event if the fetch is aborted while Chrome does.
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+
+ let happened = false;
+ const img = frame.contentDocument.createElement("img");
+ img.src = new URL("resources/slow-png.py", document.URL);
+ img.onload = t.unreached_func("Image loading should not have succeeded");
+ // The image fetch starts in a microtask, so let's be sure to test after
+ // the fetch has started.
+ t.step_timeout(() => {
+ frame.contentDocument.open();
+ happened = true;
+ });
+ // If 3 seconds have passed and the image has still not loaded, we consider
+ // it aborted. slow-png.py only sleeps for 2 wallclock seconds.
+ t.step_timeout(t.step_func_done(() => {
+ assert_true(happened);
+ }), 3000);
+ });
+ frame.src = "resources/http-refresh.py?0";
+}, "document.open() aborts documents that are queued for navigation through Refresh header with timeout 0 (image loading)");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-header.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-header.window.js
new file mode 100644
index 0000000000..8c6c1267c4
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-header.window.js
@@ -0,0 +1,69 @@
+// The following tests deal with the <meta http-equiv=refresh> pragma and the
+// `Refresh` header. The spec is still hazy on the precise behavior in those
+// cases but we use https://github.com/whatwg/html/issues/4003 as a guideline.
+//
+// This is separate from abort-refresh-multisecond-meta.window.js to avoid
+// browser interventions that limit the number of connections in a tab.
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ let happened = false;
+
+ const client = new frame.contentWindow.XMLHttpRequest();
+ client.open("GET", "/common/blank.html");
+ client.onload = t.step_func_done(() => {
+ assert_true(happened);
+ });
+ client.onerror = t.unreached_func("XMLHttpRequest should have succeeded");
+ client.onabort = t.unreached_func("XMLHttpRequest should have succeeded");
+ client.ontimeout = t.unreached_func("XMLHttpRequest should have succeeded");
+ client.send();
+
+ frame.contentDocument.open();
+ happened = true;
+ });
+ frame.src = "resources/http-refresh.py?1";
+}, "document.open() does NOT abort documents that are queued for navigation through Refresh header with 1-sec timeout (XMLHttpRequest)");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ let happened = false;
+ frame.contentWindow.fetch("/common/blank.html").then(
+ t.step_func_done(() => {
+ assert_true(happened);
+ }),
+ t.unreached_func("Fetch should have succeeded")
+ );
+ frame.contentDocument.open();
+ happened = true;
+ });
+ frame.src = "resources/http-refresh.py?1";
+}, "document.open() does NOT abort documents that are queued for navigation through Refresh header with 1-sec timeout (fetch())");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ let happened = false;
+ const img = frame.contentDocument.createElement("img");
+ img.src = new URL("resources/slow-png.py", document.URL);
+ img.onload = t.step_func_done(() => {
+ assert_true(happened);
+ });
+ img.onerror = t.unreached_func("Image loading should not have errored");
+ // The image fetch starts in a microtask, so let's be sure to test after
+ // the fetch has started.
+ t.step_timeout(() => {
+ frame.contentDocument.open();
+ happened = true;
+ });
+ });
+ frame.src = "resources/http-refresh.py?4";
+}, "document.open() does NOT abort documents that are queued for navigation through Refresh header with 4-sec timeout (image loading)");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-meta.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-meta.window.js
new file mode 100644
index 0000000000..2895f959e5
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-meta.window.js
@@ -0,0 +1,69 @@
+// The following tests deal with the <meta http-equiv=refresh> pragma and the
+// `Refresh` header. The spec is still hazy on the precise behavior in those
+// cases but we use https://github.com/whatwg/html/issues/4003 as a guideline.
+//
+// This is separate from abort-refresh-multisecond-header.window.js to avoid
+// browser interventions that limit the number of connections in a tab.
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ let happened = false;
+
+ const client = new frame.contentWindow.XMLHttpRequest();
+ client.open("GET", "/common/blank.html");
+ client.onload = t.step_func_done(() => {
+ assert_true(happened);
+ });
+ client.onerror = t.unreached_func("XMLHttpRequest should have succeeded");
+ client.onabort = t.unreached_func("XMLHttpRequest should have succeeded");
+ client.ontimeout = t.unreached_func("XMLHttpRequest should have succeeded");
+ client.send();
+
+ frame.contentDocument.open();
+ happened = true;
+ });
+ frame.src = "resources/meta-refresh.py?1";
+}, "document.open() does NOT abort documents that are queued for navigation through <meta> refresh with 1-sec timeout (XMLHttpRequest)");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ let happened = false;
+ frame.contentWindow.fetch("/common/blank.html").then(
+ t.step_func_done(() => {
+ assert_true(happened);
+ }),
+ t.unreached_func("Fetch should have succeeded")
+ );
+ frame.contentDocument.open();
+ happened = true;
+ });
+ frame.src = "resources/meta-refresh.py?1";
+}, "document.open() does NOT abort documents that are queued for navigation through <meta> refresh with 1-sec timeout (fetch())");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ let happened = false;
+ const img = frame.contentDocument.createElement("img");
+ img.src = new URL("resources/slow-png.py", document.URL);
+ img.onload = t.step_func_done(() => {
+ assert_true(happened);
+ });
+ img.onerror = t.unreached_func("Image loading should not have errored");
+ // The image fetch starts in a microtask, so let's be sure to test after
+ // the fetch has started.
+ t.step_timeout(() => {
+ frame.contentDocument.open();
+ happened = true;
+ });
+ });
+ frame.src = "resources/meta-refresh.py?4";
+}, "document.open() does NOT abort documents that are queued for navigation through <meta> refresh with 4-sec timeout (image loading)");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-while-navigating.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-while-navigating.window.js
new file mode 100644
index 0000000000..e3efeffb8b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-while-navigating.window.js
@@ -0,0 +1,179 @@
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ const client = new frame.contentWindow.XMLHttpRequest();
+ client.open("GET", "/common/blank.html");
+ // The abort event handler is called synchronously in Chrome but
+ // asynchronously in Firefox. See https://crbug.com/879620.
+ client.onabort = t.step_func_done();
+ client.send();
+ frame.contentWindow.location.href = new URL("resources/dummy.html", document.URL);
+ frame.contentDocument.open();
+ });
+ frame.src = "/common/blank.html";
+}, "document.open() aborts documents that are navigating through Location (XMLHttpRequest)");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ let happened = false;
+ frame.contentWindow.fetch("/common/blank.html").then(
+ t.unreached_func("Fetch should have been aborted"),
+ t.step_func_done(() => {
+ assert_true(happened);
+ }));
+ frame.contentWindow.location.href = new URL("resources/dummy.html", document.URL);
+ frame.contentDocument.open();
+ happened = true;
+ });
+ frame.src = "/common/blank.html";
+}, "document.open() aborts documents that are navigating through Location (fetch())");
+
+// We cannot test for img element's error event for this test, as Firefox does
+// not fire the event if the fetch is aborted while Chrome does.
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ let happened = false;
+ const img = frame.contentDocument.createElement("img");
+ img.src = new URL("resources/slow-png.py", document.URL);
+ img.onload = t.unreached_func("Image loading should not have succeeded");
+ // The image fetch starts in a microtask, so let's be sure to test after
+ // the fetch has started.
+ t.step_timeout(() => {
+ frame.contentWindow.location.href = new URL("resources/dummy.html", document.URL);
+ frame.contentDocument.open();
+ happened = true;
+ });
+ // If 3 seconds have passed and the image has still not loaded, we consider
+ // it aborted. slow-png.py only sleeps for 2 wallclock seconds.
+ t.step_timeout(t.step_func_done(() => {
+ assert_true(happened);
+ }), 3000);
+ });
+ frame.src = "/common/blank.html";
+}, "document.open() aborts documents that are navigating through Location (image loading)");
+
+async_test(t => {
+ const div = document.body.appendChild(document.createElement("div"));
+ t.add_cleanup(() => div.remove());
+ div.innerHTML = "<iframe src='/common/slow.py'></iframe>";
+ const frame = div.childNodes[0];
+ const client = new frame.contentWindow.XMLHttpRequest();
+ client.open("GET", "/common/blank.html");
+ client.onabort = t.step_func_done();
+ client.send();
+ frame.contentDocument.open();
+}, "document.open() aborts documents that are navigating through iframe loading (XMLHttpRequest)");
+
+async_test(t => {
+ const div = document.body.appendChild(document.createElement("div"));
+ t.add_cleanup(() => div.remove());
+ div.innerHTML = "<iframe src='/common/slow.py'></iframe>";
+ const frame = div.childNodes[0];
+ frame.contentWindow.fetch("/common/blank.html").then(
+ t.unreached_func("Fetch should have been aborted"),
+ t.step_func_done());
+ frame.contentDocument.open();
+}, "document.open() aborts documents that are navigating through iframe loading (fetch())");
+
+// We cannot test for img element's error event for this test, as Firefox does
+// not fire the event if the fetch is aborted while Chrome does.
+//
+// We use /common/slow.py here as the source of the iframe, to prevent the
+// situation where when document.open() is called the initial about:blank
+// document has already become inactive.
+async_test(t => {
+ const div = document.body.appendChild(document.createElement("div"));
+ t.add_cleanup(() => div.remove());
+ div.innerHTML = "<iframe src='/common/slow.py'></iframe>";
+ const frame = div.childNodes[0];
+ let happened = false;
+ const img = frame.contentDocument.createElement("img");
+ img.src = new URL("resources/slow-png.py", document.URL);
+ img.onload = t.unreached_func("Image loading should not have succeeded");
+ // The image fetch starts in a microtask, so let's be sure to test after
+ // the fetch has started.
+ t.step_timeout(() => {
+ frame.contentDocument.open();
+ happened = true;
+ });
+ // If 3 seconds have passed and the image has still not loaded, we consider
+ // it aborted. slow-png.py only sleeps for 2 wallclock seconds.
+ t.step_timeout(t.step_func_done(() => {
+ assert_true(happened);
+ }), 3000);
+}, "document.open() aborts documents that are navigating through iframe loading (image loading)");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ const link = frame.contentDocument.body.appendChild(frame.contentDocument.createElement("a"));
+ link.href = new URL("resources/dummy.html", document.URL);
+
+ const client = new frame.contentWindow.XMLHttpRequest();
+ client.open("GET", "/common/blank.html");
+ client.onabort = t.step_func_done();
+ client.send();
+
+ link.click();
+ frame.contentDocument.open();
+ });
+ frame.src = "/common/blank.html";
+}, "document.open() aborts documents that are queued for navigation through .click() (XMLHttpRequest)");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ const link = frame.contentDocument.body.appendChild(frame.contentDocument.createElement("a"));
+ link.href = new URL("resources/dummy.html", document.URL);
+
+ frame.contentWindow.fetch("/common/blank.html").then(
+ t.unreached_func("Fetch should have been aborted"),
+ t.step_func_done());
+
+ link.click();
+ frame.contentDocument.open();
+ });
+ frame.src = "/common/blank.html";
+}, "document.open() aborts documents that are queued for navigation through .click() (fetch())");
+
+// We cannot test for img element's error event for this test, as Firefox does
+// not fire the event if the fetch is aborted while Chrome does.
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ const link = frame.contentDocument.body.appendChild(frame.contentDocument.createElement("a"));
+ link.href = new URL("resources/dummy.html", document.URL);
+
+ let happened = false;
+ const img = frame.contentDocument.createElement("img");
+ img.src = new URL("resources/slow-png.py", document.URL);
+ img.onload = t.unreached_func("Image loading should not have succeeded");
+ // The image fetch starts in a microtask, so let's be sure to test after
+ // the fetch has started.
+ t.step_timeout(() => {
+ link.click();
+ frame.contentDocument.open();
+ happened = true;
+ });
+ // If 3 seconds have passed and the image has still not loaded, we consider
+ // it aborted. slow-png.py only sleeps for 2 wallclock seconds.
+ t.step_timeout(t.step_func_done(() => {
+ assert_true(happened);
+ }), 3000);
+ });
+ frame.src = "/common/blank.html";
+}, "document.open() aborts documents that are queued for navigation through .click() (image loading)");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort.sub.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort.sub.window.js
new file mode 100644
index 0000000000..b2f05cf056
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort.sub.window.js
@@ -0,0 +1,104 @@
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ let happened = false;
+ const client = new frame.contentWindow.XMLHttpRequest();
+ client.open("GET", "/common/blank.html");
+ client.onload = t.step_func_done(e => {
+ assert_true(happened);
+ });
+ client.onerror = t.unreached_func("XMLHttpRequest should have succeeded");
+ client.onabort = t.unreached_func("XMLHttpRequest should have succeeded");
+ client.ontimeout = t.unreached_func("XMLHttpRequest should have succeeded");
+ client.send();
+ frame.contentDocument.open();
+ happened = true;
+ });
+ frame.src = "/common/blank.html";
+}, "document.open() does not abort documents that are not navigating (XMLHttpRequest)");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ let happened = false;
+ frame.contentWindow.fetch("/common/blank.html").then(
+ t.step_func_done(() => {
+ assert_true(happened);
+ }),
+ t.unreached_func("Fetch should have succeeded")
+ );
+ frame.contentDocument.open();
+ happened = true;
+ });
+ frame.src = "/common/blank.html";
+}, "document.open() does not abort documents that are not navigating (fetch())");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ let happened = false;
+ const img = frame.contentDocument.createElement("img");
+ img.src = new URL("resources/slow-png.py", document.URL);
+ img.onload = t.step_func_done(() => {
+ assert_true(happened);
+ });
+ img.onerror = t.unreached_func("Image loading should not have errored");
+ // The image fetch starts in a microtask, so let's be sure to test after
+ // the fetch has started.
+ t.step_timeout(() => {
+ frame.contentDocument.open();
+ happened = true;
+ });
+ });
+ frame.src = "/common/blank.html";
+}, "document.open() does not abort documents that are not navigating (image loading)");
+
+async_test(t => {
+ const __SERVER__NAME = "{{host}}";
+ const __PORT = {{ports[ws][0]}};
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ let happened = false;
+ const ws = new frame.contentWindow.WebSocket(`ws://${__SERVER__NAME}:${__PORT}/echo`);
+ ws.onopen = t.step_func_done(() => {
+ assert_true(happened);
+ });
+ ws.onclose = t.unreached_func("WebSocket fetch should have succeeded");
+ ws.onerror = t.unreached_func("WebSocket should have no error");
+ frame.contentDocument.open();
+ happened = true;
+ });
+ frame.src = "/common/blank.html";
+}, "document.open() does not abort documents that are not navigating (establish a WebSocket connection)");
+
+// An already established WebSocket connection shouldn't be terminated during
+// an "abort a document" anyway. Test just for completeness.
+async_test(t => {
+ const __SERVER__NAME = "{{host}}";
+ const __PORT = {{ports[ws][0]}};
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ let happened = false;
+ const ws = new frame.contentWindow.WebSocket(`ws://${__SERVER__NAME}:${__PORT}/echo`);
+ ws.onopen = t.step_func(() => {
+ t.step_timeout(t.step_func_done(() => {
+ assert_true(happened);
+ }), 100);
+ frame.contentDocument.open();
+ happened = true;
+ });
+ ws.onclose = t.unreached_func("WebSocket should not be closed");
+ ws.onerror = t.unreached_func("WebSocket should have no error");
+ });
+ frame.src = "/common/blank.html";
+}, "document.open() does not abort documents that are not navigating (already established WebSocket connection)");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.js
new file mode 100644
index 0000000000..ba7278ef18
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.js
@@ -0,0 +1,31 @@
+// document.open() bails out early if there is an active parser with non-zero
+// script nesting level or if a load was aborted while there was an active
+// parser. window.stop() aborts the current parser, so once it has been called
+// while a parser is active, document.open() will no longer do anything to that
+// document,
+
+window.handlers = {};
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.src = "resources/aborted-parser-frame.html";
+ window.handlers.afterOpen = t.step_func_done(() => {
+ const openCalled = frame.contentDocument.childNodes.length === 0;
+ assert_false(openCalled, "child document should not be empty");
+ assert_equals(frame.contentDocument.querySelector("p").textContent,
+ "Text", "Should still have our paragraph");
+ });
+}, "document.open() after parser is aborted");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.src = "resources/aborted-parser-async-frame.html";
+ window.handlers.afterOpenAsync = t.step_func_done(() => {
+ const openCalled = frame.contentDocument.childNodes.length === 0;
+ assert_false(openCalled, "child document should not be empty");
+ assert_equals(frame.contentDocument.querySelector("p").textContent,
+ "Text", "Should still have our paragraph");
+ });
+}, "async document.open() after parser is aborted");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/active.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/active.window.js
new file mode 100644
index 0000000000..f96710999a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/active.window.js
@@ -0,0 +1,98 @@
+function assertOpenIsEffective(doc, initialNodeCount) {
+ assert_equals(doc.childNodes.length, initialNodeCount);
+
+ // Test direct document.open() call.
+ assert_equals(doc.open(), doc);
+ assert_equals(doc.childNodes.length, 0, "after open: no nodes in document");
+ doc.write("<!DOCTYPE html>");
+ assert_equals(doc.childNodes.length, 1, "after write: doctype node in document");
+ doc.close();
+ assert_equals(doc.childNodes.length, 2, "after parser close: doctype node and an html element in document");
+
+ // Test implicit document.open() call through write(). Since we called
+ // doc.close() above, which sets the insertion point of the parser to
+ // undefined, document.write() will run the document open steps.
+ doc.write();
+ assert_equals(doc.childNodes.length, 0, "after implicit open: no nodes in document");
+ doc.write("<!DOCTYPE html>");
+ assert_equals(doc.childNodes.length, 1, "after write: doctype node in document");
+ doc.close();
+ assert_equals(doc.childNodes.length, 2, "after parser close: doctype node and an html element in document");
+}
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ assertOpenIsEffective(frame.contentDocument, 1);
+}, "document.open() removes the document's children (fully active document)");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ const childFrame = frame.contentDocument.querySelector("iframe");
+ const childDoc = childFrame.contentDocument;
+ const childWin = childFrame.contentWindow;
+
+ // Right now childDoc is still fully active.
+
+ frame.onload = t.step_func_done(() => {
+ // Now childDoc is still active but no longer fully active.
+ assertOpenIsEffective(childDoc, 1);
+ });
+ frame.src = "/common/blank.html";
+ });
+ frame.src = "resources/page-with-frame.html";
+}, "document.open() removes the document's children (active but not fully active document)");
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ const doc = frame.contentDocument;
+
+ // Right now the frame is connected and it has an active document.
+ frame.remove();
+
+ // Now the frame is no longer connected. Its document is no longer active.
+ assertOpenIsEffective(doc, 1);
+}, "document.open() removes the document's children (non-active document with an associated Window object; frame is removed)");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.src = "resources/dummy.html";
+
+ frame.onload = t.step_func(() => {
+ const firstDocument = frame.contentDocument;
+ // Right now the frame is connected and it has an active document.
+
+ frame.onload = t.step_func_done(() => {
+ // Now even though the frame is still connected, its document is no
+ // longer active.
+ assert_not_equals(frame.contentDocument, firstDocument);
+ assertOpenIsEffective(firstDocument, 2);
+ });
+
+ frame.src = "/common/blank.html";
+ });
+}, "document.open() removes the document's children (non-active document with an associated Window object; navigated away)");
+
+test(t => {
+ const doc = document.implementation.createHTMLDocument();
+ assertOpenIsEffective(doc, 2);
+}, "document.open() removes the document's children (non-active document without an associated Window object; createHTMLDocument)");
+
+test(t => {
+ const doc = new DOMParser().parseFromString("", "text/html");
+ assertOpenIsEffective(doc, 1);
+}, "document.open() removes the document's children (non-active document without an associated Window object; DOMParser)");
+
+async_test(t => {
+ const xhr = new XMLHttpRequest();
+ xhr.onload = t.step_func_done(() => {
+ assert_equals(xhr.status, 200);
+ assertOpenIsEffective(xhr.responseXML, 2);
+ });
+ xhr.responseType = "document";
+ xhr.open("GET", "resources/dummy.html");
+ xhr.send();
+}, "document.open() removes the document's children (non-active document without an associated Window object; XMLHttpRequest)");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-origin.sub.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-origin.sub.window.js
new file mode 100644
index 0000000000..b20c3e3f31
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-origin.sub.window.js
@@ -0,0 +1,117 @@
+document.domain = "{{host}}";
+
+// In many cases in this test, we want to delay execution of a piece of code so
+// that the entry settings object would be the top-level page. A microtask is
+// perfect for this purpose as it is executed in the "clean up after running
+// script" algorithm, which is generally called right after the callback.
+function setEntryToTopLevel(cb) {
+ Promise.resolve().then(cb);
+}
+
+async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => { iframe.remove(); });
+ iframe.onload = t.step_func_done(() => {
+ // Since this is called as an event handler on an element of this window,
+ // the entry settings object is that of this browsing context.
+ assert_throws_dom(
+ "InvalidStateError",
+ iframe.contentWindow.DOMException,
+ () => {
+ iframe.contentDocument.open();
+ },
+ "opening an XML document should throw an InvalidStateError"
+ );
+ });
+ const frameURL = new URL("resources/bailout-order-xml-with-domain-frame.sub.xhtml", document.URL);
+ frameURL.port = "{{ports[http][1]}}";
+ iframe.src = frameURL.href;
+}, "document.open should throw an InvalidStateError with XML document even if it is cross-origin");
+
+async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => { iframe.remove(); });
+ window.onCustomElementReady = t.step_func(() => {
+ window.onCustomElementReady = t.unreached_func("onCustomElementReady called again");
+ // Here, the entry settings object is still the iframe's, as the function
+ // is called from a custom element constructor in the iframe document.
+ // Delay execution in such a way that makes the entry settings object the
+ // top-level page's, but without delaying too much that the
+ // throw-on-dynamic-markup-insertion counter gets decremented (which is
+ // what this test tries to pit against the cross-origin document check).
+ //
+ // "Clean up after running script" is executed through the "construct" Web
+ // IDL algorithm in "create an element", called by "create an element for a
+ // token" in the parser.
+ setEntryToTopLevel(t.step_func_done(() => {
+ assert_throws_dom(
+ "InvalidStateError",
+ iframe.contentWindow.DOMException,
+ () => {
+ iframe.contentDocument.open();
+ },
+ "opening a document when the throw-on-dynamic-markup-insertion counter is incremented should throw an InvalidStateError"
+ );
+ }));
+ });
+ const frameURL = new URL("resources/bailout-order-custom-element-with-domain-frame.sub.html", document.URL);
+ frameURL.port = "{{ports[http][1]}}";
+ iframe.src = frameURL.href;
+}, "document.open should throw an InvalidStateError when the throw-on-dynamic-markup-insertion counter is incremented even if the document is cross-origin");
+
+async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => { iframe.remove(); });
+ self.testSynchronousScript = t.step_func(() => {
+ // Here, the entry settings object is still the iframe's, as the function
+ // is synchronously called from a <script> element in the iframe's
+ // document.
+ //
+ // "Clean up after running script" is executed when the </script> tag is
+ // seen by the HTML parser.
+ setEntryToTopLevel(t.step_func_done(() => {
+ assert_throws_dom(
+ "SecurityError",
+ iframe.contentWindow.DOMException,
+ () => {
+ iframe.contentDocument.open();
+ },
+ "opening a same origin-domain (but not same origin) document should throw a SecurityError"
+ );
+ }));
+ });
+ const frameURL = new URL("resources/bailout-order-synchronous-script-with-domain-frame.sub.html", document.URL);
+ frameURL.port = "{{ports[http][1]}}";
+ iframe.src = frameURL.href;
+}, "document.open should throw a SecurityError with cross-origin document even when there is an active parser executing script");
+
+for (const ev of ["beforeunload", "pagehide", "unload"]) {
+ async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => { iframe.remove(); });
+ iframe.addEventListener("load", t.step_func(() => {
+ iframe.contentWindow.addEventListener(ev, t.step_func(() => {
+ // Here, the entry settings object should be the top-level page's, as
+ // the callback context of this event listener is the incumbent
+ // settings object, which is the this page. However, due to a Chrome
+ // bug (https://crbug.com/606900), the entry settings object may be
+ // mis-set to the iframe's.
+ //
+ // "Clean up after running script" is called in the task that
+ // navigates.
+ setEntryToTopLevel(t.step_func_done(() => {
+ assert_throws_dom(
+ "SecurityError",
+ iframe.contentWindow.DOMException,
+ () => {
+ iframe.contentDocument.open();
+ },
+ "opening a same origin-domain (but not same origin) document should throw a SecurityError"
+ );
+ }));
+ }));
+ iframe.src = "about:blank";
+ }), { once: true });
+ iframe.src = "http://{{host}}:{{ports[http][1]}}/common/domain-setter.sub.html";
+ }, `document.open should throw a SecurityError with cross-origin document even when the ignore-opens-during-unload counter is greater than 0 (during ${ev} event)`);
+}
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-xml.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-xml.window.js
new file mode 100644
index 0000000000..45a67f925b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-xml.window.js
@@ -0,0 +1,26 @@
+async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => { iframe.remove(); });
+ self.testSynchronousScript = t.step_func_done(() => {
+ assert_throws_dom("InvalidStateError", iframe.contentWindow.DOMException, () => {
+ iframe.contentDocument.open();
+ }, "opening an XML document should throw");
+ });
+ iframe.src = "resources/bailout-order-xml-with-synchronous-script-frame.xhtml";
+}, "document.open should throw an InvalidStateError with XML document even when there is an active parser executing script");
+
+for (const ev of ["beforeunload", "pagehide", "unload"]) {
+ async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => { iframe.remove(); });
+ iframe.addEventListener("load", t.step_func(() => {
+ iframe.contentWindow.addEventListener(ev, t.step_func_done(() => {
+ assert_throws_dom("InvalidStateError", iframe.contentWindow.DOMException, () => {
+ iframe.contentDocument.open();
+ }, "opening an XML document should throw");
+ }));
+ iframe.src = "about:blank";
+ }), { once: true });
+ iframe.src = "/common/dummy.xhtml";
+ }, `document.open should throw an InvalidStateError with XML document even when the ignore-opens-during-unload counter is greater than 0 (during ${ev} event)`);
+}
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-ignore-opens-during-unload.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-ignore-opens-during-unload.window.js
new file mode 100644
index 0000000000..98ffba20a1
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-ignore-opens-during-unload.window.js
@@ -0,0 +1,18 @@
+// META: script=resources/document-open-side-effects.js
+
+for (const ev of ["unload", "beforeunload", "pagehide"]) {
+ async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => iframe.remove());
+ iframe.src = "/common/blank.html";
+ iframe.onload = t.step_func(() => {
+ iframe.contentWindow.addEventListener(ev, t.step_func_done(() => {
+ const origURL = iframe.contentDocument.URL;
+ assertDocumentIsReadyForSideEffectsTest(iframe.contentDocument, `ignore-opens-during-unload counter is greater than 0 during ${ev} event`);
+ assert_equals(iframe.contentDocument.open(), iframe.contentDocument);
+ assertOpenHasNoSideEffects(iframe.contentDocument, origURL, `ignore-opens-during-unload counter is greater than 0 during ${ev} event`);
+ }));
+ iframe.src = "about:blank";
+ });
+ }, `document.open bailout should not have any side effects (ignore-opens-during-unload is greater than 0 during ${ev} event)`);
+}
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-same-origin-domain.sub.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-same-origin-domain.sub.window.js
new file mode 100644
index 0000000000..f5edd7aed9
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-same-origin-domain.sub.window.js
@@ -0,0 +1,14 @@
+// META: script=/html/resources/common.js
+// META: script=resources/document-open-side-effects.js
+
+document.domain = "{{host}}";
+
+testInIFrame("http://{{host}}:{{ports[http][1]}}/common/domain-setter.sub.html", (ctx) => {
+ const iframe = ctx.iframes[0];
+ const origURL = iframe.contentDocument.URL;
+ assertDocumentIsReadyForSideEffectsTest(iframe.contentDocument, "same origin-domain (but not same origin) document");
+ assert_throws_dom("SecurityError", iframe.contentWindow.DOMException, () => {
+ ctx.iframes[0].contentDocument.open();
+ }, "document.open() should throw a SecurityError on a same origin-domain (but not same origin) document");
+ assertOpenHasNoSideEffects(iframe.contentDocument, origURL, "same origin-domain (but not same origin) document");
+}, "document.open bailout should not have any side effects (same origin-domain (but not same origin) document)");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-synchronous-script.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-synchronous-script.window.js
new file mode 100644
index 0000000000..fb26c70a9c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-synchronous-script.window.js
@@ -0,0 +1,19 @@
+// META: script=resources/document-open-side-effects.js
+
+async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => iframe.remove());
+ self.testSynchronousScript = t.step_func(() => {
+ // Here, the entry settings object is still the iframe's. Delay it in such
+ // a way that makes the entry settings object the top-level page's, but
+ // without delaying too much that the parser becomes inactive. A microtask
+ // is perfect as it's executed in "clean up after running script".
+ Promise.resolve().then(t.step_func_done(() => {
+ const origURL = iframe.contentDocument.URL;
+ assertDocumentIsReadyForSideEffectsTest(iframe.contentDocument, "active parser whose script nesting level is greater than 0");
+ assert_equals(iframe.contentDocument.open(), iframe.contentDocument);
+ assertOpenHasNoSideEffects(iframe.contentDocument, origURL, "active parser whose script nesting level is greater than 0");
+ }));
+ });
+ iframe.src = "resources/bailout-order-synchronous-script-frame.html";
+}, "document.open bailout should not have any side effects (active parser whose script nesting level is greater than 0)");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-xml.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-xml.window.js
new file mode 100644
index 0000000000..bbfc015c68
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-xml.window.js
@@ -0,0 +1,20 @@
+// META: script=resources/document-open-side-effects.js
+
+async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => iframe.remove());
+ iframe.src = "/common/dummy.xhtml";
+ iframe.onload = t.step_func_done(() => {
+ const origURL = iframe.contentDocument.URL;
+ assertDocumentIsReadyForSideEffectsTest(iframe.contentDocument, "XML document");
+ assert_throws_dom(
+ "InvalidStateError",
+ iframe.contentWindow.DOMException,
+ () => {
+ iframe.contentDocument.open();
+ },
+ "document.open() should throw on XML documents"
+ );
+ assertOpenHasNoSideEffects(iframe.contentDocument, origURL, "XML document");
+ });
+}, "document.open bailout should not have any side effects (XML document)");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/beforeunload.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/beforeunload.window.js
new file mode 100644
index 0000000000..1e2f891c17
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/beforeunload.window.js
@@ -0,0 +1,18 @@
+// In an earlier version of the HTML Standard, document open steps had "prompt
+// to unload document" as a step. Test that this no longer happens.
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.src = "/common/blank.html";
+ frame.onload = t.step_func(() => {
+ frame.contentWindow.onbeforeunload = t.unreached_func("beforeunload should not be fired");
+ frame.contentDocument.open();
+ t.step_timeout(t.step_func_done(() => {
+ // If the beforeunload event has still not fired by this point, we
+ // consider the test a success. `frame.remove()` above will allow the
+ // `load` event to be fired on the top-level Window, thus unblocking
+ // testharness.
+ }), 500);
+ });
+}, "document.open() should not fire a beforeunload event");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/crbug-583445-regression.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/crbug-583445-regression.window.js
new file mode 100644
index 0000000000..3809c2e081
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/crbug-583445-regression.window.js
@@ -0,0 +1,127 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+//
+// This is a regression test for crbug.com/583445. It checks an obscure bug in
+// Chromium's handling of `document.open()` whereby the URL change would affect
+// the document's origin after a javascript navigation.
+//
+// See also dcheng@'s comments on the original code review in which he
+// introduced the precursor to this test:
+// https://codereview.chromium.org/1675473002.
+
+function nextMessage() {
+ return new Promise((resolve) => {
+ window.addEventListener("message", (e) => { resolve(e.data); }, {
+ once: true
+ });
+ });
+}
+
+promise_test(async (t) => {
+ // Embed a cross-origin frame A and set up remote code execution.
+ const iframeA = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => { iframeA.remove(); });
+
+ const uuidA = token();
+ iframeA.src = remoteExecutorUrl(uuidA, { host: get_host_info().REMOTE_HOST });
+ const ctxA = new RemoteContext(uuidA);
+
+ // Frame A embeds a cross-origin frame B, which is same-origin with the
+ // top-level frame. Frame B is the center of this test: it is where we will
+ // verify that a bug does not grant it UXSS in frame A.
+ //
+ // Though we could reach into `iframeA.frames[0]` to get a proxy to frame B
+ // and use `setTimeout()` like below to execute code inside it, we set up
+ // remote code execution using `dispatcher.js` for better ergonomics.
+ const uuidB = token();
+ await ctxA.execute_script((url) => {
+ const iframeB = document.createElement("iframe");
+ iframeB.src = url;
+ document.body.appendChild(iframeB);
+ }, [remoteExecutorUrl(uuidB).href]);
+
+ // Start listening for a message, which will come as a result of executing
+ // the code below in frame B.
+ const message = nextMessage();
+
+ const ctxB = new RemoteContext(uuidB);
+ await ctxB.execute_script(() => {
+ // Frame B embeds an `about:blank` frame C.
+ const iframeC = document.body.appendChild(document.createElement("iframe"));
+
+ // We wish to execute code inside frame C, but it is important to this test
+ // that its URL remain `about:blank`, so we cannot use `dispatcher.js`.
+ // Instead we rely on `setTimeout()`.
+ //
+ // We use `setTimeout(string, ...)` instead of `setTimeout(function, ...)`
+ // as the given script executes against the target window's global object
+ // and does not capture any local variables.
+ //
+ // In order to have nice syntax highlighting and avoid quote-escaping hell,
+ // we use a trick employed by `dispatcher.js`. We rely on the fact that
+ // functions in JS have a stringifier that returns their source code. Thus
+ // `"(" + func + ")()"` is a string that executes `func()` when evaluated.
+ iframeC.contentWindow.setTimeout("(" + (() => {
+ // This executes in frame C.
+
+ // Frame C calls `document.open()` on its parent, which results in B's
+ // URL being set to `about:blank` (C's URL).
+ //
+ // However, just before `document.open()` is called, B schedules a
+ // self-navigation to a `javascript:` URL. This will occur after
+ // `document.open()`, so the document will navigate from `about:blank` to
+ // the new URL.
+ //
+ // This should not result in B's origin changing, so B should remain
+ // same-origin with the top-level frame.
+ //
+ // Due to crbug.com/583445, this used to behave wrongly in Chromium. The
+ // navigation code incorrectly assumed that B's origin should be inherited
+ // from its parent A because B's URL was `about:blank`.
+ //
+ // It is important to schedule this from within the child, as this
+ // guarantees that `document.open()` will be called before the navigation.
+ // A previous version of this test scheduled this from within frame B
+ // right after scheduling the call to `document.open()`, but that ran the
+ // risk of races depending on which timeout fired first.
+ parent.window.setTimeout("(" + (() => {
+ // This executes in frame B.
+
+ location = "javascript:(" + (() => {
+ /* This also executes in frame B.
+ *
+ * Note that because this whole function gets stuffed in a JS URL,
+ * single-line comments do not work, as they affect the following
+ * lines. */
+
+ let error;
+ try {
+ /* This will fail with a `SecurityError` if frame B is no longer
+ * same-origin with the top-level frame. */
+ top.window.testSameOrigin = true;
+ } catch (e) {
+ error = e;
+ }
+
+ top.postMessage({
+ error: error?.toString(),
+ }, "*");
+
+ }) + ")()";
+
+ }) + ")()", 0);
+
+ // This executes in frame C.
+ parent.document.open();
+
+ }) + ")()", 0);
+ });
+
+ // Await the message from frame B after its navigation.
+ const { error } = await message;
+ assert_equals(error, undefined, "error accessing top frame from frame B");
+ assert_true(window.testSameOrigin, "top frame testSameOrigin is mutated");
+
+}, "Regression test for crbug.com/583445");
+
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/custom-element.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/custom-element.window.js
new file mode 100644
index 0000000000..1ad06b3d37
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/custom-element.window.js
@@ -0,0 +1,39 @@
+// The document open steps have:
+//
+// 2. If document's throw-on-dynamic-markup-insertion counter is greater than
+// 0, then throw an "InvalidStateError" DOMException.
+//
+// The throw-on-dynamic-markup-insertion counter is only incremented when the
+// parser creates a custom element, not when createElement is called. Test for
+// this.
+//
+// See: https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps
+
+const noError = Symbol("no error");
+let err = noError;
+
+class CustomElement extends HTMLElement {
+ constructor() {
+ super();
+ try {
+ assert_equals(document.open(), document);
+ } catch (e) {
+ err = e;
+ }
+ }
+}
+customElements.define("custom-element", CustomElement);
+
+test(t => {
+ err = noError;
+ document.createElement("custom-element");
+ assert_equals(err, noError);
+}, "document.open() works in custom element constructor for createElement()");
+
+test(t => {
+ err = noError;
+ document.write("<custom-element></custom-element>");
+ assert_throws_dom("InvalidStateError", () => {
+ throw err;
+ });
+}, "document.open() is forbidden in custom element constructor when creating element from parser");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document-open-cancels-javascript-url-navigation.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document-open-cancels-javascript-url-navigation.html
new file mode 100644
index 0000000000..5596382f22
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document-open-cancels-javascript-url-navigation.html
@@ -0,0 +1,17 @@
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(t => {
+ window.onload = t.step_func_done(() => assert_equals(i.contentDocument.body.innerText, "PASS"));
+
+ var i = document.createElement('iframe');
+ i.id ='i';
+ i.src = "javascript:'FAIL'";
+ document.body.appendChild(i);
+ i.contentDocument.open();
+ i.contentDocument.write("PASS")
+ i.contentDocument.close();
+});
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-01.xhtml b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-01.xhtml
new file mode 100644
index 0000000000..c02b3e4db5
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-01.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>document.open in XHTML</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"/>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#opening-the-input-stream"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_throws_dom("INVALID_STATE_ERR", function() {
+ document.open();
+ }, "document.open in XHTML should throw an INVALID_STATE_ERR ");
+}, "document.open in XHTML");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-02.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-02.html
new file mode 100644
index 0000000000..c7e67a0cf7
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-02.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>document.open with three arguments</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-document-open">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function open() {
+ assert_unreached("The call should be redirected to the real window.open")
+}
+test(function(t) {
+ var w;
+ t.add_cleanup(function() {try {w.close()} catch(e) {}});
+ w = document.open("/resources/testharness.js", "", "");
+ assert_true(w instanceof w.Window, "Expected a window");
+}, "document.open should redirect to window.open when called with three arguments");
+
+test(function() {
+ var parser = new DOMParser();
+ var doc = parser.parseFromString("", "text/html");
+ assert_equals(doc.defaultView, null);
+ assert_throws_dom("INVALID_ACCESS_ERR", function() {
+ doc.open("/resources/testharness.js", "", "");
+ });
+}, "document.open should throw when it has no window and is called with three arguments");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03-frame.html
new file mode 100644
index 0000000000..a4b370cea4
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03-frame.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script>
+onload = function() {
+ document.open();
+ document.close();
+ parent.report(window.setTimeout === setTimeout, true, "setTimeout");
+ parent.report(window === this, true, "this");
+ parent.done();
+}
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03.html
new file mode 100644
index 0000000000..e446d70219
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>document.open and no singleton replacement</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-document-open">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var t = async_test();
+function report(actual, expected, message) {
+ t.step(function() {
+ assert_equals(actual, expected, message);
+ });
+}
+function done() {
+ t.done();
+}
+</script>
+<iframe src=document.open-03-frame.html></iframe>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/encoding.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/encoding.window.js
new file mode 100644
index 0000000000..f0d133a532
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/encoding.window.js
@@ -0,0 +1,12 @@
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ frame.src = "resources/encoding-frame.html";
+ frame.onload = t.step_func_done(t => {
+ // Using toLowerCase() to avoid an Edge bug
+ assert_equals(frame.contentDocument.characterSet.toLowerCase(), "shift_jis", "precondition");
+ assert_equals(frame.contentDocument.open(), frame.contentDocument);
+ assert_equals(frame.contentDocument.characterSet.toLowerCase(), "shift_jis", "actual test");
+ frame.contentDocument.close();
+ assert_equals(frame.contentDocument.characterSet.toLowerCase(), "shift_jis", "might as well");
+ });
+}, "doucment.open() and the document's encoding");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/event-listeners.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/event-listeners.window.js
new file mode 100644
index 0000000000..df07124d81
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/event-listeners.window.js
@@ -0,0 +1,308 @@
+// Many of the active-related test cases in this file came from
+// active.window.js. However, we cannot test the "navigated away" non-active
+// case right now due to https://github.com/whatwg/html/issues/3997.
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe")),
+ body = frame.contentDocument.body;
+ t.add_cleanup(() => frame.remove());
+ const div = body.appendChild(frame.contentDocument.createElement("div"));
+ div.addEventListener("click", t.unreached_func("element event listener not removed"));
+ frame.contentDocument.open();
+ div.click();
+ frame.contentDocument.close();
+}, "Standard event listeners are to be removed");
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe")),
+ body = frame.contentDocument.body;
+ t.add_cleanup(() => frame.remove());
+ frame.contentDocument.addEventListener("x", t.unreached_func("document event listener not removed"));
+ body.addEventListener("x", t.unreached_func("body event listener not removed"));
+ frame.contentDocument.open();
+ frame.contentDocument.dispatchEvent(new Event("x"));
+ body.dispatchEvent(new Event("x"));
+ frame.contentDocument.close();
+}, "Custom event listeners are to be removed");
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe")),
+ body = frame.contentDocument.body;
+ t.add_cleanup(() => frame.remove());
+ // Focus on the current window so that the frame's window is blurred.
+ window.focus();
+ assert_false(frame.contentDocument.hasFocus());
+ frame.contentWindow.addEventListener("focus", t.unreached_func("window event listener not removed"));
+ body.onfocus = t.unreached_func("body event listener not removed");
+ frame.contentDocument.open();
+ assert_equals(body.onfocus, null);
+ frame.contentWindow.focus();
+ frame.contentDocument.close();
+}, "Standard event listeners are to be removed from Window");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ const childFrame = frame.contentDocument.querySelector("iframe");
+ const childWin = childFrame.contentWindow;
+ const childDoc = childFrame.contentDocument;
+ const childBody = childDoc.body;
+
+ // Right now childDoc is still fully active.
+
+ frame.onload = t.step_func_done(() => {
+ // Focus on the current window so that the frame's window is blurred.
+ window.focus();
+ // Now childDoc is still active but no longer fully active.
+ childWin.addEventListener("focus", t.unreached_func("window event listener not removed"));
+ childBody.onfocus = t.unreached_func("body event listener not removed");
+
+ childDoc.open();
+ assert_equals(childBody.onfocus, null);
+
+ // Now try to fire the focus event two different ways.
+ childWin.focus();
+ const focusEvent = new FocusEvent("focus");
+ childWin.dispatchEvent(focusEvent);
+ childDoc.close();
+ });
+ frame.src = "/common/blank.html";
+ });
+ frame.src = "resources/page-with-frame.html";
+}, "Standard event listeners are to be removed from Window for an active but not fully active document");
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ const win = frame.contentWindow;
+ const doc = frame.contentDocument;
+ const body = doc.body;
+
+ // Right now the frame is connected and it has an active document.
+ frame.remove();
+
+ win.addEventListener("focus", t.unreached_func("window event listener not removed"));
+ body.onfocus = t.unreached_func("body event listener not removed");
+ doc.open();
+ assert_equals(body.onfocus, null);
+
+ // Now try to fire the focus event two different ways.
+ win.focus();
+ const focusEvent = new FocusEvent("focus");
+ win.dispatchEvent(focusEvent);
+ doc.close();
+}, "Standard event listeners are to be removed from Window for a non-active document that is the associated Document of a Window (frame is removed)");
+
+test(t => {
+ let winHappened = 0;
+ const winListener = t.step_func(() => { winHappened++; });
+ window.addEventListener("focus", winListener);
+ t.add_cleanup(() => { window.removeEventListener("focus", winListener); });
+
+ let bodyHappened = 0;
+ const bodyListener = t.step_func(() => { bodyHappened++; });
+ document.body.onfocus = bodyListener;
+ t.add_cleanup(() => { document.body.onfocus = null; });
+
+ const doc = document.implementation.createHTMLDocument();
+ doc.open();
+
+ const focusEvent = new FocusEvent("focus");
+ window.dispatchEvent(focusEvent);
+
+ assert_equals(winHappened, 1);
+ assert_equals(bodyHappened, 1);
+}, "Standard event listeners are NOT to be removed from Window for a Window-less document (createHTMLDocument)");
+
+test(t => {
+ let winHappened = 0;
+ const winListener = t.step_func(() => { winHappened++; });
+ window.addEventListener("focus", winListener);
+ t.add_cleanup(() => { window.removeEventListener("focus", winListener); });
+
+ let bodyHappened = 0;
+ const bodyListener = t.step_func(() => { bodyHappened++; });
+ document.body.onfocus = bodyListener;
+ t.add_cleanup(() => { document.body.onfocus = null; });
+
+ const doc = new DOMParser().parseFromString("", "text/html");
+ doc.open();
+
+ const focusEvent = new FocusEvent("focus");
+ window.dispatchEvent(focusEvent);
+
+ assert_equals(winHappened, 1);
+ assert_equals(bodyHappened, 1);
+}, "Standard event listeners are NOT to be removed from Window for a Window-less document (DOMParser)");
+
+async_test(t => {
+ const xhr = new XMLHttpRequest();
+ xhr.onload = t.step_func_done(() => {
+ assert_equals(xhr.status, 200);
+ const doc = xhr.responseXML;
+
+ let winHappened = 0;
+ const winListener = t.step_func(() => { winHappened++; });
+ window.addEventListener("focus", winListener);
+ t.add_cleanup(() => { window.removeEventListener("focus", winListener); });
+
+ let bodyHappened = 0;
+ const bodyListener = t.step_func(() => { bodyHappened++; });
+ document.body.onfocus = bodyListener;
+ t.add_cleanup(() => { document.body.onfocus = null; });
+
+ doc.open();
+
+ const focusEvent = new FocusEvent("focus");
+ window.dispatchEvent(focusEvent);
+
+ assert_equals(winHappened, 1);
+ assert_equals(bodyHappened, 1);
+ });
+ xhr.responseType = "document";
+ xhr.open("GET", "resources/dummy.html");
+ xhr.send();
+}, "Standard event listeners are NOT to be removed from Window for a Window-less document (XMLHttpRequest)");
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.contentWindow.addEventListener("x", t.unreached_func("window event listener not removed"));
+ frame.contentDocument.open();
+ frame.contentWindow.dispatchEvent(new Event("x"));
+ frame.contentDocument.close();
+}, "Custom event listeners are to be removed from Window");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ const childFrame = frame.contentDocument.querySelector("iframe");
+ const childDoc = childFrame.contentDocument;
+ const childWin = childFrame.contentWindow;
+
+ // Right now childDoc is still fully active.
+
+ frame.onload = t.step_func_done(() => {
+ // Now childDoc is still active but no longer fully active.
+ childWin.addEventListener("x", t.unreached_func("window event listener not removed"));
+ childDoc.open();
+ childWin.dispatchEvent(new Event("x"));
+ childDoc.close();
+ });
+ frame.src = "/common/blank.html";
+ });
+ frame.src = "resources/page-with-frame.html";
+}, "Custom event listeners are to be removed from Window for an active but not fully active document");
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ const win = frame.contentWindow;
+ const doc = frame.contentDocument;
+
+ // Right now the frame is connected and it has an active document.
+ frame.remove();
+
+ win.addEventListener("x", t.unreached_func("window event listener not removed"));
+ doc.open();
+ win.dispatchEvent(new Event("x"));
+ doc.close();
+}, "Custom event listeners are to be removed from Window for a non-active document that is the associated Document of a Window (frame is removed)");
+
+test(t => {
+ const doc = document.implementation.createHTMLDocument();
+ let happened = false;
+ window.addEventListener("createHTMLDocumentTest", t.step_func(() => { happened = true; }));
+ doc.open();
+ window.dispatchEvent(new Event("createHTMLDocumentTest"));
+ assert_true(happened);
+}, "Custom event listeners are NOT to be removed from Window for a Window-less document (createHTMLDocument)");
+
+test(t => {
+ const doc = new DOMParser().parseFromString("", "text/html");
+ let happened = false;
+ window.addEventListener("DOMParserTest", t.step_func(() => { happened = true; }));
+ doc.open();
+ window.dispatchEvent(new Event("DOMParserTest"));
+ assert_true(happened);
+}, "Custom event listeners are NOT to be removed from Window for a Window-less document (DOMParser)");
+
+async_test(t => {
+ const xhr = new XMLHttpRequest();
+ xhr.onload = t.step_func_done(() => {
+ assert_equals(xhr.status, 200);
+ const doc = xhr.responseXML;
+ let happened = false;
+ window.addEventListener("XHRTest", t.step_func(() => { happened = true; }));
+ doc.open();
+ window.dispatchEvent(new Event("XHRTest"));
+ assert_true(happened);
+ });
+ xhr.responseType = "document";
+ xhr.open("GET", "resources/dummy.html");
+ xhr.send();
+}, "Custom event listeners are NOT to be removed from Window for a Window-less document (XMLHttpRequest)");
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe")),
+ body = frame.contentDocument.body;
+ t.add_cleanup(() => frame.remove());
+ const div = body.appendChild(frame.contentDocument.createElement("div"));
+ div.onclick = t.unreached_func("element event listener not removed");
+ frame.contentDocument.open();
+ assert_equals(div.onclick, null);
+ const e = frame.contentDocument.createEvent("mouseevents")
+ e.initEvent("click", false, false);
+ div.dispatchEvent(e);
+ frame.contentDocument.close();
+}, "IDL attribute event handlers are to be deactivated");
+
+var thrower;
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe")),
+ body = frame.contentDocument.body;
+ t.add_cleanup(() => frame.remove());
+ const div = body.appendChild(frame.contentDocument.createElement("div"));
+ thrower = t.step_func(() => { throw new Error('element event listener not removed'); });
+ div.setAttribute("onclick", "parent.thrower()");
+ assert_not_equals(div.onclick, null);
+ frame.contentDocument.open();
+ assert_equals(div.getAttribute("onclick"), "parent.thrower()");
+ assert_equals(div.onclick, null);
+ const e = frame.contentDocument.createEvent("mouseevents")
+ e.initEvent("click", false, false);
+ div.dispatchEvent(e);
+ frame.contentDocument.close();
+}, "Content attribute event handlers are to be deactivated");
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ let once = false;
+ frame.contentDocument.addEventListener("x", () => {
+ frame.contentDocument.open();
+ once = true;
+ });
+ frame.contentDocument.addEventListener("x", t.unreached_func("second event listener not removed"));
+ frame.contentDocument.dispatchEvent(new Event("x"));
+ assert_true(once);
+ frame.contentDocument.close();
+}, "Event listeners are to be removed with immediate effect");
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe")),
+ shadow = frame.contentDocument.body.attachShadow({ mode: "closed" }),
+ shadowChild = shadow.appendChild(document.createElement("div")),
+ shadowShadow = shadowChild.attachShadow({ mode: "open" }),
+ nodes = [shadow, shadowChild, shadowShadow];
+ t.add_cleanup(() => frame.remove());
+ nodes.forEach(node => {
+ node.addEventListener("x", t.unreached_func(node + "'s event listener not removed"));
+ });
+ frame.contentDocument.open();
+ nodes.forEach(node => {
+ node.dispatchEvent(new Event("x"));
+ });
+ frame.contentDocument.close();
+}, "Event listeners are to be removed from shadow trees as well");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/form-control-state.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/form-control-state.html
new file mode 100644
index 0000000000..7d03a885f0
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/form-control-state.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Writing out a document with form controls with values</title>
+<link rel="author" href="mailto:bzbarsky@mit.edu"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+function asyncHop(t, arg) {
+ return new Promise(res => t.step_timeout(res.bind(null, arg), 0));
+}
+
+function loadPromise(t, iframe) {
+ var p = new Promise(res =>
+ iframe.addEventListener("load", res.bind(null, iframe), { once: true }));
+ // We need to do one trip through the event loop to make sure we're
+ // not still under the load event firing when we start doing our
+ // document.open bits.
+ return p.then(asyncHop.bind(null, t));
+}
+
+async function createIframe(t) {
+ var i = document.createElement("iframe");
+ t.add_cleanup(() => i.remove());
+ var p = loadPromise(t, i);
+ document.body.appendChild(i);
+ return p;
+}
+
+async function replaceIframe(t, i, text) {
+ var p = loadPromise(t, i);
+ var doc = i.contentDocument;
+ doc.open();
+ doc.write(text);
+ doc.close();
+ return p;
+}
+
+promise_test(async function(t) {
+ var i = await createIframe(t);
+ var str = "<textarea>123</textarea>";
+ await replaceIframe(t, i, str);
+ i.contentDocument.querySelector("textarea").value = "abc";
+ await replaceIframe(t, i, str);
+ assert_equals(i.contentDocument.querySelector("textarea").value, "123");
+}, "textarea state");
+
+promise_test(async function(t) {
+ var i = await createIframe(t);
+ var str = "<input value='123'>";
+ await replaceIframe(t, i, str);
+ i.contentDocument.querySelector("input").value = "abc";
+ await replaceIframe(t, i, str);
+ assert_equals(i.contentDocument.querySelector("input").value, "123");
+}, "input state");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history-state.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history-state.window.js
new file mode 100644
index 0000000000..7fb172a141
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history-state.window.js
@@ -0,0 +1,29 @@
+async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => iframe.remove());
+ iframe.src = "/common/blank.html";
+ iframe.onload = t.step_func_done(() => {
+ const win = iframe.contentWindow;
+ const doc = iframe.contentDocument;
+ assert_equals(win.history.state, null);
+ win.history.replaceState("state", "");
+ assert_equals(win.history.state, "state");
+ assert_equals(doc.open(), doc);
+ assert_equals(win.history.state, "state");
+ });
+}, "history.state is kept by document.open()");
+
+async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => iframe.remove());
+ iframe.src = "/common/blank.html";
+ iframe.onload = t.step_func_done(() => {
+ const win = iframe.contentWindow;
+ const doc = iframe.contentDocument;
+ assert_equals(win.history.state, null);
+ win.history.replaceState("state", "");
+ assert_equals(win.history.state, "state");
+ assert_equals(doc.open("", "replace"), doc);
+ assert_equals(win.history.state, "state");
+ });
+}, "history.state is kept by document.open() (with historical replace parameter set)");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history.window.js
new file mode 100644
index 0000000000..0134da24f0
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history.window.js
@@ -0,0 +1,29 @@
+// Historically, document.open() created an entry in the session history so
+// that the original page could be seen by going back. Test that this behavior
+// no longer occurs.
+//
+// This test uses window.open() for variety, as most other tests in this
+// directory use document.open(). An <iframe> would probably work also. We can
+// always add an <iframe>-based test later if it is deemed necessary.
+
+const t = async_test("document.open should not add an entry to the session history");
+
+const frameURL = new URL("resources/history-frame.html", document.URL).href;
+
+let origLength;
+window.onFrameLoaded = t.step_func(() => {
+ window.onFrameLoaded = t.unreached_func("onFrameLoaded should only be called once");
+ assert_equals(win.document.URL, frameURL);
+ assert_true(win.document.body.textContent.includes("Old"));
+ origLength = win.history.length;
+});
+window.onDocumentOpen = t.step_func_done(() => {
+ window.onDocumentOpen = t.unreached_func("onDocumentOpen should only be called once");
+ assert_equals(win.document.URL, frameURL);
+ assert_true(win.document.body.textContent.includes("New"));
+ assert_not_equals(origLength, undefined);
+ assert_equals(win.history.length, origLength);
+});
+
+const win = window.open(frameURL);
+t.add_cleanup(() => win.close());
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/ignore-opens-during-unload.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/ignore-opens-during-unload.window.js
new file mode 100644
index 0000000000..43506a22a4
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/ignore-opens-during-unload.window.js
@@ -0,0 +1,60 @@
+for (const [ev, target] of [
+ ["beforeunload", iframe => iframe.contentWindow],
+ ["pagehide", iframe => iframe.contentWindow],
+ ["unload", iframe => iframe.contentWindow],
+ ["visibilitychange", iframe => iframe.contentDocument],
+]) {
+ async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => iframe.remove());
+ iframe.src = "/common/blank.html";
+ iframe.onload = t.step_func(() => {
+ target(iframe).addEventListener(ev, t.step_func_done(() => {
+ assert_not_equals(iframe.contentDocument.childNodes.length, 0);
+ assert_equals(iframe.contentDocument.open(), iframe.contentDocument);
+ assert_not_equals(iframe.contentDocument.childNodes.length, 0);
+ }));
+ iframe.src = "about:blank";
+ });
+ }, `document.open should bail out when ignore-opens-during-unload is greater than 0 during ${ev} event (in top-level browsing context)`);
+
+ async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => iframe.remove());
+ iframe.src = "/common/blank.html?1";
+ iframe.onload = t.step_func(() => {
+ const doc = iframe.contentDocument;
+ const innerIframe = doc.body.appendChild(doc.createElement("iframe"));
+ innerIframe.src = "/common/blank.html?2";
+ innerIframe.onload = t.step_func(() => {
+ // Navigate the parent, listen on the child, and open() the parent.
+ target(innerIframe).addEventListener(ev, t.step_func_done(() => {
+ assert_not_equals(iframe.contentDocument.childNodes.length, 0);
+ iframe.contentDocument.open();
+ assert_not_equals(iframe.contentDocument.childNodes.length, 0);
+ }));
+ iframe.src = "about:blank";
+ });
+ });
+ }, `document.open should bail out when ignore-opens-during-unload is greater than 0 during ${ev} event (open(parent) while unloading parent and child)`);
+
+ async_test(t => {
+ const iframe = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => iframe.remove());
+ iframe.src = "/common/blank.html?1";
+ iframe.onload = t.step_func(() => {
+ const doc = iframe.contentDocument;
+ const innerIframe = doc.body.appendChild(doc.createElement("iframe"));
+ innerIframe.src = "/common/blank.html?2";
+ innerIframe.onload = t.step_func(() => {
+ // Navigate the child, listen on the child, and open() the parent.
+ target(innerIframe).addEventListener(ev, t.step_func_done(() => {
+ assert_not_equals(iframe.contentDocument.childNodes.length, 0);
+ iframe.contentDocument.open();
+ assert_equals(iframe.contentDocument.childNodes.length, 0);
+ }));
+ innerIframe.src = "about:blank";
+ });
+ });
+ }, `document.open should bail out when ignore-opens-during-unload is greater than 0 during ${ev} event (open(parent) while unloading child only)`);
+}
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/location-set-and-document-open.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/location-set-and-document-open.html
new file mode 100644
index 0000000000..a3bdd86ee6
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/location-set-and-document-open.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+ <script>
+ var t = async_test("Location sets should cancel current navigation and prevent later document.open() from doing anything");
+
+ var finishTest = t.step_func_done(function() {
+ assert_equals(frames[0].document.body.textContent, "PASS",
+ "Should not have FAIL in our textContent");
+ });
+
+ t.step(function() {
+ var i = document.createElement("iframe");
+ i.srcdoc = `
+ <script>
+ var blob = new Blob(["PASS"], { type: "text/html" });
+ var url = URL.createObjectURL(blob);
+ location.href = url;
+ frameElement.onload = parent.finishTest;
+ document.open();
+ document.write("FAIL");
+ document.close();
+ <\/script>`;
+ document.body.appendChild(i);
+ });
+
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-events.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-events.window.js
new file mode 100644
index 0000000000..4efbb863c6
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-events.window.js
@@ -0,0 +1,22 @@
+// In an ideal world this test would eventually be obsolete due to mutation events disappearing. Or
+// would have to change to account for mutation events not firing synchronously. Neither seems
+// realistic to the author though.
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ frame.contentWindow.addEventListener("DOMNodeInserted", t.unreached_func());
+ frame.contentWindow.addEventListener("DOMNodeInserted", t.unreached_func(), true);
+ frame.contentWindow.addEventListener("DOMNodeInsertedIntoDocument", t.unreached_func(), true);
+ frame.contentWindow.addEventListener("DOMNodeRemoved", t.unreached_func());
+ frame.contentWindow.addEventListener("DOMNodeRemoved", t.unreached_func(), true);
+ frame.contentWindow.addEventListener("DOMNodeRemovedFromDocument", t.unreached_func(), true);
+ frame.contentWindow.addEventListener("DOMSubtreeModified", t.unreached_func());
+ frame.contentWindow.addEventListener("DOMSubtreeModified", t.unreached_func(), true);
+ assert_equals(frame.contentDocument.documentElement.localName, "html");
+ assert_equals(frame.contentDocument.open(), frame.contentDocument);
+ assert_equals(frame.contentDocument.documentElement, null);
+ frame.contentDocument.write("<div>heya</div>");
+ frame.contentDocument.close();
+ assert_equals(frame.contentDocument.documentElement.localName, "html");
+ frame.remove();
+}, "document.open(), the HTML parser, and mutation events");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-observer.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-observer.window.js
new file mode 100644
index 0000000000..34e73146a9
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-observer.window.js
@@ -0,0 +1,19 @@
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => { frame.remove(); });
+ const originalHTMLElement = frame.contentDocument.documentElement;
+ assert_equals(originalHTMLElement.localName, "html");
+ const observer = new frame.contentWindow.MutationObserver(t.step_func_done(records => {
+ // Even though we passed `subtree: true` to observer.observe, due to the
+ // fact that "replace all" algorithm removes children with the "suppress
+ // observers flag" set, we still only get the html element as the sole
+ // removed node.
+ assert_equals(records.length, 1);
+ assert_equals(records[0].type, "childList");
+ assert_equals(records[0].target, frame.contentDocument);
+ assert_array_equals(records[0].addedNodes, []);
+ assert_array_equals(records[0].removedNodes, [originalHTMLElement]);
+ }));
+ observer.observe(frame.contentDocument, { childList: true, subtree: true });
+ assert_equals(frame.contentDocument.open(), frame.contentDocument);
+}, "document.open() should inform mutation observer of node removal");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/no-new-global.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/no-new-global.window.js
new file mode 100644
index 0000000000..d4a9296fca
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/no-new-global.window.js
@@ -0,0 +1,57 @@
+// In an earlier version of the HTML Standard, document open steps created a
+// new JavaScript realm and migrated the existing objects to use the new realm.
+// Test that this no longer happens.
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ // Ensure a load event gets dispatched to unblock testharness
+ t.add_cleanup(() => frame.remove());
+ frame.src = "resources/global-variables-frame.html";
+ frame.onload = t.step_func_done(() => {
+ assert_equals(frame.contentWindow.hey, "You", "precondition");
+ frame.contentDocument.open();
+ assert_equals(frame.contentWindow.hey, "You", "actual check");
+ });
+}, "Obtaining a variable from a global whose document had open() invoked");
+
+function testIdentity(desc, frameToObject, frameToConstructor) {
+ async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ // Ensure a load event gets dispatched to unblock testharness
+ t.add_cleanup(() => frame.remove());
+ frame.src = "/common/blank.html";
+ frame.onload = t.step_func_done(() => {
+ const obj = frameToObject(frame);
+ frame.contentDocument.open();
+ assert_equals(frameToObject(frame), obj);
+ });
+ }, `${desc} maintains object identity through open()`);
+
+ async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ // Ensure a load event gets dispatched to unblock testharness
+ t.add_cleanup(() => frame.remove());
+ frame.src = "/common/blank.html";
+ frame.onload = t.step_func_done(() => {
+ const obj = frameToObject(frame);
+ const origProto = Object.getPrototypeOf(obj);
+ const origCtor = frameToConstructor(frame);
+ const sym = Symbol();
+ obj[sym] = "foo";
+ frame.contentDocument.open();
+ assert_equals(frameToObject(frame)[sym], "foo");
+ assert_true(frameToObject(frame) instanceof origCtor);
+ assert_equals(Object.getPrototypeOf(frameToObject(frame)), origProto);
+ assert_equals(frameToConstructor(frame), origCtor);
+ });
+ }, `${desc} maintains its prototype and properties through open()`);
+}
+
+testIdentity("Document", frame => frame.contentDocument, frame => frame.contentWindow.Document);
+testIdentity("WindowProxy", frame => frame.contentWindow, frame => frame.contentWindow.Window);
+testIdentity("BarProp", frame => frame.contentWindow.locationbar, frame => frame.contentWindow.BarProp);
+testIdentity("History", frame => frame.contentWindow.history, frame => frame.contentWindow.History);
+testIdentity("localStorage", frame => frame.contentWindow.localStorage, frame => frame.contentWindow.Storage);
+testIdentity("Location", frame => frame.contentWindow.location, frame => frame.contentWindow.Location);
+testIdentity("sessionStorage", frame => frame.contentWindow.sessionStorage, frame => frame.contentWindow.Storage);
+testIdentity("Navigator", frame => frame.contentWindow.navigator, frame => frame.contentWindow.Navigator);
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-basic.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-basic.html
new file mode 100644
index 0000000000..118be71af1
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-basic.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<title>Origin check in document.open() - Basic usage</title>
+<link rel="author" title="Jochen Eisinger" href="mailto:jochen@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#opening-the-input-stream">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/resources/common.js"></script>
+<body>
+<script>
+testInIFrame(undefined, (ctx) => {
+ try {
+ ctx.iframes[0].contentDocument.open();
+ } catch (e) {
+ assert_unreached("Opening a same origin document throws");
+ }
+}, "It should be possible to open same origin documents.");
+
+testInIFrame(undefined, (ctx) => {
+ try {
+ ctx.iframes[0].contentDocument.write("");
+ } catch (e) {
+ assert_unreached("Implicitly opening a same origin document throws");
+ }
+}, "It should be possible to implicitly open same origin documents.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-same-origin-domain.sub.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-same-origin-domain.sub.html
new file mode 100644
index 0000000000..ba4ef3bae8
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-same-origin-domain.sub.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>Origin check in document.open() - same origin-domain (but not same origin) documents</title>
+<link rel="author" title="Jochen Eisinger" href="mailto:jochen@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#opening-the-input-stream">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/resources/common.js"></script>
+<body>
+<script>
+testInIFrame("http://{{host}}:{{ports[http][1]}}/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/set-document-domain.html", (ctx) => {
+ document.domain = document.domain;
+ let doc = ctx.iframes[0].contentDocument;
+ let constructor = ctx.iframes[0].contentWindow.DOMException;
+ assert_throws_dom("SecurityError", constructor, doc.open.bind(doc), "Opening a same origin-domain (but not same origin) document doesn't throw.");
+}, "It should not be possible to open same origin-domain (but not same origin) documents.");
+
+testInIFrame("http://{{host}}:{{ports[http][1]}}/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/set-document-domain.html", (ctx) => {
+ document.domain = document.domain;
+ let doc = ctx.iframes[0].contentDocument;
+ let constructor = ctx.iframes[0].contentWindow.DOMException;
+ assert_throws_dom("SecurityError", constructor, doc.write.bind(doc, ""), "Implicitly opening a same origin-domain (but not same origin) document doesn't throw.");
+}, "It should not be possible to implicitly open same origin-domain (but not same origin) documents.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/quirks.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/quirks.window.js
new file mode 100644
index 0000000000..0ff0bb9944
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/quirks.window.js
@@ -0,0 +1,74 @@
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.contentDocument.close());
+ assert_equals(frame.contentDocument.compatMode, "BackCompat");
+ frame.contentDocument.open();
+ assert_equals(frame.contentDocument.compatMode, "CSS1Compat");
+ frame.contentDocument.close();
+ assert_equals(frame.contentDocument.compatMode, "BackCompat");
+}, "document.open() sets document to no-quirks mode (write no doctype)");
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.contentDocument.close());
+ assert_equals(frame.contentDocument.compatMode, "BackCompat");
+ frame.contentDocument.open();
+ assert_equals(frame.contentDocument.compatMode, "CSS1Compat");
+ frame.contentDocument.write("<!doctype html public");
+ assert_equals(frame.contentDocument.compatMode, "CSS1Compat");
+ frame.contentDocument.write(" \"-//IETF//DTD HTML 3//\"");
+ assert_equals(frame.contentDocument.compatMode, "CSS1Compat");
+ frame.contentDocument.write(">");
+ assert_equals(frame.contentDocument.compatMode, "BackCompat");
+ frame.contentDocument.close();
+ assert_equals(frame.contentDocument.compatMode, "BackCompat");
+}, "document.open() sets document to no-quirks mode (write old doctype)");
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.contentDocument.close());
+ assert_equals(frame.contentDocument.compatMode, "BackCompat");
+ frame.contentDocument.open();
+ assert_equals(frame.contentDocument.compatMode, "CSS1Compat");
+ frame.contentDocument.write("<!doctype html");
+ assert_equals(frame.contentDocument.compatMode, "CSS1Compat");
+ frame.contentDocument.write(">");
+ assert_equals(frame.contentDocument.compatMode, "CSS1Compat");
+ frame.contentDocument.close();
+ assert_equals(frame.contentDocument.compatMode, "CSS1Compat");
+}, "document.open() sets document to no-quirks mode (write new doctype)");
+
+// This tests the document.open() call in fact sets the document to no-quirks
+// mode, not limited-quirks mode. It is derived from
+// quirks/blocks-ignore-line-height.html in WPT, as there is no direct way to
+// distinguish between a no-quirks document and a limited-quirks document. It
+// assumes that the user agent passes the linked test, which at the time of
+// writing is all major web browsers.
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.contentDocument.close());
+ assert_equals(frame.contentDocument.compatMode, "BackCompat");
+ frame.contentDocument.open();
+ assert_equals(frame.contentDocument.compatMode, "CSS1Compat");
+
+ // Create the DOM tree manually rather than going through document.write() to
+ // bypass the parser, which resets the document mode.
+ const html = frame.contentDocument.appendChild(frame.contentDocument.createElement("html"));
+ const body = html.appendChild(frame.contentDocument.createElement("body"));
+ assert_equals(frame.contentDocument.body, body);
+ body.innerHTML = `
+ <style>#ref { display:block }</style>
+ <div id=test><font size=1>x</font></div>
+ <font id=ref size=1>x</font>
+ <div id=s_ref>x</div>
+ `;
+ assert_equals(frame.contentDocument.compatMode, "CSS1Compat");
+
+ const idTest = frame.contentDocument.getElementById("test");
+ const idRef = frame.contentDocument.getElementById("ref");
+ const idSRef = frame.contentDocument.getElementById("s_ref");
+ assert_equals(frame.contentWindow.getComputedStyle(idTest).height,
+ frame.contentWindow.getComputedStyle(idSRef).height);
+ assert_not_equals(frame.contentWindow.getComputedStyle(idTest).height,
+ frame.contentWindow.getComputedStyle(idRef).height);
+}, "document.open() sets document to no-quirks mode, not limited-quirks mode");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/readiness.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/readiness.window.js
new file mode 100644
index 0000000000..729a958700
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/readiness.window.js
@@ -0,0 +1,25 @@
+// This tests the behavior of dynamic markup insertion APIs with a document's
+// readiness.
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => { frame.remove(); });
+ frame.src = "/common/blank.html";
+ frame.onload = t.step_func_done(() => {
+ const states = [];
+ frame.contentDocument.onreadystatechange = t.step_func(() => {
+ states.push(frame.contentDocument.readyState);
+ });
+ assert_equals(frame.contentDocument.readyState, "complete");
+ assert_array_equals(states, []);
+
+ // When open() is called, it first removes the event listeners and handlers
+ // from all nodes in the DOM tree. Then, after a new parser is created and
+ // initialized, it changes the current document readiness to "loading".
+ // However, because all event listeners are removed, we cannot observe the
+ // readystatechange event fired for "loading" inside open().
+ frame.contentDocument.open();
+ assert_equals(frame.contentDocument.readyState, "loading");
+ assert_array_equals(states, []);
+ });
+}, "document.open() and readiness");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/reload.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/reload.window.js
new file mode 100644
index 0000000000..279020f64d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/reload.window.js
@@ -0,0 +1,71 @@
+// This test tests for the nonexistence of a reload override buffer, which is
+// used in a previous version of the HTML Standard to make reloads of a
+// document.open()'d document load the written-to document rather than doing an
+// actual reload of the document's URL.
+//
+// This test has a somewhat interesting structure compared to the other tests
+// in this directory. It eschews the <iframe> structure used by other tests,
+// since when the child frame is reloaded it would adopt the URL of the test
+// page (the responsible document of the entry settings object), and the spec
+// forbids navigation in nested browsing contexts to the same URL as their
+// parent. To work around that, we use window.open() which does not suffer from
+// that restriction.
+//
+// In any case, this test as the caller of `document.open()` would be used both
+// as the test file and as part of the test file. The `if (window.name !==
+// "opened-dummy-window")` condition controls what role this file plays.
+
+if (window.name !== "opened-dummy-window") {
+ async_test(t => {
+ const testURL = document.URL;
+ const dummyURL = new URL("resources/dummy.html", document.URL).href;
+
+ // 1. Open an auxiliary window.
+ const win = window.open("resources/dummy.html", "opened-dummy-window");
+ t.add_cleanup(() => { win.close(); });
+
+ win.addEventListener("load", t.step_func(() => {
+ // The timeout seems to be necessary for Firefox, which when `load` is
+ // called may still have an active parser.
+ t.step_timeout(() => {
+ const doc = win.document;
+ assert_true(doc.body.textContent.includes("Dummy"), "precondition");
+ assert_equals(doc.URL, dummyURL, "precondition");
+
+ window.onChildLoad = t.step_func(message => {
+ // 3. The dynamically overwritten content will trigger this function,
+ // which puts in place the actual test.
+
+ assert_equals(message, "Written", "script on written page is executed");
+ assert_true(win.document.body.textContent.includes("Content"), "page is written to");
+ assert_equals(win.document.URL, testURL, "postcondition: after document.write()");
+ assert_equals(win.document, doc, "document.open should not change the document object");
+ window.onChildLoad = t.step_func_done(message => {
+ // 6. This function should be called from the if (opener) branch of
+ // this file. It would throw an assertion error if the overwritten
+ // content was executed instead.
+ assert_equals(message, "Done!", "actual test");
+ assert_true(win.document.body.textContent.includes("Back to the test"), "test is reloaded");
+ assert_equals(win.document.URL, testURL, "postcondition: after reload");
+ assert_not_equals(win.document, doc, "reload should change the document object");
+ });
+
+ // 4. Reload the pop-up window. Because of the doc.open() call, this
+ // pop-up window will reload to the same URL as this test itself.
+ win.location.reload();
+ });
+
+ // 2. When it is loaded, dynamically overwrite its content.
+ assert_equals(doc.open(), doc);
+ assert_equals(doc.URL, testURL, "postcondition: after document.open()");
+ doc.write("<p>Content</p><script>opener.onChildLoad('Written');</script>");
+ doc.close();
+ }, 100);
+ }), { once: true });
+ }, "Reloading a document.open()'d page should reload the URL of the entry realm's responsible document");
+} else {
+ document.write("<p>Back to the test</p>");
+ // 5. Since this window is window.open()'d, opener refers to the test window.
+ // Inform the opener that reload succeeded.
+ opener.onChildLoad("Done!");
+}
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/remove-initial-about-blankness.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/remove-initial-about-blankness.window.js
new file mode 100644
index 0000000000..7442bc4925
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/remove-initial-about-blankness.window.js
@@ -0,0 +1,65 @@
+// This tests the issues discussed in https://github.com/whatwg/html/issues/4299
+// and fixed in https://github.com/whatwg/html/pull/6567.
+
+// Note: because browsers do not interoperate on the spec's notion of window reuse (see e.g. https://crbug.com/778318)
+// we pick a specific interoperable test case, which is "currently on initial about:blank, but loading something".
+
+async_test(t => {
+ const iframe = document.createElement("iframe");
+
+ // We can't just leave it at the actual initial about:blank because of the interop issues mentioned above.
+ // So put it in the "currently on initial about:blank, but loading something" state which interoperably does Window
+ // reuse.
+ iframe.src = "/common/blank.html";
+
+ // Create the Window object. It will be for the initial about:blank since the load of /common/blank.html hasn't
+ // completed.
+ document.body.append(iframe);
+
+ // Store a string on that Window object so we can later test if it's reused.
+ iframe.contentWindow.persistedString = "Hello world!";
+
+ // This will reset the initial about:blank-ness. But, it will also cancel any ongoing loads.
+ iframe.contentDocument.open();
+
+ // So, re-start the load of /common/blank.html.
+ iframe.src = "/common/blank.html";
+
+ // When the load finally happens, will it reuse the Window object or not?
+ // Because document.open() resets the initial about:blank-ness, it will *not* reuse the Window object.
+ // The point of the test is to assert that.
+ iframe.addEventListener("load", t.step_func_done(() => {
+ assert_equals(
+ iframe.contentDocument.URL,
+ iframe.src,
+ "Prerequisite check: we are getting the right load event"
+ );
+
+ assert_equals(iframe.contentWindow.persistedString, undefined);
+ }), { once: true });
+}, "document.open() removes the initial about:blank-ness of the document");
+
+// This test is redundant with others in WPT but it's intended to make it clear that document.open() is the
+// distinguishing factor. It does the same exact thing but without document.open() and with the resulting final assert
+// flipped.
+async_test(t => {
+ const iframe = document.createElement("iframe");
+ iframe.src = "/common/blank.html";
+ document.body.append(iframe);
+
+ iframe.contentWindow.persistedString = "Hello world!";
+
+ // NO document.open() call.
+
+ iframe.src = "/common/blank.html";
+
+ iframe.addEventListener("load", t.step_func_done(() => {
+ assert_equals(
+ iframe.contentDocument.URL,
+ iframe.src,
+ "Prerequisite check: we are getting the right load event"
+ );
+
+ assert_equals(iframe.contentWindow.persistedString, "Hello world!");
+ }), { once: true });
+}, "Double-check: without document.open(), Window reuse indeed happens");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-async-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-async-frame.html
new file mode 100644
index 0000000000..d5535630be
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-async-frame.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<p>Text</p>
+<script>
+window.stop();
+parent.step_timeout(() => {
+ document.open();
+ parent.handlers.afterOpenAsync();
+}, 10);
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-frame.html
new file mode 100644
index 0000000000..d9ec23590b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-frame.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<p>Text</p>
+<script>
+window.stop();
+document.open();
+parent.handlers.afterOpen();
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-custom-element-with-domain-frame.sub.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-custom-element-with-domain-frame.sub.html
new file mode 100644
index 0000000000..4de97e8ed1
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-custom-element-with-domain-frame.sub.html
@@ -0,0 +1,13 @@
+<p>Text</p>
+<script>
+document.domain = "{{host}}";
+
+class CustomElement extends HTMLElement {
+ constructor() {
+ super();
+ parent.onCustomElementReady();
+ }
+}
+customElements.define("custom-element", CustomElement);
+</script>
+<custom-element></custom-element>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-frame.html
new file mode 100644
index 0000000000..632b2934ac
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-frame.html
@@ -0,0 +1,4 @@
+<p>Text</p>
+<script>
+parent.testSynchronousScript();
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-with-domain-frame.sub.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-with-domain-frame.sub.html
new file mode 100644
index 0000000000..7ca7b5f44c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-with-domain-frame.sub.html
@@ -0,0 +1,5 @@
+<p>Text</p>
+<script>
+document.domain = "{{host}}";
+parent.testSynchronousScript();
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-domain-frame.sub.xhtml b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-domain-frame.sub.xhtml
new file mode 100644
index 0000000000..b054c0fe3a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-domain-frame.sub.xhtml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head><title>XHTML document with domain set</title></head>
+ <body>
+ <p>Text</p>
+ <script>
+ document.domain = "{{host}}";
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-synchronous-script-frame.xhtml b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-synchronous-script-frame.xhtml
new file mode 100644
index 0000000000..00fc71eccf
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-synchronous-script-frame.xhtml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head><title>XHTML document with hook to run script from a script tag</title></head>
+ <body>
+ <p>Text</p>
+ <script>
+ parent.testSynchronousScript();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/document-open-side-effects.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/document-open-side-effects.js
new file mode 100644
index 0000000000..7cb86dcba0
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/document-open-side-effects.js
@@ -0,0 +1,8 @@
+function assertDocumentIsReadyForSideEffectsTest(doc, description) {
+ assert_not_equals(doc.childNodes.length, 0, `document should not be empty before side effects test (${description})`);
+}
+
+function assertOpenHasNoSideEffects(doc, originalURL, description) {
+ assert_not_equals(doc.childNodes.length, 0, `document nodes should not be cleared (${description})`);
+ assert_equals(doc.URL, originalURL, `The original URL should be kept (${description})`);
+}
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/dummy.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/dummy.html
new file mode 100644
index 0000000000..a092f4e2d7
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/dummy.html
@@ -0,0 +1,2 @@
+<!-- Like /common/blank.html, but with some content in it. -->
+<p>Dummy</p>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/encoding-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/encoding-frame.html
new file mode 100644
index 0000000000..843c3a2c79
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/encoding-frame.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<meta charset=ms932>
+<p>Encoded in Shift_JIS.</p>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/global-variables-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/global-variables-frame.html
new file mode 100644
index 0000000000..0fe189914c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/global-variables-frame.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<script>
+hey = "You";
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/history-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/history-frame.html
new file mode 100644
index 0000000000..2404105b09
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/history-frame.html
@@ -0,0 +1,20 @@
+<script>
+function queueTest() {
+ // The timeout is necessary to avoid the parser still being active when
+ // `document.open()` is called and becoming a no-op.
+ //
+ // We also cannot use setTimeout(..., 0), as the parser is terminated in a
+ // task with DOM manipulation task source while the timeout is run in a task
+ // on the timer task source. The order is therefore not guaranteed. Let's
+ // play it safer and use some actual timeout.
+ setTimeout(() => {
+ document.open();
+ document.write("<p>New content</p>");
+ document.close();
+ opener.onDocumentOpen();
+ }, 200);
+}
+</script>
+<body onload="opener.onFrameLoaded(); queueTest();">
+<p>Old content</p>
+</body>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/http-refresh.py b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/http-refresh.py
new file mode 100644
index 0000000000..161a34b6b5
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/http-refresh.py
@@ -0,0 +1,5 @@
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ time = isomorphic_encode(request.url_parts.query) if request.url_parts.query else b'0'
+ return 200, [(b'Refresh', time), (b'Content-Type', b"text/html")], b''
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/meta-refresh.py b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/meta-refresh.py
new file mode 100644
index 0000000000..2dfbab6e76
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/meta-refresh.py
@@ -0,0 +1,3 @@
+def main(request, response):
+ time = request.url_parts.query if request.url_parts.query else u'0'
+ return 200, [[b'Content-Type', b'text/html']], u'<meta http-equiv=refresh content=%s>' % time
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/page-with-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/page-with-frame.html
new file mode 100644
index 0000000000..a1ab01e072
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/page-with-frame.html
@@ -0,0 +1 @@
+<iframe src="/common/blank.html"></iframe>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/set-document-domain.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/set-document-domain.html
new file mode 100644
index 0000000000..a92a7ae39f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/set-document-domain.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<script>
+document.domain = document.domain;
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/slow-png.py b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/slow-png.py
new file mode 100644
index 0000000000..fced22aa26
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/slow-png.py
@@ -0,0 +1,8 @@
+import time
+from base64 import decodebytes
+
+png_response = decodebytes(b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==')
+
+def main(request, response):
+ time.sleep(2)
+ return 200, [], png_response
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-incumbent-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-incumbent-frame.html
new file mode 100644
index 0000000000..bd78d8ee52
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-incumbent-frame.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<script>
+window.callDocumentMethod = methodName => document[methodName]();
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-timer-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-timer-frame.html
new file mode 100644
index 0000000000..b2c050768c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-timer-frame.html
@@ -0,0 +1,3 @@
+<script>
+setTimeout(parent.timerTest, 10);
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-frame.html
new file mode 100644
index 0000000000..be483ff0ae
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-frame.html
@@ -0,0 +1,9 @@
+<script>
+onload = () => {
+ const beforeURL = document.URL;
+ document.open();
+ const afterURL = document.URL;
+ document.close();
+ parent.testDone(beforeURL, afterURL);
+}
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/tasks.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/tasks.window.js
new file mode 100644
index 0000000000..887adcb739
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/tasks.window.js
@@ -0,0 +1,106 @@
+// An older version of the HTML Standard mandated that document.open() remove
+// all tasks associated with the document on which open() is called. This step
+// has been proposed to be removed. This series of tests ensures that this step
+// is no longer executed.
+//
+// This file comprehensively (but not exhaustively) tests for many queued tasks
+// that may be observable. Each taskTest() call in fact runs two tests: the
+// first one "tasks without document.open()" does not actually run
+// document.open(), just to test that the tested task works ordinarily; the
+// second actually calls document.open() to test if the method call removes
+// that specific task from the queue.
+
+// This is necessary to allow the promise rejection test below.
+setup({
+ allow_uncaught_exception: true
+});
+
+function taskTest(description, testBody) {
+ async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ // The empty HTML seems to be necessary to cajole Chrome and Safari into
+ // firing a load event asynchronously, which is necessary to make sure the
+ // frame's document doesn't have a parser associated with it.
+ // See: https://crbug.com/569511
+ frame.src = "/common/blank.html";
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ // Make sure there is no parser. Firefox seems to have an additional
+ // non-spec-compliant readiness state "uninitialized", so test for the
+ // two known valid readiness states instead.
+ // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1191683
+ assert_in_array(frame.contentDocument.readyState, ["interactive", "complete"]);
+ testBody(t, frame, doc => {});
+ });
+ }, `tasks without document.open() (${description})`);
+
+ async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ // The empty HTML seems to be necessary to cajole Chrome into firing a load
+ // event, which is necessary to make sure the frame's document doesn't have
+ // a parser associated with it.
+ // See: https://crbug.com/569511
+ frame.src = "/common/blank.html";
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ // Make sure there is no parser. Firefox seems to have an additional
+ // non-spec-compliant readiness state "uninitialized", so test for the
+ // two known valid readiness states instead.
+ // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1191683
+ assert_in_array(frame.contentDocument.readyState, ["interactive", "complete"]);
+ testBody(t, frame, doc => doc.open());
+ });
+ }, `document.open() and tasks (${description})`);
+}
+
+taskTest("timeout", (t, frame, open) => {
+ frame.contentWindow.setTimeout(t.step_func_done(), 100);
+ open(frame.contentDocument);
+});
+
+taskTest("window message", (t, frame, open) => {
+ let counter = 0;
+ frame.contentWindow.postMessage(undefined, "*");
+ open(frame.contentDocument);
+ frame.contentWindow.postMessage(undefined, "*");
+ frame.contentWindow.onmessage = t.step_func(e => {
+ assert_equals(e.data, undefined);
+ counter++;
+ assert_less_than_equal(counter, 2);
+ if (counter == 2) {
+ t.done();
+ }
+ });
+});
+
+taskTest("canvas.toBlob()", (t, frame, open) => {
+ const canvas = frame.contentDocument.body.appendChild(frame.contentDocument.createElement("canvas"));
+ canvas.toBlob(t.step_func_done());
+ open(frame.contentDocument);
+});
+
+taskTest("MessagePort", (t, frame, open) => {
+ frame.contentWindow.eval(`({ port1, port2 } = new MessageChannel());`);
+ frame.contentWindow.port2.onmessage = t.step_func_done(ev => {
+ assert_equals(ev.data, "Hello world");
+ });
+ frame.contentWindow.port1.postMessage("Hello world");
+ open(frame.contentDocument);
+});
+
+taskTest("Promise rejection", (t, frame, open) => {
+ // There is currently some ambiguity on which Window object the
+ // unhandledrejection event should be fired on. Here, let's account for that
+ // ambiguity and allow event fired on _any_ global to pass this test.
+ // See:
+ // - https://github.com/whatwg/html/issues/958,
+ // - https://bugs.webkit.org/show_bug.cgi?id=187822
+ const promise = frame.contentWindow.eval("Promise.reject(42);");
+ open(frame.contentDocument);
+ const listener = t.step_func_done(ev => {
+ assert_equals(ev.promise, promise);
+ assert_equals(ev.reason, 42);
+ });
+ frame.contentWindow.onunhandledrejection = listener;
+ window.onunhandledrejection = listener;
+});
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext-subframe.txt b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext-subframe.txt
new file mode 100644
index 0000000000..3e715502b9
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext-subframe.txt
@@ -0,0 +1 @@
+Some text.
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext.window.js
new file mode 100644
index 0000000000..ab1d9706a4
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext.window.js
@@ -0,0 +1,23 @@
+["replace",
+ "NOBODY",
+ "@ FD ;",
+ "it does not matter, you see \f",
+ "text/plain",
+ "text/xml",
+ "application/octet-stream",
+ "\0"].forEach(type => {
+ async_test(t => {
+ const frame = document.createElement("iframe");
+ frame.src = "type-argument-plaintext-subframe.txt";
+ document.body.appendChild(frame);
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func_done(() => {
+ assert_equals(frame.contentDocument.open(type), frame.contentDocument);
+ frame.contentDocument.write("<B>heya</b>");
+ frame.contentDocument.close();
+ assert_equals(frame.contentDocument.body.firstChild.localName, "b");
+ assert_equals(frame.contentDocument.body.textContent, "heya");
+ assert_equals(frame.contentDocument.contentType, "text/plain");
+ });
+ }, "document.open() on plaintext document with type set to: " + type + " (type argument is supposed to be ignored)");
+});
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument.window.js
new file mode 100644
index 0000000000..9174008da3
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument.window.js
@@ -0,0 +1,20 @@
+["replace",
+ "NOBODY",
+ "@ FD ;",
+ "it does not matter, you see \f",
+ "text/plain",
+ "text/xml",
+ "application/octet-stream",
+ "\0"].forEach(type => {
+ async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ assert_equals(frame.contentDocument.open(type), frame.contentDocument);
+ frame.contentDocument.write("<B>heya</b>");
+ frame.contentDocument.close();
+ assert_equals(frame.contentDocument.body.firstChild.localName, "b");
+ assert_equals(frame.contentDocument.body.textContent, "heya");
+ assert_equals(frame.contentDocument.contentType, "text/html");
+ t.done();
+ }, "document.open() with type set to: " + type + " (type argument is supposed to be ignored)");
+});
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/unload.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/unload.window.js
new file mode 100644
index 0000000000..e275a4987a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/unload.window.js
@@ -0,0 +1,19 @@
+// In an earlier version of the HTML Standard, document open steps had "unload
+// document" as a step. Test that this no longer happens.
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.src = "/common/blank.html";
+ frame.onload = t.step_func(() => {
+ frame.contentWindow.onpagehide = t.unreached_func("onpagehide got called");
+ frame.contentDocument.onvisibilitychange = t.unreached_func("onvisibilitychange got called");
+ frame.contentWindow.onunload = t.unreached_func("onunload got called");
+ frame.contentDocument.open();
+ t.step_timeout(t.step_func_done(() => {
+ // If none of the three events have been fired by this point, we consider
+ // the test a success. `frame.remove()` above will allow the `load` event
+ // to be fired on the top-level Window, thus unblocking testharness.
+ }), 500);
+ });
+}, "document.open(): Do not fire pagehide, visibilitychange, or unload events");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document-sync-call.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document-sync-call.window.js
new file mode 100644
index 0000000000..f20b4341e3
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document-sync-call.window.js
@@ -0,0 +1,13 @@
+for (const methodName of ["open", "write", "writeln"]) {
+ async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => { frame.remove(); });
+ const frameURL = new URL("resources/url-entry-document-incumbent-frame.html", document.URL).href;
+ frame.onload = t.step_func_done(() => {
+ assert_equals(frame.contentDocument.URL, frameURL);
+ frame.contentWindow.callDocumentMethod(methodName);
+ assert_equals(frame.contentDocument.URL, document.URL);
+ });
+ frame.src = frameURL;
+ }, `document.${methodName}() changes document's URL to the entry global object's associate document's (sync call)`);
+}
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document.window.js
new file mode 100644
index 0000000000..c3a1c3a874
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document.window.js
@@ -0,0 +1,18 @@
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ const frameURL = new URL("resources/url-entry-document-timer-frame.html", document.URL).href;
+ window.timerTest = t.step_func_done(() => {
+ assert_equals(frame.contentDocument.URL, frameURL);
+ assert_equals(frame.contentWindow.location.href, frameURL);
+
+ // In this case, the entry settings object was set when this function is
+ // executed in the timer task through Web IDL's "invoke a callback
+ // function" algorithm, to be the relevant settings object of this
+ // function. Therefore the URL of this document would be inherited.
+ assert_equals(frame.contentDocument.open(), frame.contentDocument);
+ assert_equals(frame.contentDocument.URL, document.URL);
+ assert_equals(frame.contentWindow.location.href, document.URL);
+ });
+ frame.src = frameURL;
+}, "document.open() changes document's URL to the entry settings object's responsible document's (through timeouts)");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-fragment.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-fragment.window.js
new file mode 100644
index 0000000000..0c528935b5
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-fragment.window.js
@@ -0,0 +1,26 @@
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe")),
+ urlSansHash = document.URL;
+ t.add_cleanup(() => { frame.remove(); });
+ assert_equals(frame.contentDocument.URL, "about:blank");
+ assert_equals(frame.contentWindow.location.href, "about:blank");
+ self.onhashchange = t.step_func_done(() => {
+ frame.contentDocument.open();
+ assert_equals(frame.contentDocument.URL, urlSansHash);
+ assert_equals(frame.contentWindow.location.href, urlSansHash);
+ });
+ self.location.hash = "heya";
+}, "document.open() and document's URL containing a fragment (entry is not relevant)");
+
+window.testDone = undefined;
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"))
+ t.add_cleanup(() => { frame.remove(); });
+ frame.src = "resources/url-frame.html#heya";
+ window.testDone = t.step_func_done((beforeURL, afterURL) => {
+ assert_equals(beforeURL, frame.src);
+ assert_equals(afterURL, frame.src);
+ assert_equals(frame.contentDocument.URL, frame.src);
+ assert_equals(frame.contentWindow.location.href, frame.src);
+ });
+}, "document.open() and document's URL containing a fragment (entry is relevant)");
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url.window.js
new file mode 100644
index 0000000000..4e7c649f45
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url.window.js
@@ -0,0 +1,93 @@
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ assert_equals(frame.contentDocument.URL, "about:blank");
+ assert_equals(frame.contentWindow.location.href, "about:blank");
+ assert_equals(frame.contentDocument.open(), frame.contentDocument);
+ assert_equals(frame.contentDocument.URL, document.URL);
+ assert_equals(frame.contentWindow.location.href, document.URL);
+}, "document.open() changes document's URL (fully active document)");
+
+async_test(t => {
+ const blankURL = new URL("/common/blank.html", document.URL).href;
+ const frameURL = new URL("resources/page-with-frame.html", document.URL).href;
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ assert_equals(frame.contentDocument.URL, frameURL);
+ assert_equals(frame.contentWindow.location.href, frameURL);
+ const childFrame = frame.contentDocument.querySelector("iframe");
+ const childDoc = childFrame.contentDocument;
+ const childWin = childFrame.contentWindow;
+ assert_equals(childDoc.URL, blankURL);
+ assert_equals(childWin.location.href, blankURL);
+
+ // Right now childDoc is still fully active.
+
+ frame.onload = t.step_func_done(() => {
+ // Now childDoc is still active but no longer fully active.
+ assert_equals(childDoc.open(), childDoc);
+ assert_equals(childDoc.URL, blankURL);
+ assert_equals(childWin.location.href, blankURL);
+ });
+ frame.src = "/common/blank.html";
+ });
+ frame.src = frameURL;
+}, "document.open() does not change document's URL (active but not fully active document)");
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ const doc = frame.contentDocument;
+
+ // We do not test for win.location.href in this test due to
+ // https://github.com/whatwg/html/issues/3959.
+
+ // Right now the frame is connected and it has an active document.
+ assert_equals(doc.URL, "about:blank");
+
+ frame.remove();
+
+ // Now the frame is no longer connected. Its document is no longer active.
+ assert_equals(doc.URL, "about:blank");
+ assert_equals(doc.open(), doc);
+ assert_equals(doc.URL, "about:blank");
+}, "document.open() does not change document's URL (non-active document with an associated Window object; frame is removed)");
+
+async_test(t => {
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+
+ // We do not test for win.location.href in this test due to
+ // https://github.com/whatwg/html/issues/3959.
+
+ frame.onload = t.step_func(() => {
+ const doc = frame.contentDocument;
+ // Right now the frame is connected and it has an active document.
+ assert_equals(doc.URL, "about:blank");
+
+ frame.onload = t.step_func_done(() => {
+ // Now even though the frame is still connected, its document is no
+ // longer active.
+ assert_not_equals(frame.contentDocument, doc);
+ assert_equals(doc.URL, "about:blank");
+ assert_equals(doc.open(), doc);
+ assert_equals(doc.URL, "about:blank");
+ });
+
+ frame.src = "/common/blank.html";
+ });
+
+ // We need to connect the frame after the load event is set up to mitigate
+ // against https://crbug.com/569511.
+ document.body.appendChild(frame);
+}, "document.open() does not change document's URL (non-active document with an associated Window object; navigated away)");
+
+test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ const doc = frame.contentDocument.implementation.createHTMLDocument();
+ assert_equals(doc.URL, "about:blank");
+ assert_equals(doc.open(), doc);
+ assert_equals(doc.URL, "about:blank");
+}, "document.open() does not change document's URL (non-active document without an associated Window object)");
diff --git a/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-cross-realm-callback-report-exception.html b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-cross-realm-callback-report-exception.html
new file mode 100644
index 0000000000..fa153f8f96
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-cross-realm-callback-report-exception.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>queueMicrotask() 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></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(() => {
+ frames[0].queueMicrotask(new frames[1].Function(`throw new parent.frames[2].Error("PASS");`));
+
+ t.step_timeout(() => {
+ assert_array_equals(onerrorCalls, ["frame1"]);
+ t.done();
+ }, 4);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-exceptions.any.js b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-exceptions.any.js
new file mode 100644
index 0000000000..01f32ac9ba
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-exceptions.any.js
@@ -0,0 +1,15 @@
+// META: global=window,worker
+"use strict";
+
+setup({
+ allow_uncaught_exception: true
+});
+
+async_test(t => {
+ const error = new Error("boo");
+ self.addEventListener("error", t.step_func_done(ev => {
+ assert_equals(ev.error, error);
+ }));
+
+ queueMicrotask(() => { throw error; });
+}, "It rethrows exceptions");
diff --git a/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.any.js b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.any.js
new file mode 100644
index 0000000000..e67765fade
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.any.js
@@ -0,0 +1,39 @@
+// META: global=window,worker
+"use strict";
+
+test(() => {
+ assert_equals(typeof queueMicrotask, "function");
+}, "It exists and is a function");
+
+test(() => {
+ assert_throws_js(TypeError, () => queueMicrotask(), "no argument");
+ assert_throws_js(TypeError, () => queueMicrotask(undefined), "undefined");
+ assert_throws_js(TypeError, () => queueMicrotask(null), "null");
+ assert_throws_js(TypeError, () => queueMicrotask(0), "0");
+ assert_throws_js(TypeError, () => queueMicrotask({ handleEvent() { } }), "an event handler object");
+ assert_throws_js(TypeError, () => queueMicrotask("window.x = 5;"), "a string");
+}, "It throws when given non-functions");
+
+async_test(t => {
+ let called = false;
+ queueMicrotask(t.step_func_done(() => {
+ called = true;
+ }));
+ assert_false(called);
+}, "It calls the callback asynchronously");
+
+async_test(t => {
+ queueMicrotask(t.step_func_done(function () { // note: intentionally not an arrow function
+ assert_array_equals(arguments, []);
+ }), "x", "y");
+}, "It does not pass any arguments");
+
+async_test(t => {
+ const happenings = [];
+ Promise.resolve().then(() => happenings.push("a"));
+ queueMicrotask(() => happenings.push("b"));
+ Promise.reject().catch(() => happenings.push("c"));
+ queueMicrotask(t.step_func_done(() => {
+ assert_array_equals(happenings, ["a", "b", "c"]);
+ }));
+}, "It interleaves with promises as expected");
diff --git a/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.window.js b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.window.js
new file mode 100644
index 0000000000..78cdcfc5d9
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.window.js
@@ -0,0 +1,45 @@
+"use strict";
+
+// This does not work as you expect because mutation observer compound microtasks are confusing.
+// Basically you can only use it once per test.
+function queueMicrotaskViaMO(cb) {
+ const observer = new MutationObserver(cb);
+ const node = document.createTextNode("");
+ observer.observe(node, { characterData: true });
+ node.data = "foo";
+}
+
+// Need to use promise_test to get sequential ordering; otherwise the global mutation observer
+// compound microtask business screws us over.
+
+promise_test(() => {
+ return new Promise(resolve => {
+ const happenings = [];
+
+ queueMicrotaskViaMO(() => happenings.push("x"));
+ queueMicrotask(() => happenings.push("a"));
+
+ queueMicrotask(() => {
+ assert_array_equals(happenings, ["x", "a"]);
+ resolve();
+ });
+ });
+}, "It interleaves with MutationObservers as expected");
+
+promise_test(() => {
+ return new Promise(resolve => {
+ const happenings = [];
+
+ queueMicrotask(() => happenings.push("a"));
+ Promise.reject().catch(() => happenings.push("x"));
+ queueMicrotaskViaMO(() => happenings.push(1));
+ Promise.resolve().then(() => happenings.push("y"));
+ queueMicrotask(() => happenings.push("b"));
+ queueMicrotask(() => happenings.push("c"));
+
+ queueMicrotask(() => {
+ assert_array_equals(happenings, ["a", "x", 1, "y", "b", "c"]);
+ resolve();
+ });
+ });
+}, "It interleaves with MutationObservers and promises together as expected");
diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/fully_active_document.window.js b/testing/web-platform/tests/html/webappapis/scripting/event-loops/fully_active_document.window.js
new file mode 100644
index 0000000000..950a8a29ee
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/fully_active_document.window.js
@@ -0,0 +1,29 @@
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+
+ frame.onload = t.step_func(() => {
+ // Right now the doc of the iframe inside "frame" is still "fully-active".
+ // Navigate parent away, making the child iframe's doc "active", not "fully-active".
+ frame.contentWindow.location = "/common/blank.html";
+
+ frame.onload = t.step_func(() => {
+ // The child iframe's doc is "active", not "fully-active", and should not receive the storage notification.
+ sessionStorage.setItem('myCat', 'Tom');
+ t.step_timeout(() => {
+ // The child iframe's hasn't received the storage notification.
+ assert_equals(sessionStorage.getItem("Received storage event"), null);
+ frame.contentWindow.history.go(-1);
+ t.step_timeout(() => {
+ // Now The child iframe's doc is "fully-active" again,
+ // the previously not run storage task should now have been run.
+ assert_equals(sessionStorage.getItem("Received storage event"), "true");
+ t.done();
+ }, 1000);
+ }, 1000);
+ });
+ });
+
+ frame.src = "resources/page-with-frame.html";
+}, "Tasks for documents that are not fully active are stored, and run when the documents becomes fully-active");
+
diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_raf.html b/testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_raf.html
new file mode 100644
index 0000000000..824dbc4b92
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_raf.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<head>
+<link rel=author title="Aleks Totic" href="mailto:atotic@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/#clean-up-after-running-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/common.js"></script>
+</head>
+<body style="height:2000px;">
+<script>
+/*
+promise 1, promise 2 execute immediately after rAF
+promise 1 child executes immediately after promise 2.
+
+Relevant specs:
+
+https://html.spec.whatwg.org/#clean-up-after-running-script
+If the JavaScript execution context stack is now empty, perform a microtask checkpoint.
+
+https://html.spec.whatwg.org/#perform-a-microtask-checkpoint
+"perform a microtask checkpoint" runs in a loop until all microtasks have been delivered.
+*/
+
+var test = async_test("Microtask execute immediately after script");
+
+window.requestAnimationFrame( function() {
+ var events = [];
+
+ Promise.resolve()
+ .then(function() {
+ events.push("promise 1");
+ return Promise.resolve();
+ })
+ .then(function() {
+ test.step(function() {
+ events.push("promise 1 child");
+ assert_array_equals(events, ["promise 1", "promise 2", "promise 1 child"]);
+ test.done();
+ });
+ });
+ Promise.resolve()
+ .then(function() {
+ events.push("promise 2");
+ });
+
+ // Set up events that must be executed after Promise.
+ window.setTimeout(function() {
+ events.push('timeout');
+ }, 0);
+ window.addEventListener('scroll', function() {
+ events.push('scroll');
+ });
+ window.scrollBy(0,10);
+
+});
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_script.html b/testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_script.html
new file mode 100644
index 0000000000..799a0de605
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_script.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<head>
+<link rel=author title="Aleks Totic" href="mailto:atotic@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/#clean-up-after-running-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/common.js"></script>
+</head>
+<body style="height:2000px;">
+<script>
+/*
+promise 1, promise 2 execute immediately after script tag
+promise 1 child executes immediately after promise 2.
+
+Relevant specs:
+
+https://html.spec.whatwg.org/#clean-up-after-running-script
+If the JavaScript execution context stack is now empty, perform a microtask checkpoint.
+
+https://html.spec.whatwg.org/#perform-a-microtask-checkpoint
+"perform a microtask checkpoint" runs in a loop until all microtasks have been delivered.
+*/
+
+var test = async_test("Microtask immediately after script");
+
+var events = [];
+
+Promise.resolve()
+.then(function() {
+ events.push("promise 1");
+ return Promise.resolve();
+})
+.then(function() {
+ test.step(function() {
+ events.push("promise 1 child");
+ assert_array_equals(events, ["promise 1", "promise 2", "promise 1 child"]);
+ test.done();
+ });
+});
+Promise.resolve()
+.then(function() {
+ events.push("promise 2");
+});
+
+// Set up events that must be executed after Promise.
+window.setTimeout(function() {
+ events.push('timeout');
+}, 0);
+window.addEventListener('scroll', function() {
+ events.push('scroll');
+});
+window.scrollBy(0,10);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/common.js b/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/common.js
new file mode 100644
index 0000000000..e2279f93dd
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/common.js
@@ -0,0 +1,20 @@
+// Helper for tests that just want to verify the ordering of a series of events.
+// Usage:
+// log_test(function(t, log) {
+// log('first');
+// log('second');
+// }, ['first', 'second'], 'Ordinal numbers are ordinal');
+
+function log_test(func, expected, description) {
+ async_test(function(t) {
+ var actual = [];
+ function log(entry) {
+ actual.push(entry);
+ if (expected.length <= actual.length) {
+ assert_array_equals(actual, expected);
+ t.done();
+ }
+ }
+ func(t, t.step_func(log));
+ }, description);
+}
diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/iframe.html b/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/iframe.html
new file mode 100644
index 0000000000..32e4862360
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/iframe.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>Childframe</title>
+<script>
+ window.addEventListener('storage', () => {
+ sessionStorage.setItem("Received storage event", true);
+ });
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/page-with-frame.html b/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/page-with-frame.html
new file mode 100644
index 0000000000..f13170576e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/page-with-frame.html
@@ -0,0 +1 @@
+<iframe src="iframe.html"></iframe>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering-manual.html b/testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering-manual.html
new file mode 100644
index 0000000000..ed2f70e196
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering-manual.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<title>Task and Microtask Ordering </title>
+<link rel=author title="Joshua Bell" href="mailto:jsbell@google.com">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#event-loops">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/common.js"></script>
+<style>
+.inner { padding: 46px; width: 0; margin: 0 auto; background: #d4d4d4; }
+.outer { padding: 25px; width: 92px; background: #f1f1f1; }
+</style>
+
+<p>Click on the inner box:</p>
+<div class="outer">
+ <div class="inner"></div>
+</div>
+
+<script>
+
+// Based on: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
+
+log_test(function(t, log) {
+ // Let's get hold of those elements
+ var outer = document.querySelector('.outer');
+ var inner = document.querySelector('.inner');
+
+ // Let's listen for attribute changes on the
+ // outer element
+ new MutationObserver(function() {
+ log('mutate');
+ }).observe(outer, {
+ attributes: true
+ });
+
+ // Here's a click listener...
+ function onClick() {
+ log('click');
+
+ setTimeout(function() {
+ log('timeout');
+ }, 0);
+
+ Promise.resolve().then(function() {
+ log('promise');
+ });
+
+ outer.setAttribute('data-random', Math.random());
+ }
+
+ // ...which we'll attach to both elements
+ inner.addEventListener('click', onClick);
+ outer.addEventListener('click', onClick);
+}, [
+ 'click',
+ 'promise',
+ 'mutate',
+ 'click',
+ 'promise',
+ 'mutate',
+ 'timeout',
+ 'timeout'
+], 'Level 1 bossfight (manual click)');
+
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering.html b/testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering.html
new file mode 100644
index 0000000000..c14a043b6a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<title>Task and Microtask Ordering </title>
+<link rel=author title="Joshua Bell" href="mailto:jsbell@google.com">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#event-loops">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/common.js"></script>
+
+<div class="outer">
+ <div class="inner"></div>
+</div>
+
+<script>
+
+// Based on: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
+
+log_test(function(t, log) {
+ log('script start');
+
+ setTimeout(function() {
+ log('setTimeout');
+ }, 0);
+
+ Promise.resolve().then(function() {
+ log('promise1');
+ }).then(function() {
+ log('promise2');
+ });
+
+ log('script end');
+}, [
+ 'script start',
+ 'script end',
+ 'promise1',
+ 'promise2',
+ 'setTimeout'
+], 'Basic task and microtask ordering');
+
+log_test(function(t, log) {
+ // Let's get hold of those elements
+ var outer = document.querySelector('.outer');
+ var inner = document.querySelector('.inner');
+
+ // Let's listen for attribute changes on the
+ // outer element
+ new MutationObserver(function() {
+ log('mutate');
+ }).observe(outer, {
+ attributes: true
+ });
+
+ // Here's a click listener...
+ function onClick() {
+ log('click');
+
+ setTimeout(function() {
+ log('timeout');
+ }, 0);
+
+ Promise.resolve().then(function() {
+ log('promise');
+ });
+
+ outer.setAttribute('data-random', Math.random());
+ }
+
+ // ...which we'll attach to both elements
+ inner.addEventListener('click', onClick);
+ outer.addEventListener('click', onClick);
+
+ // Note that this will behave differently than a real click,
+ // since the dispatch is synchronous and microtasks will not
+ // run between event bubbling steps.
+ inner.click();
+}, [
+ 'click',
+ 'click',
+ 'promise',
+ 'mutate',
+ 'promise',
+ 'timeout',
+ 'timeout'
+], 'Level 1 bossfight (synthetic click)');
+
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/body-onload.html b/testing/web-platform/tests/html/webappapis/scripting/events/body-onload.html
new file mode 100644
index 0000000000..1e43d1ccd4
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/body-onload.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>HTMLBodyElement.onload</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#handler-window-onload">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var t = async_test("body.onload should set the window.onload handler")
+window.onload = t.step_func(function() {
+ assert_unreached("This handler should be overwritten.")
+})
+var b = document.createElement("body")
+b.onload = t.step_func(function(e) {
+ assert_equals(e.currentTarget, window,
+ "The event should be fired at the window.")
+ t.done()
+})
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes-form-owner.html b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes-form-owner.html
new file mode 100644
index 0000000000..e31bd2496a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes-form-owner.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Form's lexical scope is established only for form-associated elements</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#form-associated-element">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<form id="form">
+ <input onclick="window.inputOnClickElements = elements;">
+ <img onclick="window.imgOnClickElements = elements;" alt="img">
+ <div onclick="window.divOnClickElements = elements;">div</div>
+ <x-foo onclick="window.xFooOnClickElements = elements;">x-foo</x-foo>
+</form>
+
+<script>
+"use strict";
+
+window.elements = "global_elements";
+
+test(() => {
+ const input = form.querySelector("input");
+ input.click();
+ assert_equals(window.inputOnClickElements, form.elements);
+}, "<input> has a form owner");
+
+test(() => {
+ const img = form.querySelector("img");
+ img.click();
+ assert_equals(window.imgOnClickElements, form.elements);
+}, "<img> has a form owner");
+
+test(() => {
+ const div = form.querySelector("div");
+ div.click();
+ assert_equals(window.divOnClickElements, window.elements);
+}, "<div> doesn't have a form owner");
+
+test(() => {
+ customElements.define("x-foo", class extends HTMLElement {
+ static formAssociated = true;
+ });
+
+ const xFoo = form.querySelector("x-foo");
+ xFoo.click();
+ assert_equals(window.xFooOnClickElements, form.elements);
+}, "form-associated <x-foo> has a form owner");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes.html b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes.html
new file mode 100644
index 0000000000..ed6c006651
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes.html
@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Lexical scopes when compiling an inline event handler</title>
+<link rel="help" href="https://html.spec.whatwg.org/C/#getting-the-current-value-of-the-event-handler">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+setup({allow_uncaught_exception: true});
+</script>
+
+<!-- test case 1: element, document, and window -->
+
+<table>
+ <tr id="test1_outer">
+ <td id="test1_target" onclick="
+window.testResults.complete = typeof(complete);
+window.testResults.cellIndex = typeof(cellIndex);
+window.testResults.cells = typeof(cells);
+window.testResults.domain = typeof(domain);
+window.testResults.print = typeof(print);
+window.testResults.testResults = typeof(testResults);
+window.testResults.target_own_property = typeof(target_own_property);
+window.testResults.inner_own_property = typeof(inner_own_property);
+window.testResults.outer_own_property = typeof(outer_own_property);
+window.testResults.event = typeof(event);
+">
+ <img id="test1_inner">
+ </td>
+ </tr>
+</table>
+
+<script>
+"use strict";
+
+test(() => {
+ const target_element = document.getElementById("test1_target");
+ const inner_element = document.getElementById("test1_inner");
+ const outer_element = document.getElementById("test1_outer");
+
+ target_element.target_own_property = {};
+ inner_element.inner_own_property = {};
+ outer_element.outer_own_property = {};
+
+ const results = window.testResults = {};
+ // Clicking an inner element makes the event target where the event handler is
+ // registered doesn't match the event target that the event is fired. From a
+ // point of view |target_element|, event.target != event.currentTarget.
+ inner_element.click();
+ // Expected scopes are: |target_element|, document, and window.
+ assert_equals(results.complete, "undefined", "HTMLImageElement.prototype.complete");
+ assert_equals(results.cellIndex, "number", "HTMLTableCellElement.prototype.cellIndex");
+ assert_equals(results.cells, "undefined", "HTMLTableRowElement.prototype.cellIndex");
+ assert_equals(results.domain, "string", "Document.prototype.domain");
+ assert_equals(results.print, "function", "window.print");
+ assert_equals(results.testResults, "object");
+ assert_equals(results.target_own_property, "object");
+ assert_equals(results.inner_own_property, "undefined");
+ assert_equals(results.outer_own_property, "undefined");
+ assert_equals(results.event, "object", "The argument of event handler");
+}, "The EventHandler is an element's event handler and has no form owner.");
+</script>
+
+
+<!-- test case 2: element, form owner, document, and window -->
+
+<form id="test2_form_owner" onsubmit="return false;">
+ <!-- 'button' is a form-associated element and has a form owner.
+ https://html.spec.whatwg.org/C/#form-associated-element
+ -->
+ <button id="test2_target" onclick="
+window.testResults.cite = typeof(cite);
+window.testResults.autofocus = typeof(autofocus);
+window.testResults.form = typeof(form);
+window.testResults.encoding = typeof(encoding);
+window.testResults.domain = typeof(domain);
+window.testResults.print = typeof(print);
+window.testResults.testResults = typeof(testResults);
+window.testResults.target_own_property = typeof(target_own_property);
+window.testResults.inner_own_property = typeof(inner_own_property);
+window.testResults.form_owner_own_property = typeof(form_owner_own_property);
+window.testResults.event = typeof(event);
+">
+ <q id="test2_inner"></q>
+ </button>
+</form>
+
+<script>
+"use strict";
+
+test(() => {
+ const target_element = document.getElementById("test2_target");
+ const inner_element = document.getElementById("test2_inner");
+ const form_owner_element = document.getElementById("test2_form_owner");
+
+ target_element.target_own_property = {};
+ inner_element.inner_own_property = {};
+ form_owner_element.form_owner_own_property = {};
+
+ const results = window.testResults = {};
+ // Clicking an inner element makes the event target where the event handler is
+ // registered doesn't match the event target that the event is fired. From a
+ // point of view |target_element|, event.target != event.currentTarget.
+ inner_element.click();
+ // Expected scopes are: |target_element|, form owner, document, and window.
+ assert_equals(results.cite, "undefined", "HTMLQuoteElement.prototype.cite");
+ assert_equals(results.autofocus, "boolean", "HTMLButtonElement.prototype.autofocus");
+ assert_equals(results.form, "object", "HTMLButtonElement.prototype.form");
+ assert_equals(results.encoding, "string", "HTMLFormElement.prototype.encoding");
+ assert_equals(results.domain, "string", "Document.prototype.domain");
+ assert_equals(results.print, "function", "window.print");
+ assert_equals(results.testResults, "object");
+ assert_equals(results.target_own_property, "object");
+ assert_equals(results.inner_own_property, "undefined");
+ assert_equals(results.form_owner_own_property, "object");
+ assert_equals(results.event, "object", "The argument of event handler");
+}, "The EventHandler is an element's event handler and has a form owner.");
+</script>
+
+
+<!-- test case 3: element and window -->
+
+<a id="test3_inner"></a>
+
+<script>
+"use strict";
+
+// This test is placed at last so that it can safely use a global variable
+// without conflicting other tests. Only this test is asynchronous.
+async_test(t => {
+ const target_element = window;
+ const inner_element = document.getElementById("test3_inner");
+
+ target_element.target_own_property = {};
+ inner_element.inner_own_property = {};
+ document.body.body_own_property = {};
+
+ // "onerror" is one of the Window-reflecting body element event handler set.
+ // https://html.spec.whatwg.org/C/#window-reflecting-body-element-event-handler-set
+ // So, the EventHandler is treated as a Window's event handler.
+ document.body.setAttribute("onerror", "\
+window.testResults.ping = typeof(ping); \
+window.testResults.domain = typeof(domain); \
+window.testResults.print = typeof(print); \
+window.testResults.testResults = typeof(testResults); \
+window.testResults.target_own_property = typeof(target_own_property); \
+window.testResults.inner_own_property = typeof(inner_own_property); \
+window.testResults.body_own_property = typeof(body_own_property); \
+window.testResults.event = typeof(event); \
+");
+
+ const results = window.testResults = {};
+ window.addEventListener("error", t.step_func_done(() => {
+ // Expected scopes are: |target_element| and window only.
+ assert_equals(results.domain, "undefined", "Document.prototype.domain");
+ assert_equals(results.print, "function", "window.print");
+ assert_equals(results.testResults, "object");
+ assert_equals(results.target_own_property, "object");
+ assert_equals(results.inner_own_property, "undefined");
+ assert_in_array(results.event, ["object", "string"], "The first argument of onerror event handler");
+ }));
+
+ // Make a compilation error happen in order to invoke onerror event handler.
+ inner_element.setAttribute("onclick", "cause a compilation error");
+ inner_element.click();
+}, "The EventHandler is not an element's event handler (i.e. Window's event handler) and has no form owner.");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-settings-objects.html b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-settings-objects.html
new file mode 100644
index 0000000000..29ac9b8ced
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-settings-objects.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Entry and incumbent settings objects when compiling an inline event handler</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script>
+"use strict";
+window.name = "parent frame";
+
+async_test(t => {
+ const iframe = document.createElement("iframe");
+ iframe.src = "resources/compiled-event-handler-settings-objects-support.html";
+ iframe.onload = t.step_func(() => {
+ const button = iframe.contentDocument.querySelector("button");
+ const compiled = button.onclick;
+
+ assert_equals(compiled.constructor, iframe.contentWindow.Function, "The constructor must be from the iframe");
+ assert_not_equals(compiled.constructor, Function, "The constructor must not be from this page");
+
+ t.done();
+ });
+
+ document.body.appendChild(iframe);
+
+}, "The Function instance must be created in the Realm of the node document");
+
+async_test(t => {
+ const iframe = document.createElement("iframe");
+ iframe.src = "resources/compiled-event-handler-settings-objects-support.html";
+ iframe.onload = t.step_func(() => {
+ const button = iframe.contentDocument.querySelector("button");
+
+ window.onWindowLoaded = t.step_func_done(url => {
+ const pathname = new URL(url).pathname;
+ assert_equals(pathname,
+ "/html/webappapis/scripting/events/resources/open-window.html");
+ // This tests that the entry settings object used to resolve URLs in that window.open() was the same as that
+ // of the node document (i.e. the iframe document), not e.g. this window.
+ });
+
+ button.click();
+ });
+
+ document.body.appendChild(iframe);
+
+}, "The entry settings object while executing the compiled callback via Web IDL's invoke must be that " +
+ "of the node document");
+
+async_test(t => {
+ const iframe = document.createElement("iframe");
+ iframe.src = "resources/compiled-event-handler-settings-objects-support.html";
+ iframe.onload = t.step_func(() => {
+ window.onmessage = t.step_func_done(event => {
+ assert_equals(event.data, "PASS");
+ assert_equals(event.source.name, "iframe");
+ assert_equals(event.source, iframe.contentWindow, "The source must be the iframe");
+ });
+
+ iframe.src = "about:blank";
+ });
+
+ document.body.appendChild(iframe);
+
+}, "The incumbent settings object while executing the compiled callback via Web IDL's invoke must be that " +
+ "of the node document");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-symbol-unscopables.html b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-symbol-unscopables.html
new file mode 100644
index 0000000000..c840059e68
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-symbol-unscopables.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<meta charset="UTF-8" />
+<title>Inline event handler scopes exclude unscopable properties</title>
+<link rel="author" title="ExE Boss" href="https://ExE-Boss.tech" />
+<link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+ "use strict";
+ window.testVariable = {};
+</script>
+
+<!-- test case 1: element, document, and window -->
+<div id="test1_target" onclick='
+ "use strict";
+
+ window.testResults.testVariable = testVariable;
+'></div>
+
+<script>
+ "use strict";
+
+ test(() => {
+ const results = window.testResults = {};
+
+ document[Symbol.unscopables].testVariable = true;
+ document.testVariable = "FAIL (document)";
+
+ document.getElementById("test1_target").click();
+ assert_equals(results.testVariable, window.testVariable);
+ }, "unscopable `document.testVariable` doesn't shadow `window.testVariable`");
+
+ test(() => {
+ const results = window.testResults = {};
+ const element = document.getElementById("test1_target");
+
+ element[Symbol.unscopables].testVariable = true;
+ element.testVariable = "FAIL (element)";
+
+ element.click();
+ assert_equals(results.testVariable, window.testVariable);
+ }, "unscopable `element.testVariable` doesn't shadow `window.testVariable`");
+</script>
+
+<!-- test case 2: element, form owner, document, and window -->
+<form id="test2_form_owner" onsubmit="return false;">
+ <!-- <button> is a form-associated element and has a form owner.
+ https://html.spec.whatwg.org/C/#form-associated-element -->
+ <button id="test2_target" onclick='
+ "use strict";
+
+ window.testResults.testVariable = testVariable;
+ '></button>
+</form>
+
+<script>
+ "use strict";
+
+ test(() => {
+ const results = window.testResults = {};
+ const element = document.getElementById("test2_target");
+ const formOwner = document.getElementById("test2_form_owner");
+
+ formOwner[Symbol.unscopables].testVariable = true;
+ formOwner.testVariable = "FAIL (formOwner)";
+
+ element.click();
+ assert_equals(results.testVariable, window.testVariable);
+ }, "unscopable `formOwner.testVariable` doesn't shadow `window.testVariable`")
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/contextmenu-event-manual.htm b/testing/web-platform/tests/html/webappapis/scripting/events/contextmenu-event-manual.htm
new file mode 100644
index 0000000000..2331fa17ee
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/contextmenu-event-manual.htm
@@ -0,0 +1,21 @@
+<!doctype html>
+<html>
+ <head>
+ <title>HTML contextmenu event is a MouseEvent</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style>#contextmenutarget { width: 100px; height: 100px; background-color: red; }</style>
+ </head>
+ <body>
+ <div id='contextmenutarget'>Trigger context menu in this box.</div>
+ <div id="log"></div>
+ <script type="text/javascript">
+var t = async_test('contextmenu event generated from user action is MouseEvent');
+document.querySelector("#contextmenutarget").addEventListener('contextmenu', t.step_func(function (e) {
+ assert_equals(e.constructor, window.MouseEvent);
+ document.querySelector("#contextmenutarget").style.backgroundColor = "green";
+ t.done();
+}));
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-all-global-events.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-all-global-events.html
new file mode 100644
index 0000000000..ee8c34ced3
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-all-global-events.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<title>GlobalEventHandlers</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#globaleventhandlers">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#event-handler-idl-attributes">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#event-handler-content-attributes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+
+<script>
+"use strict";
+
+// The prefixed animation events are special; their event types are
+// camel-case.
+const prefixedAnimationAttributeToEventType = new Map([
+ ["webkitanimationend", "webkitAnimationEnd"],
+ ["webkitanimationiteration", "webkitAnimationIteration"],
+ ["webkitanimationstart", "webkitAnimationStart"],
+ ["webkittransitionend", "webkitTransitionEnd"],
+]);
+
+setup({ explicit_done: true });
+
+fetch("/interfaces/html.idl").then(res => res.text()).then(htmlIDL => {
+ const parsedHTMLIDL = WebIDL2.parse(htmlIDL);
+ const globalEventHandlers = parsedHTMLIDL.find(idl => idl.name === "GlobalEventHandlers");
+
+ // onerror is too special
+ const names = globalEventHandlers.members.map(member => member.name).filter(name => name !== "onerror");
+
+ for (const name of names) {
+ const withoutOn = name.substring(2);
+
+ test(() => {
+ for (const location of [window, HTMLElement.prototype, SVGElement.prototype, Document.prototype]) {
+ assert_true(location.hasOwnProperty(name),
+ `${location.constructor.name} has an own property named "${name}"`);
+ }
+ assert_false(name in Element.prototype, `Element.prototype must not contain a "${name}" property`);
+ }, `${name}: must be on the appropriate locations for GlobalEventHandlers`);
+
+ test(() => {
+ const htmlElement = document.createElement("span");
+ const svgElement = document.createElementNS("http://www.w3.org/2000/svg", "g");
+
+ for (var location of [window, htmlElement, svgElement, document]) {
+ assert_equals(location[name], null,
+ `The default value of the property is null for a ${location.constructor.name} instance`);
+ }
+ }, `${name}: the default value must be null`);
+
+ test(() => {
+ const el = document.createElement("div");
+ el.setAttribute(name, `window.${name}Happened = true;`);
+ const compiledHandler = el[name];
+
+ assert_equals(typeof compiledHandler, "function", `The ${name} property must be a function`);
+ compiledHandler();
+ assert_true(window[name + "Happened"], "Calling the handler must run the code");
+ }, `${name}: the content attribute must be compiled into a function as the corresponding property`);
+
+ test(() => {
+ const el = document.createElement("div");
+ el.setAttribute(name, `window.${name}Happened2 = true;`);
+
+ let eventType = withoutOn;
+ if (prefixedAnimationAttributeToEventType.has(eventType)) {
+ eventType = prefixedAnimationAttributeToEventType.get(eventType);
+ }
+ el.dispatchEvent(new Event(eventType));
+
+ assert_true(window[name + "Happened2"], "Dispatching an event must run the code");
+ }, `${name}: the content attribute must execute when an event is dispatched`);
+
+ test(() => {
+ const element = document.createElement("meta");
+ element[name] = e => {
+ assert_equals(e.currentTarget, element, "The event must be fired at the <meta> element");
+ };
+
+ element.dispatchEvent(new Event(withoutOn));
+ }, `${name}: dispatching an Event at a <meta> element must trigger element.${name}`);
+ }
+
+ done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-body-window.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-body-window.html
new file mode 100644
index 0000000000..e8055d99f3
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-body-window.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>HTMLBodyElement event handlers</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="resources/event-handler-body.js"></script>
+<div id="log"></div>
+<body>
+<script>
+setup({ explicit_done: true });
+
+handlersListPromise.then(({ shadowedHandlers, notShadowedHandlers }) => {
+ eventHandlerTest(shadowedHandlers, notShadowedHandlers, "body");
+
+ done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-frameset-window.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-frameset-window.html
new file mode 100644
index 0000000000..b583eca52d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-frameset-window.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>event handlers</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="resources/event-handler-body.js"></script>
+<script>
+setup({ explicit_done: true });
+
+handlersListPromise.then(({ shadowedHandlers, notShadowedHandlers }) => {
+ eventHandlerTest(shadowedHandlers, notShadowedHandlers, "frameset");
+
+ // The testharness framework appends test results to document.body,
+ // show test results in frame after test done.
+ add_completion_callback(() => {
+ const log_elem = document.getElementById("log");
+ const frame_elem = document.querySelector("frame");
+ if (log_elem) {
+ frame_elem.contentDocument.body.innerHTML = log_elem.innerHTML;
+ }
+ });
+
+ done();
+});
+</script>
+<frameset>
+ <frame src="/common/blank.html" />
+</frameset>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-windowless-body.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-windowless-body.html
new file mode 100644
index 0000000000..9b81d42ff7
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-windowless-body.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<meta charset="utf-8">
+<title></title>
+<body></body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="resources/event-handler-body.js"></script>
+<script>
+setup({ explicit_done: true });
+const elements = ['body', 'frameset'];
+handlersListPromise.then(({ shadowedHandlers, notShadowedHandlers }) => {
+ elements.forEach(function (elementName) {
+ shadowedHandlers.forEach(function (eventName) {
+ var handlerName = "on" + eventName;
+
+ test(function() {
+ var windowHandler = function () { return "Handler attached to the window"; };
+ window[handlerName] = windowHandler;
+
+ var d = (new DOMParser).parseFromString('', 'text/html');
+ var b = d.createElement(elementName);
+
+ assert_equals(b[handlerName], null);
+
+ window[handlerName] = null;
+ }, "Return null when getting the " + eventName + " event handler of a windowless " + elementName);
+
+ test(function() {
+ var windowHandler = function () { return "Handler attached to the window"; };
+ window[handlerName] = windowHandler;
+
+ var d = (new DOMParser).parseFromString('', 'text/html');
+ var b = d.createElement(elementName);
+ b[handlerName] = function() { return "Handler attached to windowless element"; };
+
+ assert_equals(window[handlerName], windowHandler);
+ assert_equals(b[handlerName], null);
+
+ // Clean up window event handler
+ window[handlerName] = null;
+ }, "Ignore setting of " + eventName + " window event handlers on windowless " + elementName);
+ });
+
+ notShadowedHandlers.forEach(function (eventName) {
+ var handlerName = "on" + eventName;
+
+ test(function() {
+ var windowHandler = function () { return "Handler attached to the window"; };
+ window[handlerName] = windowHandler;
+
+ var d = (new DOMParser).parseFromString('', 'text/html');
+ var b = d.createElement(elementName);
+
+ assert_equals(b[handlerName], null);
+
+ var elementHandler = function () { return "Handler attached to the element"; };
+ b[handlerName] = elementHandler;
+
+ assert_equals(window[handlerName], windowHandler);
+ assert_equals(b[handlerName], elementHandler);
+
+ // Clean up window event handler
+ window[handlerName] = null;
+ }, eventName + " is unaffected on a windowless " + elementName);
+ });
+ });
+
+ done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-handleEvent-ignored.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-handleEvent-ignored.html
new file mode 100644
index 0000000000..8039bac7ad
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-handleEvent-ignored.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>"handleEvent" property of EventHandler should be ignored</title>
+<link rel="help" href="https://html.spec.whatwg.org/#eventhandler">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+"use strict";
+
+test(t => {
+ const handler = Object.create(null, {
+ handleEvent: {
+ get: t.unreached_func('"handleEvent" property should not be looked up'),
+ },
+ });
+
+ const el = document.createElement("div");
+ el.onmouseenter = handler;
+ el.dispatchEvent(new MouseEvent("mouseenter"));
+}, 'plain object "mouseenter" handler');
+
+async_test(t => {
+ const handler = Object.create(Function.prototype, {
+ handleEvent: {
+ get: t.unreached_func('"handleEvent" property should not be looked up'),
+ },
+ });
+ assert_true(handler instanceof Function);
+
+ window.onmessage = handler;
+ window.postMessage({}, "*");
+
+ step_timeout(() => {
+ t.done();
+ }, 50);
+}, 'non-callable "message" handler that is instance of Function');
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-javascript.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-javascript.html
new file mode 100644
index 0000000000..657a37839d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-javascript.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Event handler with labels</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body onload="javascript:
+ for (var i = 0; i < 2; ++i) {
+ for (var j = 0; j < 2; ++j) {
+ t.step(function() {
+ assert_equals(i, 0);
+ assert_equals(j, 0);
+ });
+ break javascript;
+ }
+ }
+ t.done();
+">
+<div id="log"></div>
+<script>
+var t = async_test("Event handlers starting with 'javascript:' should treat that as a label.");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onmove-01.tentative.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onmove-01.tentative.html
new file mode 100644
index 0000000000..e4454a20a1
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onmove-01.tentative.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Window Object 'onmove'</title>
+<link rel="author" title="Sonja Laurila" href="mailto:laurila@google.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#handler-window-onmove">
+<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 t => {
+ var onMoveCalled = false;
+ window.onmove = (event) => { onMoveCalled = true; }
+ window.dispatchEvent(new Event('move'));
+ assert_true(onMoveCalled, "The onMove handler has been executed.");
+}, "Window move event");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onmove-02.tentative.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onmove-02.tentative.html
new file mode 100644
index 0000000000..a751f39d30
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onmove-02.tentative.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Window Object 'onmove'</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#handler-window-onmove">
+<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((t) => {
+ var expectedRect = {'x': 100, 'y': 200};
+ return new Promise(resolve => {
+ window.addEventListener("move", resolve);
+ test_driver.set_window_rect(expectedRect);
+ }).then(event => {
+ return test_driver.get_window_rect()
+ .then(rect => {
+ assert_equals(rect.x, expectedRect.x, "The window rect X is correct.")
+ assert_equals(rect.y, expectedRect.y, "The window rect Y is correct.")
+ });
+ });
+}, "Window move event");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onresize.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onresize.html
new file mode 100644
index 0000000000..0e44e7272f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onresize.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>HTMLBodyElement.onresize</title>
+<link rel="author" title="His-Name-Is-Joof" href="mailto:jeffrharrison@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#handler-window-onresize">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var t = async_test("body.onresize should set the window.onresize handler")
+window.onresize = t.step_func(function() {
+ assert_unreached("This handler should be overwritten.")
+})
+
+var body = document.createElement("body")
+body.onresize = t.step_func(function(e) {
+ assert_equals(e.currentTarget, window,
+ "The event should be fired at the window.")
+ t.done()
+})
+window.dispatchEvent(new Event('resize'));
+
+t = async_test("document.onresize should set the document.onresize handler");
+document.onresize = t.step_func(function(e) {
+ assert_equals(e.currentTarget, document,
+ "The event should be fired at the document")
+ t.done()
+})
+document.dispatchEvent(new Event('resize'));
+
+t = async_test("meta.onresize should set the meta.onresize handler");
+var meta = document.createElement("meta")
+meta.onresize = t.step_func(function(e) {
+ assert_equals(e.currentTarget, meta,
+ "The event should be fired at the <meta> object")
+ t.done()
+})
+meta.dispatchEvent(new Event('resize'));
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-errorevent.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-errorevent.html
new file mode 100644
index 0000000000..9ab0020ec3
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-errorevent.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event handlers processing algorithm: error events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ allow_uncaught_exception: true });
+
+promise_test(t => {
+ document.body.onerror = t.step_func((...args) => {
+ assert_greater_than(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, window, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, true);
+ });
+
+ document.body.dispatchEvent(new ErrorEvent("error", { bubbles: true, cancelable: true }));
+
+ return promise;
+}, "error event is weird (return true cancels; many args) on Window, with a synthetic ErrorEvent");
+
+promise_test(t => {
+ const theError = { the: "error object" };
+
+ document.body.onerror = t.step_func(function (message, filename, lineno, colno, error) {
+ assert_equals(arguments.length, 5, "There must be exactly 5 arguments");
+ assert_equals(message, "message");
+ assert_equals(filename, "filename");
+ assert_equals(lineno, 1);
+ assert_equals(colno, 2);
+ assert_equals(error, theError);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, window, "error");
+ const promise = eventWatcher.wait_for("error");
+
+ document.body.dispatchEvent(new ErrorEvent("error", {
+ bubbles: true,
+ message: "message",
+ filename: "filename",
+ lineno: 1,
+ colno: 2,
+ error: theError
+ }));
+
+ return promise;
+}, "error event has the right 5 args on Window, with a synthetic ErrorEvent");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-event.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-event.html
new file mode 100644
index 0000000000..9ed2638416
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-event.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event handlers processing algorithm: error events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ allow_uncaught_exception: true });
+
+promise_test(t => {
+ document.body.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, window, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ document.body.dispatchEvent(new Event("error", { bubbles: true, cancelable: true }));
+
+ return promise;
+}, "error event is normal (return true does not cancel; one arg) on Window, with a synthetic Event");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-errorevent.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-errorevent.html
new file mode 100644
index 0000000000..4165beaf63
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-errorevent.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event handlers processing algorithm: error events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ allow_uncaught_exception: true });
+
+promise_test(t => {
+ document.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, document, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ assert_equals(e.message, "");
+ assert_equals(e.filename, "");
+ assert_equals(e.lineno, 0);
+ assert_equals(e.colno, 0);
+ assert_equals(e.error, undefined);
+ });
+
+ document.dispatchEvent(new ErrorEvent("error", { cancelable: true }));
+
+ return promise;
+}, "error event is normal (return true does not cancel; one arg) on Document, with a synthetic ErrorEvent");
+
+test(() => {
+ const e = new ErrorEvent("error");
+ assert_equals(e.message, "");
+ assert_equals(e.filename, "");
+ assert_equals(e.lineno, 0);
+ assert_equals(e.colno, 0);
+ assert_equals(e.error, undefined);
+}, "Initial values of ErrorEvent members")
+
+test(() => {
+ const e = new ErrorEvent("error", {error : null});
+ assert_equals(e.error, null);
+}, "error member can be set to null")
+
+test(() => {
+ const e = new ErrorEvent("error", {error : undefined});
+ assert_equals(e.error, undefined);
+}, "error member can be set to undefined")
+
+test(() => {
+ const e = new ErrorEvent("error", {error : "foo"});
+ assert_equals(e.error, "foo");
+}, "error member can be set to arbitrary")
+
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-event.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-event.html
new file mode 100644
index 0000000000..6cf44e9d35
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-event.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event handlers processing algorithm: error events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ allow_uncaught_exception: true });
+
+promise_test(t => {
+ document.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, document, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ document.dispatchEvent(new Event("error", { cancelable: true }));
+
+ return promise;
+}, "error event is normal (return true does not cancel; one arg) on Document, with a synthetic Event");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-errorevent.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-errorevent.html
new file mode 100644
index 0000000000..20d87dbacf
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-errorevent.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event handlers processing algorithm: error events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm">
+
+<iframe name="framesetWindow" src="resources/frameset-frame.html"></iframe>
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ allow_uncaught_exception: true });
+
+window.onload = () => {
+
+const frameset = framesetWindow.document.querySelector("frameset");
+
+promise_test(t => {
+ frameset.onerror = t.step_func((...args) => {
+ assert_greater_than(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, framesetWindow, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, true);
+ });
+
+ frameset.dispatchEvent(new ErrorEvent("error", { bubbles: true, cancelable: true }));
+
+ return promise;
+}, "error event is weird (return true cancels; many args) on Window, with a synthetic ErrorEvent");
+
+promise_test(t => {
+ const theError = { the: "error object" };
+
+ frameset.onerror = t.step_func(function (message, filename, lineno, colno, error) {
+ assert_equals(arguments.length, 5, "There must be exactly 5 arguments");
+ assert_equals(message, "message");
+ assert_equals(filename, "filename");
+ assert_equals(lineno, 1);
+ assert_equals(colno, 2);
+ assert_equals(error, theError);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, framesetWindow, "error");
+ const promise = eventWatcher.wait_for("error");
+
+ frameset.dispatchEvent(new ErrorEvent("error", {
+ bubbles: true,
+ message: "message",
+ filename: "filename",
+ lineno: 1,
+ colno: 2,
+ error: theError
+ }));
+
+ return promise;
+}, "error event has the right 5 args on Window, with a synthetic ErrorEvent");
+
+};
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-event.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-event.html
new file mode 100644
index 0000000000..2fdca3ad86
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-event.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event handlers processing algorithm: error events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm">
+
+<iframe name="framesetWindow" src="resources/frameset-frame.html"></iframe>
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ allow_uncaught_exception: true });
+
+window.onload = () => {
+
+const frameset = framesetWindow.document.querySelector("frameset");
+
+promise_test(t => {
+ frameset.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, framesetWindow, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ frameset.dispatchEvent(new Event("error", { bubbles: true, cancelable: true }));
+
+ return promise;
+}, "error event is normal (return true does not cancel; one arg) on Window, with a synthetic Event");
+
+};
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/frameset-frame.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/frameset-frame.html
new file mode 100644
index 0000000000..028be4919e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/frameset-frame.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<frameset></frameset>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/no-op-worker.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/no-op-worker.js
new file mode 100644
index 0000000000..3918c74e44
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/no-op-worker.js
@@ -0,0 +1 @@
+"use strict";
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/worker-with-syntax-error.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/worker-with-syntax-error.js
new file mode 100644
index 0000000000..dc9a0dbf4a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/worker-with-syntax-error.js
@@ -0,0 +1 @@
+< 3;
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/script-element.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/script-element.html
new file mode 100644
index 0000000000..f3ef1165e0
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/script-element.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event handlers processing algorithm: error events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+
+promise_test(t => {
+ const script = document.createElement("script");
+ script.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, script, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.constructor, Event); // not ErrorEvent
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ script.src = "404.js";
+ document.body.appendChild(script);
+
+ return promise;
+}, "error event behaves normally (return true does not cancel; one arg) on a script element, with a 404 error");
+
+promise_test(t => {
+ const script = document.createElement("script");
+ script.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, script, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ script.dispatchEvent(new Event("error", { cancelable: true }));
+
+ return promise;
+}, "error event behaves normally (return true does not cancel; one arg) on a script element, with a synthetic Event");
+
+promise_test(t => {
+ const script = document.createElement("script");
+ script.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, script, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ script.dispatchEvent(new ErrorEvent("error", { cancelable: true }));
+
+ return promise;
+}, "error event behaves normally (return true does not cancel; one arg) on a script element, with a synthetic ErrorEvent");
+
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.html
new file mode 100644
index 0000000000..75a1772485
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event handlers processing algorithm: click events using ErrorEvent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+promise_test(t => {
+ document.onclick = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, document, "click");
+ const promise = eventWatcher.wait_for("click").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ document.dispatchEvent(new ErrorEvent("click", { cancelable: true }));
+
+ return promise;
+}, "click event is normal (return true does not cancel; one arg) on Document, with a synthetic ErrorEvent");
+
+promise_test(t => {
+ window.onclick = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, window, "click");
+ const promise = eventWatcher.wait_for("click").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ window.dispatchEvent(new ErrorEvent("click", { cancelable: true }));
+
+ return promise;
+}, "click event is normal (return true does not cancel; one arg) on Window, with a synthetic ErrorEvent");
+
+promise_test(t => {
+ const el = document.createElement("script");
+ el.onclick = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, el, "click");
+ const promise = eventWatcher.wait_for("click").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ el.dispatchEvent(new ErrorEvent("click", { cancelable: true }));
+
+ return promise;
+}, "click event is normal (return true does not cancel; one arg) on a script element, with a synthetic ErrorEvent");
+
+promise_test(t => {
+ const worker = new Worker("resources/no-op-worker.js");
+ worker.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, worker, "click");
+ const promise = eventWatcher.wait_for("click").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ worker.dispatchEvent(new ErrorEvent("click", { cancelable: true }));
+
+ return promise;
+}, "click event is normal (return true does not cancel; one arg) on Worker, with a synthetic ErrorEvent");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.worker.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.worker.js
new file mode 100644
index 0000000000..177a99e2ce
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.worker.js
@@ -0,0 +1,22 @@
+"use strict";
+importScripts("/resources/testharness.js");
+
+setup({ allow_uncaught_exception: true });
+
+promise_test(t => {
+ self.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, self, "click");
+ const promise = eventWatcher.wait_for("click").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ self.dispatchEvent(new ErrorEvent("click", { cancelable: true }));
+
+ return promise;
+}, "error event is normal (return true does not cancel; one arg) on WorkerGlobalScope, with a synthetic ErrorEvent");
+
+done();
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-runtime-error.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-runtime-error.html
new file mode 100644
index 0000000000..1b387ca81c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-runtime-error.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event handlers processing algorithm: error events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ allow_uncaught_exception: true });
+
+promise_test(t => {
+ window.onerror = t.step_func((...args) => {
+ assert_greater_than(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, window, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, true);
+ });
+
+ setTimeout(() => thisFunctionDoesNotExist(), 0);
+
+ return promise;
+}, "error event is weird (return true cancels; many args) on Window, with a runtime error");
+
+promise_test(t => {
+ window.onerror = t.step_func(function (message, filename, lineno, colno, error) {
+ assert_equals(arguments.length, 5, "There must be exactly 5 arguments");
+ assert_equals(typeof message, "string", "message argument must be a string");
+ assert_equals(typeof filename, "string", "filename argument must be a string");
+ assert_equals(typeof lineno, "number", "lineno argument must be a number");
+ assert_equals(typeof colno, "number", "colno argument must be a number");
+ assert_equals(typeof error, "object", "error argument must be an object");
+ assert_equals(error.constructor, ReferenceError, "error argument must be a ReferenceError");
+ return true;
+ });
+
+ setTimeout(() => thisFunctionDoesNotExist(), 0);
+
+ const eventWatcher = new EventWatcher(t, window, "error");
+ return eventWatcher.wait_for("error");
+}, "error event has the right 5 args on Window, with a runtime error");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-errorevent.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-errorevent.html
new file mode 100644
index 0000000000..2d62d8a204
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-errorevent.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event handlers processing algorithm: error events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ allow_uncaught_exception: true });
+
+promise_test(t => {
+ window.onerror = t.step_func((...args) => {
+ assert_greater_than(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, window, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, true);
+ });
+
+ window.dispatchEvent(new ErrorEvent("error", { cancelable: true }));
+
+ return promise;
+}, "error event is weird (return true cancels; many args) on Window, with a synthetic ErrorEvent");
+
+promise_test(t => {
+ const theError = { the: "error object" };
+
+ window.onerror = t.step_func(function (message, filename, lineno, colno, error) {
+ assert_equals(arguments.length, 5, "There must be exactly 5 arguments");
+ assert_equals(message, "message");
+ assert_equals(filename, "filename");
+ assert_equals(lineno, 1);
+ assert_equals(colno, 2);
+ assert_equals(error, theError);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, window, "error");
+ const promise = eventWatcher.wait_for("error");
+
+ window.dispatchEvent(new ErrorEvent("error", {
+ message: "message",
+ filename: "filename",
+ lineno: 1,
+ colno: 2,
+ error: theError
+ }));
+
+ return promise;
+}, "error event has the right 5 args on Window, with a synthetic ErrorEvent");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-event.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-event.html
new file mode 100644
index 0000000000..0bcc7defb7
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-event.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event handlers processing algorithm: error events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ allow_uncaught_exception: true });
+
+promise_test(t => {
+ window.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, window, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ window.dispatchEvent(new Event("error", { cancelable: true }));
+
+ return promise;
+}, "error event is normal (return true does not cancel; one arg) on Window, with a synthetic Event");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/worker.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/worker.html
new file mode 100644
index 0000000000..a8c0d97ce2
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/worker.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event handlers processing algorithm: error events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ allow_uncaught_exception: true });
+
+promise_test(t => {
+ const worker = new Worker("resources/worker-with-syntax-error.js");
+ worker.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, worker, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ return promise;
+}, "error event is normal (return true does not cancel; one arg) on Worker, with a syntax error in the worker code");
+
+promise_test(t => {
+ const worker = new Worker("resources/no-op-worker.js");
+ worker.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, worker, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ worker.dispatchEvent(new Event("error", { cancelable: true }));
+
+ return promise;
+}, "error event is normal (return true does not cancel; one arg) on Worker, with a synthetic Event");
+
+promise_test(t => {
+ const worker = new Worker("resources/no-op-worker.js");
+ worker.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, worker, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ worker.dispatchEvent(new ErrorEvent("error", { cancelable: true }));
+
+ return promise;
+}, "error event is normal (return true does not cancel; one arg) on Worker, with a synthetic ErrorEvent");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-runtime-error.worker.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-runtime-error.worker.js
new file mode 100644
index 0000000000..264fef810d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-runtime-error.worker.js
@@ -0,0 +1,40 @@
+"use strict";
+importScripts("/resources/testharness.js");
+
+setup({ allow_uncaught_exception: true });
+
+promise_test(t => {
+ self.onerror = t.step_func((...args) => {
+ assert_greater_than(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, self, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, true);
+ });
+
+ setTimeout(() => thisFunctionDoesNotExist(), 0);
+
+ return promise;
+}, "error event is weird (return true cancels; many args) on WorkerGlobalScope, with a runtime error");
+
+promise_test(t => {
+ self.onerror = t.step_func(function (message, filename, lineno, colno, error) {
+ assert_equals(arguments.length, 5, "There must be exactly 5 arguments");
+ assert_equals(typeof message, "string", "message argument must be a string");
+ assert_equals(typeof filename, "string", "filename argument must be a string");
+ assert_equals(typeof lineno, "number", "lineno argument must be a number");
+ assert_equals(typeof colno, "number", "colno argument must be a number");
+ assert_equals(typeof error, "object", "error argument must be an object");
+ assert_equals(error.constructor, ReferenceError, "error argument must be a ReferenceError");
+ return true;
+ });
+
+ setTimeout(() => thisFunctionDoesNotExist(), 0);
+
+ const eventWatcher = new EventWatcher(t, self, "error");
+ return eventWatcher.wait_for("error");
+}, "error event has the right 5 args on WorkerGlobalScope, with a runtime error");
+
+done();
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-errorevent.worker.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-errorevent.worker.js
new file mode 100644
index 0000000000..a14f6e01a9
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-errorevent.worker.js
@@ -0,0 +1,49 @@
+"use strict";
+importScripts("/resources/testharness.js");
+
+setup({ allow_uncaught_exception: true });
+
+promise_test(t => {
+ self.onerror = t.step_func((...args) => {
+ assert_greater_than(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, self, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, true);
+ });
+
+ self.dispatchEvent(new ErrorEvent("error", { cancelable: true }));
+
+ return promise;
+}, "error event is weird (return true cancels; many args) on WorkerGlobalScope, with a synthetic ErrorEvent");
+
+promise_test(t => {
+ const theError = { the: "error object" };
+
+ self.onerror = t.step_func(function (message, filename, lineno, colno, error) {
+ assert_equals(arguments.length, 5, "There must be exactly 5 arguments");
+ assert_equals(message, "message");
+ assert_equals(filename, "filename");
+ assert_equals(lineno, 1);
+ assert_equals(colno, 2);
+ assert_equals(error, theError);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, self, "error");
+ const promise = eventWatcher.wait_for("error");
+
+ self.dispatchEvent(new ErrorEvent("error", {
+ message: "message",
+ filename: "filename",
+ lineno: 1,
+ colno: 2,
+ error: theError
+ }));
+
+ return promise;
+}, "error event has the right 5 args on WorkerGlobalScope, with a synthetic ErrorEvent");
+
+done();
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-event.worker.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-event.worker.js
new file mode 100644
index 0000000000..a3e16ded88
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-event.worker.js
@@ -0,0 +1,22 @@
+"use strict";
+importScripts("/resources/testharness.js");
+
+setup({ allow_uncaught_exception: true });
+
+promise_test(t => {
+ self.onerror = t.step_func((...args) => {
+ assert_equals(args.length, 1);
+ return true;
+ });
+
+ const eventWatcher = new EventWatcher(t, self, "error");
+ const promise = eventWatcher.wait_for("error").then(e => {
+ assert_equals(e.defaultPrevented, false);
+ });
+
+ self.dispatchEvent(new Event("error", { cancelable: true }));
+
+ return promise;
+}, "error event is normal (return true does not cancel; one arg) on WorkerGlobalScope, with a synthetic Event");
+
+done();
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-manual.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-manual.html
new file mode 100644
index 0000000000..205e876c1d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-manual.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event handlers processing algorithm: manual tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<style>
+ div[id^="d"] {
+ width: 100px;
+ height: 100px;
+ background-color: blue;
+ }
+</style>
+
+<div id="log"></div>
+
+<p>Mouseover these four divs</p>
+
+<div id="d1"></div>
+<div id="d2"></div>
+
+<div id="d3" onmouseover="return false"></div>
+<div id="d4" onmouseover="return true"></div>
+
+<script>
+"use strict";
+async_test(t => {
+ const div = document.querySelector("#d1");
+
+ div.onmouseover = t.step_func(() => false);
+ div.addEventListener("mouseover", t.step_func_done(e => {
+ assert_equals(e.defaultPrevented, true);
+ }));
+}, "Listener added via JavaScript, returns false: cancels the event");
+
+async_test(t => {
+ const div = document.querySelector("#d2");
+
+ div.onmouseover = t.step_func(() => true);
+ div.addEventListener("mouseover", t.step_func_done(e => {
+ assert_equals(e.defaultPrevented, false);
+ }));
+}, "Listener added via JavaScript, returns true: does not cancel the event");
+
+async_test(t => {
+ const div = document.querySelector("#d3");
+
+ div.addEventListener("mouseover", t.step_func_done(e => {
+ assert_equals(e.defaultPrevented, true);
+ }));
+}, "Listener added via markup, returns false: cancels the event");
+
+async_test(t => {
+ const div = document.querySelector("#d4");
+
+ div.addEventListener("mouseover", t.step_func_done(e => {
+ assert_equals(e.defaultPrevented, false);
+ }));
+}, "Listener added via markup, returns true: does not cancel the event");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm.html
new file mode 100644
index 0000000000..f5423d7ed4
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Event handlers processing algorithm</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+ <body>
+ <div id="foo" style="width: 100px; height: 100px; background-color: black"></div>
+ <script>
+
+ // Historically mouseover was special in the spec, but now it is not. See https://github.com/whatwg/html/pull/2398.
+ test(function(t) {
+ var ev = new Event('mouseover', {cancelable: true});
+ document.getElementById("foo").onmouseover = t.step_func(function() { return false });
+ document.getElementById("foo").dispatchEvent(ev);
+ assert_equals(ev.defaultPrevented, true)
+ }, "mouseover listener returning false cancels event (using Event)");
+
+ test(function(t) {
+ var ev = new MouseEvent('mouseover', {cancelable: true});
+ document.getElementById("foo").onmouseover = t.step_func(function() { return false });
+ document.getElementById("foo").dispatchEvent(ev);
+ assert_equals(ev.defaultPrevented, true)
+ }, "mouseover listener returning false cancels event (using MouseEvent)");
+
+ test(function(t) {
+ var ev = new Event('mouseover', {cancelable: true});
+ document.getElementById("foo").onmouseover = t.step_func(function() { return true });
+ document.getElementById("foo").dispatchEvent(ev);
+ assert_equals(ev.defaultPrevented, false)
+ }, "mouseover listener returning true doesn't cancel event (using Event)");
+
+ test(function(t) {
+ var ev = new MouseEvent('mouseover', {cancelable: true});
+ document.getElementById("foo").onmouseover = t.step_func(function() { return true });
+ document.getElementById("foo").dispatchEvent(ev);
+ assert_equals(ev.defaultPrevented, false)
+ }, "mouseover listener returning true doesn't cancel event (using MouseEvent)");
+
+ // beforeunload is tested in html/browsers/browsing-the-web/unloading-documents/beforeunload-canceling.html
+
+ test(function(t) {
+ var ev = new Event("click", {cancelable: true});
+ document.getElementById("foo").onclick = t.step_func(function() { return false; });
+ document.getElementById("foo").dispatchEvent(ev);
+ assert_equals(ev.defaultPrevented, true);
+ }, "click listener returning false cancels event");
+
+ test(function(t) {
+ var ev = new Event("blur", {cancelable: true});
+ document.getElementById("foo").onblur = t.step_func(function() { return false; });
+ document.getElementById("foo").dispatchEvent(ev);
+ assert_equals(ev.defaultPrevented, true);
+ }, "blur listener returning false cancels event");
+
+ test(function(t) {
+ var ev = new Event("dblclick", {cancelable: true});
+ document.getElementById("foo").ondblclick = t.step_func(function() { return false; });
+ document.getElementById("foo").dispatchEvent(ev);
+ assert_equals(ev.defaultPrevented, true);
+ }, "dblclick listener returning false cancels event");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-removal.window.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-removal.window.js
new file mode 100644
index 0000000000..a20e2ec1d2
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-removal.window.js
@@ -0,0 +1,76 @@
+let firstEventHandler;
+
+test(t => {
+ var i = 0;
+ firstEventHandler = t.unreached_func('First event handler.');
+ var uncalled = "firstEventHandler();";
+ var button = document.createElement('button');
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 1) }), false);
+ button.setAttribute('onclick', uncalled); // event handler is activated here
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 2) }), false);
+ button.onclick = null; // but de-activated here
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false);
+ button.onclick = t.step_func(() => { assert_equals(++i, 4); }); // and re-activated here
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 5) }), false);
+ button.click()
+ assert_equals(button.getAttribute("onclick"), uncalled)
+ assert_equals(i, 5);
+}, "Event handler set through content attribute should be removed when they are set to null.");
+
+let happened = 0;
+test(() => {
+ var script = "happened++;";
+ var button = document.createElement('button');
+ button.setAttribute('onclick', script); // event handler is activated here
+ button.onclick = null; // but de-activated here
+ assert_equals(button.getAttribute("onclick"), script)
+ button.setAttribute('onclick', script); // and re-activated here
+ button.click()
+ assert_equals(happened, 1);
+}, "Event handler set through content attribute should be re-activated even if content is the same.");
+
+test(t => {
+ var i = 0;
+ firstEventHandler = t.unreached_func('First event handler.');
+ var uncalled = "firstEventHandler();";
+ var button = document.createElement('button');
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 1) }), false);
+ button.setAttribute('onclick', uncalled); // event handler is activated here
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 2) }), false);
+ button.removeAttribute('onclick'); // but de-activated here
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false);
+ button.onclick = t.step_func(() => { assert_equals(++i, 4); }); // and re-activated here
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 5) }), false);
+ button.click()
+ assert_equals(i, 5);
+}, "Event handler set through content attribute should be deactivated when the content attribute is removed.");
+test(t => {
+ var i = 0;
+ firstEventHandler = t.unreached_func('First event handler.');
+ var uncalled = "firstEventHandler();";
+ var button = document.createElement('button');
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 1) }), false);
+ button.onclick = t.unreached_func('First event handler.'); // event handler is activated here
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 2) }), false);
+ button.onclick = null; // but de-activated here
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false);
+ button.onclick = t.step_func(() => { assert_equals(++i, 4); }); // and re-activated here
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 5) }), false);
+ button.click()
+ assert_equals(i, 5);
+}, "Event handler set through IDL should be deactivated when the IDL attribute is set to null.");
+test(t => {
+ var i = 0;
+ firstEventHandler = t.unreached_func('First event handler.');
+ var uncalled = "firstEventHandler();";
+ var button = document.createElement('button');
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 1) }), false);
+ button.onclick = t.unreached_func('First event handler.'); // event handler is activated here
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false);
+ button.removeAttribute('onclick'); // and NOT de-activated here
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 4) }), false);
+ button.onclick = t.step_func(() => { assert_equals(++i, 2); });
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 5) }), false);
+ button.click()
+ assert_equals(i, 5);
+}, "Event handler set through IDL should NOT be deactivated when the content attribute is removed.");
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-sourcetext.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-sourcetext.html
new file mode 100644
index 0000000000..57555faa7b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-sourcetext.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test the sourceText of event handlers</title>
+<link rel="help" href="https://github.com/whatwg/html/issues/5500">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+test(() => {
+ const el = document.createElement("div");
+ el.setAttribute("onclick", "foo");
+ assert_equals(el.onclick.toString(), "function onclick(event) {\nfoo\n}");
+}, "non-error event handler");
+
+test(() => {
+ const el = document.createElement("div");
+ el.setAttribute("onerror", "foo");
+ assert_equals(el.onerror.toString(), "function onerror(event) {\nfoo\n}");
+}, "error event handler not on body");
+
+test(() => {
+ const el = document.createElement("body");
+ el.setAttribute("onerror", "foo");
+ assert_equals(el.onerror.toString(), "function onerror(event, source, lineno, colno, error) {\nfoo\n}");
+}, "error event handler on disconnected body");
+
+test(() => {
+ const el = document.createElement("frameset");
+ el.setAttribute("onerror", "foo");
+ assert_equals(el.onerror.toString(), "function onerror(event, source, lineno, colno, error) {\nfoo\n}");
+}, "error event handler on disconnected frameset");
+
+test(() => {
+ document.body.setAttribute("onerror", "foo");
+ assert_equals(window.onerror.toString(), "function onerror(event, source, lineno, colno, error) {\nfoo\n}");
+}, "error event handler on connected body, reflected to Window");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-spec-example.window.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-spec-example.window.js
new file mode 100644
index 0000000000..abf46882aa
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-spec-example.window.js
@@ -0,0 +1,55 @@
+var objects = [{}, function() {}, new Number(42), new String()];
+var primitives = [42, null, undefined, ""];
+var firstEventHandler;
+objects.forEach(function(object) {
+ test(t => {
+ var i = 0;
+ firstEventHandler = t.unreached_func('First event handler.');
+ var uncalled = "firstEventHandler();";
+ var button = document.createElement('button');
+ button.onclick = object; // event handler listener is registered here
+ assert_equals(button.onclick, object);
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 2) }), false);
+ button.setAttribute('onclick', uncalled);
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false);
+ button.onclick = t.step_func(() => { assert_equals(++i, 1); });
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 4) }), false);
+ button.click()
+ assert_equals(button.getAttribute("onclick"), uncalled)
+ assert_equals(i, 4);
+ }, "Event handler listeners should be registered when they are first set to an object value " +
+ "(" + format_value(object) + ").");
+});
+primitives.forEach(function(primitive) {
+ test(t => {
+ var i = 0;
+ firstEventHandler = t.unreached_func('First event handler.');
+ var uncalled = "firstEventHandler();";
+ var button = document.createElement('button');
+ button.onclick = primitive;
+ assert_equals(button.onclick, null);
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 1) }), false);
+ button.setAttribute('onclick', uncalled); // event handler listener is registered here
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false);
+ button.onclick = t.step_func(() => { assert_equals(++i, 2); });
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 4) }), false);
+ button.click()
+ assert_equals(button.getAttribute("onclick"), uncalled)
+ assert_equals(i, 4);
+ }, "Event handler listeners should be registered when they are first set to an object value " +
+ "(" + format_value(primitive) + ").");
+});
+test(t => {
+ var i = 0;
+ firstEventHandler = t.unreached_func('First event handler.');
+ var uncalled = "firstEventHandler();";
+ var button = document.createElement('button');
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 1) }), false);
+ button.setAttribute('onclick', uncalled); // event handler listener is registered here
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false);
+ button.onclick = t.step_func(() => { assert_equals(++i, 2); });
+ button.addEventListener('click', t.step_func(() => { assert_equals(++i, 4) }), false);
+ button.click()
+ assert_equals(button.getAttribute("onclick"), uncalled)
+ assert_equals(i, 4);
+}, "Event handler listeners should be registered when they are first set to an object value.");
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/eventhandler-cancellation.html b/testing/web-platform/tests/html/webappapis/scripting/events/eventhandler-cancellation.html
new file mode 100644
index 0000000000..6be581fa24
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/eventhandler-cancellation.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<!-- A window to work with that won't trigger the harness exception detection
+ when we fire "error" events at it -->
+<iframe style="display: none"></iframe>
+<script>
+ test(function() {
+ var blob = new Blob([""]);
+ // Most targets disabled for now until
+ // https://github.com/whatwg/html/issues/2296 is sorted out.
+ var targets = [ frames[0] /*, document, document.documentElement,
+ new Worker(URL.createObjectURL(blob) */ ];
+ // Event constructors also mostly disabled until
+ // https://github.com/whatwg/html/issues/2296 is sorted out.
+ var eventCtors = [ /* Event, */ ErrorEvent /*, MouseEvent */ ];
+ var values = [true, false, "", "abc", {}, 0, 1, -1, null, undefined,
+ 2.5, NaN, Infinity, Symbol.toStringTag ];
+ // Event types also mostly disabled pending
+ // https://github.com/whatwg/html/issues/2296
+ var eventTypes = [ "error"/*, "click", "load"*/ ];
+
+ // Variables that keep track of which subtest we're running.
+ var curTarget;
+ var curValue;
+ var curCtor;
+ var curType;
+
+ function defaultPreventedTester(event) {
+ var expectedValue;
+ if (curTarget === frames[0] &&
+ curCtor === ErrorEvent &&
+ curValue === true &&
+ curType == "error") {
+ expectedValue = true;
+ } else {
+ // This will need adjusting once we allow more targets and event
+ // constructors above!
+ expectedValue = false;
+ }
+ var valueRepr;
+ if (typeof curValue == "string") {
+ valueRepr = '"' + curValue + '"';
+ } else {
+ valueRepr = String(curValue);
+ }
+ test(function() {
+ assert_equals(event.defaultPrevented, expectedValue);
+ }, "Returning " + valueRepr +
+ " from " + String(curTarget) + "'s on" + curType +
+ " event handler while " + curCtor.name +
+ " is firing should" +
+ (expectedValue ? "" : " not") +
+ " cancel the event");
+ }
+
+ for (curCtor of eventCtors) {
+ for (curTarget of targets) {
+ for (curType of eventTypes) {
+ for (curValue of values) {
+ // We have to make sure that defaultPreventedTester is added after
+ // our event handler.
+ curTarget["on" + curType] = function() { return curValue; }
+ curTarget.addEventListener(curType, defaultPreventedTester);
+ var e = new curCtor(curType, { cancelable: true });
+ curTarget.dispatchEvent(e);
+ curTarget["on" + curType] = null;
+ curTarget.removeEventListener(curType, defaultPreventedTester);
+ }
+ }
+ }
+ }
+ }, "event handler cancellation behavior");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/inline-event-handler-ordering.html b/testing/web-platform/tests/html/webappapis/scripting/events/inline-event-handler-ordering.html
new file mode 100644
index 0000000000..aae0f1abf8
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/inline-event-handler-ordering.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Inline event handlers retain their ordering even when invalid</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+setup({ allow_uncaught_exception: true });
+var events = [];
+
+test(function() {
+ events = [];
+ var e = document.createElement("div");
+ document.body.appendChild(e);
+ e.addEventListener("click", function() { events.push("ONE") });
+ e.setAttribute("onclick", "window.open(");
+ e.addEventListener("click", function() { events.push("THREE") });
+ // Try to compile the event handler.
+ e.onclick;
+ e.setAttribute("onclick", "events.push('TWO')");
+ e.dispatchEvent(new Event("click"));
+ var expected_events = ["ONE", "TWO", "THREE"];
+ assert_array_equals(events, expected_events);
+}, "Inline event handlers retain their ordering when invalid and force-compiled");
+
+test(function() {
+ events = [];
+ var e = document.createElement("div");
+ document.body.appendChild(e);
+ e.addEventListener("click", function() { events.push("ONE") });
+ e.setAttribute("onclick", "window.open(");
+ e.addEventListener("click", function() { events.push("THREE") });
+ e.dispatchEvent(new Event("click"));
+ e.setAttribute("onclick", "events.push('TWO')");
+ e.dispatchEvent(new Event("click"));
+ var expected_events = ["ONE", "THREE", "ONE", "TWO", "THREE"];
+ assert_array_equals(events, expected_events);
+}, "Inline event handlers retain their ordering when invalid and force-compiled via dispatch");
+
+test(function() {
+ events = [];
+ var e = document.createElement("div");
+ document.body.appendChild(e);
+ e.addEventListener("click", function() { events.push("ONE") });
+ e.setAttribute("onclick", "window.open(");
+ e.addEventListener("click", function() { events.push("THREE") });
+ e.setAttribute("onclick", "events.push('TWO')");
+ e.dispatchEvent(new Event("click"));
+ var expected_events = ["ONE", "TWO", "THREE"];
+ assert_array_equals(events, expected_events);
+}, "Inline event handlers retain their ordering when invalid and lazy-compiled");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-late.window.js b/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-late.window.js
new file mode 100644
index 0000000000..2892a4c3ab
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-late.window.js
@@ -0,0 +1,16 @@
+setup({ allow_uncaught_exception: true });
+
+test(function() {
+ var events = [];
+ window.onerror = function() {
+ events.push("error");
+ };
+
+ var div = document.createElement("div");
+ div.addEventListener("click", function (e) { events.push("click 1") });
+ div.setAttribute("onclick", "}");
+ div.addEventListener("click", function (e) { events.push("click 2") });
+ div.dispatchEvent(new Event("click"));
+ assert_equals(div.onclick, null);
+ assert_array_equals(events, ["click 1", "error", "click 2"]);
+}, "Invalid uncompiled raw handlers should only be compiled when about to call them");
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-once.window.js b/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-once.window.js
new file mode 100644
index 0000000000..b39b54b0e9
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-once.window.js
@@ -0,0 +1,14 @@
+setup({ allow_uncaught_exception: true });
+
+var errors = 0;
+window.onerror = function() {
+ errors++;
+};
+
+test(function() {
+ var e = document.body;
+ e.setAttribute("onclick", "window.open(");
+ assert_equals(e.onclick, null);
+ assert_equals(e.onclick, null);
+ assert_equals(errors, 1);
+}, "Invalid uncompiled raw handlers should only be compiled once");
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-keeps-position.window.js b/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-keeps-position.window.js
new file mode 100644
index 0000000000..f9443bf99a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-keeps-position.window.js
@@ -0,0 +1,20 @@
+setup({ allow_uncaught_exception: true });
+
+test(function() {
+ var events = [];
+ window.onerror = function() {
+ events.push("error");
+ };
+
+ var div = document.createElement("div");
+ div.addEventListener("click", function (e) { events.push("click 1"); });
+ div.setAttribute("onclick", "}");
+ div.addEventListener("click", function (e) { events.push("click 3"); });
+ assert_equals(div.onclick, null);
+ assert_array_equals(events, ["error"]);
+
+ events = [];
+ div.onclick = function (e) { events.push("click 2"); };
+ div.dispatchEvent(new Event("click"));
+ assert_array_equals(events, ["click 1", "click 2", "click 3"]);
+}, "Compiling invalid uncompiled raw handlers should keep the position in event listener list");
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/messageevent-constructor.https.html b/testing/web-platform/tests/html/webappapis/scripting/events/messageevent-constructor.https.html
new file mode 100644
index 0000000000..2d62019dbf
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/messageevent-constructor.https.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<title>MessageEvent constructor</title>
+<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>
+test(function() {
+ assert_throws_js(
+ TypeError,
+ () => MessageEvent(""),
+ "Calling MessageEvent constructor without 'new' must throw"
+ );
+}, "MessageEvent constructor called as normal function");
+
+test(function() {
+ var ev = new MessageEvent("test")
+ assert_equals(ev.type, "test", "type attribute")
+ assert_equals(ev.target, null, "target attribute")
+ assert_equals(ev.currentTarget, null, "currentTarget attribute")
+ assert_equals(ev.eventPhase, Event.NONE, "eventPhase attribute")
+ assert_equals(ev.bubbles, false, "bubbles attribute")
+ assert_equals(ev.cancelable, false, "cancelable attribute")
+ assert_equals(ev.defaultPrevented, false, "defaultPrevented attribute")
+ assert_equals(ev.isTrusted, false, "isTrusted attribute")
+ assert_true(ev.timeStamp > 0, "timeStamp attribute")
+ assert_true("initMessageEvent" in ev, "initMessageEvent operation")
+ assert_equals(ev.data, null, "data attribute")
+ assert_equals(ev.origin, "", "origin attribute")
+ assert_equals(ev.lastEventId, "", "lastEventId attribute")
+ assert_equals(ev.source, null, "source attribute")
+ assert_array_equals(ev.ports, [], "ports attribute")
+}, "Default event values")
+
+test(function() {
+ var channel = new MessageChannel()
+ var ev = new MessageEvent("test", { data: "testData", origin: "testOrigin", lastEventId: "testId", source: window, ports: [channel.port1] })
+ assert_equals(ev.type, "test", "type attribute")
+ assert_equals(ev.data, "testData", "data attribute")
+ assert_equals(ev.origin, "testOrigin", "origin attribute")
+ assert_equals(ev.lastEventId, "testId", "lastEventId attribute")
+ assert_equals(ev.source, window, "source attribute")
+ assert_array_equals(ev.ports, [channel.port1], "ports attribute")
+}, "MessageEventInit dictionary")
+
+test(function() {
+ assert_throws_js(TypeError, function() {
+ new MessageEvent("test", { ports: null })
+ })
+}, "Passing null for ports member")
+
+test(function() {
+ var ev = new MessageEvent("test", { ports: [] })
+ assert_true(Array.isArray(ev.ports), "Array.isArray() should return true")
+ assert_true(Object.isFrozen(ev.ports), "Object.isFrozen() should return true")
+ assert_equals(ev.ports, ev.ports, "ev.ports should return the same object")
+
+ const oldPorts = ev.ports;
+ ev.initMessageEvent("test", false, false, null, "", "", null, ev.ports);
+ assert_not_equals(oldPorts, ev.ports, "initMessageEvent() changes ev.ports");
+}, "ports attribute should be a FrozenArray")
+
+test(function() {
+ var ev = document.createEvent("messageevent");
+ var channel = new MessageChannel()
+ ev.initMessageEvent("test", true, false, "testData", "testOrigin", "testId", window, [channel.port1])
+ assert_equals(ev.type, "test", "type attribute")
+ assert_equals(ev.bubbles, true, "bubbles attribute")
+ assert_equals(ev.cancelable, false, "bubbles attribute")
+ assert_equals(ev.data, "testData", "data attribute")
+ assert_equals(ev.origin, "testOrigin", "origin attribute")
+ assert_equals(ev.lastEventId, "testId", "lastEventId attribute")
+ assert_equals(ev.source, window, "source attribute")
+ assert_array_equals(ev.ports, [channel.port1], "ports attribute")
+}, "initMessageEvent operation")
+
+test(function() {
+ var ev = document.createEvent("messageevent")
+ assert_throws_js(TypeError, function() {
+ ev.initMessageEvent("test", true, false, "testData", "testOrigin", "testId", window, null)
+ })
+}, "Passing null for ports parameter to initMessageEvent")
+
+test(function() {
+ var ev = document.createEvent("messageevent")
+ assert_equals(MessageEvent.prototype.initMessageEvent.length, 1, "MessageEvent.prototype.initMessageEvent.length should be 1")
+ ev.initMessageEvent("test")
+ assert_equals(ev.type, "test", "type attribute")
+ assert_equals(ev.bubbles, false, "bubbles attribute")
+ assert_equals(ev.cancelable, false, "bubbles attribute")
+ assert_equals(ev.data, null, "data attribute")
+ assert_equals(ev.origin, "", "origin attribute")
+ assert_equals(ev.lastEventId, "", "lastEventId attribute")
+ assert_equals(ev.source, null, "source attribute")
+ assert_array_equals(ev.ports, [], "ports attribute")
+}, "initMessageEvent operation default parameter values")
+
+promise_test(function(t) {
+ var worker_url = "/service-workers/service-worker/resources/empty-worker.js";
+ var scope = "/service-workers/service-worker/resources/";
+ var registration;
+
+ return service_worker_unregister_and_register(t, worker_url, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, r.installing, "activated");
+ })
+ .then(function() {
+ var ev = new MessageEvent("test", { source: registration.active });
+ assert_equals(ev.source, registration.active, "source attribute should return the ServiceWorker");
+ service_worker_unregister(t, scope);
+ });
+ }, "Passing ServiceWorker for source member");
+
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler-frame.html b/testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler-frame.html
new file mode 100644
index 0000000000..79e4af3020
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler-frame.html
@@ -0,0 +1,56 @@
+<body></body>
+<script>
+function check1(args, callee) {
+ parent.t.step(function() {
+ parent.assert_equals(callee.length, 5);
+ parent.assert_equals(args.length, 5);
+ parent.assert_equals(args[0], reference_error.message);
+ parent.assert_equals(args[1], reference_error.filename);
+ parent.assert_equals(args[2], reference_error.lineno);
+ parent.assert_equals(args[3], reference_error.colno);
+ parent.assert_equals(args[4], reference_error.error);
+ parent.t.done();
+ });
+}
+
+var reference_error = new ErrorEvent("error", {
+ filename: "error_file.js",
+ lineno: 333,
+ colno: 999,
+ message: "there was an error",
+ error: {nondefault: 'some unusual object'},
+});
+
+parent.t.step(function() {
+ document.body.outerHTML = "<body onerror='check1(arguments, arguments.callee)'></body>"
+ window.dispatchEvent(reference_error);
+});
+
+function check2(args, callee) {
+ parent.t2.step(function() {
+ parent.assert_equals(callee.length, 5);
+ parent.assert_equals(args.length, 1);
+ parent.assert_false(args[0] instanceof ErrorEvent);
+ parent.t2.done()
+ });
+}
+
+parent.t2.step(function() {
+ document.body.outerHTML = "<body onerror='check2(arguments, arguments.callee)'></body>"
+ window.dispatchEvent(new Event("error"));
+});
+
+function check3(args, callee) {
+ parent.t3.step(function() {
+ parent.assert_equals(args.length, 1);
+ parent.assert_equals(callee.length, 1);
+ });
+}
+
+parent.t3.step(function() {
+ document.body.outerHTML = "<body><span onerror='check3(arguments, arguments.callee)'></span></body>"
+ document.body.firstChild.dispatchEvent(reference_error);
+ document.body.firstChild.dispatchEvent(new Event("error"));
+ parent.t3.done();
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler.html b/testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler.html
new file mode 100644
index 0000000000..60fc674d57
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>OnErrorEventHandler + ErrorEvent is treated differently</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+var t = async_test("onerror + ErrorEvent + Window");
+var t2 = async_test("onerror + !ErrorEvent + Window");
+var t3 = async_test("onerror + Document");
+</script>
+<iframe src="onerroreventhandler-frame.html"></iframe>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/resources/compiled-event-handler-settings-objects-support.html b/testing/web-platform/tests/html/webappapis/scripting/events/resources/compiled-event-handler-settings-objects-support.html
new file mode 100644
index 0000000000..d40c0b9cce
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/resources/compiled-event-handler-settings-objects-support.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>This will be in an iframe</title>
+
+<script>
+window.name = "iframe";
+</script>
+
+<body onbeforeunload="return { toString: parent.postMessage.bind(parent, 'PASS', '*') };">
+
+<!-- window.open() uses the entry settings object to determine how the URL will be parsed -->
+<button onclick="var w = window.open('open-window.html'); w.onload = () => { parent.onWindowLoaded(w.document.URL); };">This will be clicked</button>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/resources/event-handler-body.js b/testing/web-platform/tests/html/webappapis/scripting/events/resources/event-handler-body.js
new file mode 100644
index 0000000000..d7889e230e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/resources/event-handler-body.js
@@ -0,0 +1,61 @@
+const windowReflectingBodyElementEventHandlerSet =
+ new Set(['blur', 'error', 'focus', 'load', 'resize', 'scroll']);
+
+function handlersInInterface(mainIDL, name) {
+ return mainIDL.find(idl => idl.name === name).members.map(member => member.name.slice(2));
+}
+
+const handlersListPromise = fetch("/interfaces/html.idl").then(res => res.text()).then(htmlIDL => {
+ const parsedHTMLIDL = WebIDL2.parse(htmlIDL);
+ const windowEventHandlers = handlersInInterface(parsedHTMLIDL, "WindowEventHandlers");
+ const globalEventHandlers = handlersInInterface(parsedHTMLIDL, "GlobalEventHandlers");
+
+ const shadowedHandlers = [
+ ...windowReflectingBodyElementEventHandlerSet,
+ ...windowEventHandlers
+ ];
+ const notShadowedHandlers = globalEventHandlers.filter(name => !windowReflectingBodyElementEventHandlerSet.has(name));
+ return {
+ shadowedHandlers,
+ notShadowedHandlers
+ };
+});
+
+function eventHandlerTest(shadowedHandlers, notShadowedHandlers, element) {
+ const altBody = document.createElement(element);
+ for (const [des, obj1, obj2, obj3, des1, des2, des3] of [
+ ["document.body", document.body, altBody, window, "body", "alternative body", "window"],
+ [`document.createElement("${element}")`, altBody, document.body, window, "alternative body", "body", "window"],
+ ["window", window, document.body, altBody, "window", "body", "alternative body"]
+ ]) {
+ const f = () => 0;
+
+ shadowedHandlers.forEach(handler => {
+ const eventHandler = obj1['on' + handler];
+ test(() => {
+ obj1['on' + handler] = f;
+ assert_equals(obj2['on' + handler], f, `${des2} should reflect`);
+ assert_equals(obj3['on' + handler], f, `${des3} should reflect`);
+ }, `shadowed ${handler} (${des})`);
+ obj1['on' + handler] = eventHandler;
+ });
+
+ notShadowedHandlers.forEach(handler => {
+ const eventHandler = obj1['on' + handler];
+ test(() => {
+ obj1['on' + handler] = f;
+ assert_equals(obj2['on' + handler], null, `${des2} should reflect`);
+ assert_equals(obj3['on' + handler], null, `${des3} should reflect`);
+ }, `not shadowed ${handler} (${des})`);
+ obj1['on' + handler] = eventHandler;
+ });
+
+ shadowedHandlers.forEach(handler => {
+ test(() => {
+ assert_equals(obj1['on' + handler], null, `${des1} should reflect changes to itself`);
+ assert_equals(obj2['on' + handler], null, `${des2} should reflect`);
+ assert_equals(obj3['on' + handler], null, `${des3} should reflect`);
+ }, `shadowed ${handler} removal (${des})`);
+ });
+ }
+}
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/resources/open-window.html b/testing/web-platform/tests/html/webappapis/scripting/events/resources/open-window.html
new file mode 100644
index 0000000000..1d23263570
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/resources/open-window.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>This window will open during the course of the test</title>
+<h1>Hello</h1>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/uncompiled_event_handler_with_scripting_disabled.html b/testing/web-platform/tests/html/webappapis/scripting/events/uncompiled_event_handler_with_scripting_disabled.html
new file mode 100644
index 0000000000..a912b32d7f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/events/uncompiled_event_handler_with_scripting_disabled.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Uncompiled event handler check that scripting is enabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({ allow_uncaught_exception: true });
+ test(function() {
+ var invoked = false;
+ window.addEventListener("error", function() {
+ invoked = true;
+ });
+
+ // Make sure that `this_will_error` will in fact error when it's referenced
+ assert_equals(typeof this_will_error, "undefined");
+ var dom = (new DOMParser()).parseFromString("<div id=\"has-event-handler\" onclick=\"this_will_error;\"></div>", "text/html");
+ var click = new MouseEvent("click");
+ dom.getElementById("has-event-handler").dispatchEvent(click);
+ assert_equals(invoked, false);
+ }, "when scripting is disabled, the handler is never compiled");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/addEventListener.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/addEventListener.html
new file mode 100644
index 0000000000..dbb1cdd5a9
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/addEventListener.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - addEventListener</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var ran = false;
+ window.addEventListener('error', t.step_func(function(e){
+ ran = true;
+ assert_true(e.isTrusted, 'isTrusted');
+ }), false);
+ </script>
+ <script>
+ undefined_variable;
+ </script>
+ <script>
+ for (;) {}
+ </script>
+ <script>
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error-data-url.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error-data-url.html
new file mode 100644
index 0000000000..66e1dfed4d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error-data-url.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html>
+ <head>
+ <title>&lt;body onerror> - compile error in &lt;script src=data:...></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ </script>
+ <body onerror="
+ t.step(function(){
+ ran = true;
+ assert_equals(typeof event, 'string', 'first arg');
+ assert_equals(source, 'data:text/javascript,for(;){}', 'second arg');
+ assert_equals(typeof lineno, 'number', 'third arg');
+ });
+ t_col.step(function() {
+ assert_equals(typeof colno, 'number', 'fourth arg');
+ });
+ ">
+ <div id=log></div>
+ <script src="data:text/javascript,for(;){}"></script>
+ <script>
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ t_col.done();
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error.html
new file mode 100644
index 0000000000..0f65f73999
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<html>
+ <head>
+ <title>&lt;body onerror> - compile error in &lt;script></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ </script>
+ <body onerror="
+ t.step(function(){
+ ran = true;
+ assert_equals(typeof event, 'string', 'first arg');
+ assert_equals(source, location.href, 'second arg');
+ assert_equals(typeof lineno, 'number', 'third arg');
+ });
+ t_col.step(function() {
+ assert_equals(typeof colno, 'number', 'fourth arg');
+ });
+ ">
+ <div id=log></div>
+ <script>
+ for(;) {}
+ </script>
+ <script>
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ t_col.done();
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-runtime-error.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-runtime-error.html
new file mode 100644
index 0000000000..faaddd9ed9
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-runtime-error.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<html>
+ <head>
+ <title>&lt;body onerror> - runtime error in &lt;script></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ </script>
+ <body onerror="
+ t.step(function(){
+ ran = true;
+ assert_equals(typeof event, 'string', 'first arg');
+ assert_equals(source, location.href, 'second arg');
+ assert_equals(typeof lineno, 'number', 'third arg');
+ });
+ t_col.step(function(){
+ assert_equals(typeof colno, 'number', 'fourth arg');
+ });
+ ">
+ <div id=log></div>
+ <script>
+ undefined_variable;
+ </script>
+ <script>
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ t_col.done();
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setInterval.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setInterval.html
new file mode 100644
index 0000000000..c4028e650b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setInterval.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - compile error in cross-origin setInterval</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var ran = false;
+ var interval;
+ window.addEventListener('error', t.step_func(e => {
+ clearInterval(interval);
+ ran = true;
+ assert_equals(e.error.constructor, SyntaxError);
+ }));
+ var script = document.createElement('script');
+ script.src = location.href.replace('://', '://www1.').replace(/\/[^\/]+$/, '/support/syntax-error-in-setInterval.js');
+ document.body.appendChild(script);
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setTimeout.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setTimeout.html
new file mode 100644
index 0000000000..1eebf82fbb
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setTimeout.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - compile error in cross-origin setTimeout</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var ran = false;
+ window.addEventListener('error', t.step_func(e => {
+ ran = true;
+ assert_equals(e.error.constructor, SyntaxError);
+ }));
+ var script = document.createElement('script');
+ script.src = location.href.replace('://', '://www1.').replace(/\/[^\/]+$/, '/support/syntax-error-in-setTimeout.js');
+ document.body.appendChild(script);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin.html
new file mode 100644
index 0000000000..b7e989529f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - compile error in &lt;script src=//www1...></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(a, 'Script error.', 'first arg');
+ assert_equals(b, '', 'second arg');
+ assert_equals(c, 0, 'third arg');
+ });
+ var script = document.createElement('script');
+ script.src = location.href.replace('://', '://www1.').replace(/\/[^\/]+$/, '/support/syntax-error.js');
+ document.body.appendChild(script);
+ onload = function(){
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(col_value, 0, 'fourth arg');
+ t_col.done();
+ });
+ };
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-data-url.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-data-url.html
new file mode 100644
index 0000000000..08ce2f348f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-data-url.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - compile error in &lt;script src=data:...></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, 'data:text/javascript,for(;){}', 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ </script>
+ <script src="data:text/javascript,for(;){}"></script>
+ <script>
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-attribute.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-attribute.html
new file mode 100644
index 0000000000..864d09fc1e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-attribute.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - compile error in attribute</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, location.href, 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ </script>
+ <p onclick="{"></p>
+ <script>
+ t.step(function(){
+ var ev = document.createEvent('Event');
+ ev.initEvent('click', false, false);
+ document.querySelector('p').dispatchEvent(ev);
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-body-onerror.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-body-onerror.html
new file mode 100644
index 0000000000..0b094e71c3
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-body-onerror.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - compile error in &lt;body onerror></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var ran = false;
+ window.onerror = t.step_func(function(){
+ ran = true;
+ });
+ </script>
+ </head>
+ <body onerror="{"><!-- sets the event handler to null before compiling -->
+ <div id=log></div>
+ <script>
+ for(;) {}
+ </script>
+ <script>
+ t.step(function(){
+ assert_false(ran, 'ran');
+ t.done();
+ });
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setInterval.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setInterval.html
new file mode 100644
index 0000000000..79ca7d524a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setInterval.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - compile error in setInterval</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ var interval;
+ window.onerror = t.step_func(function(a, b, c, d){
+ clearInterval(interval);
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, location.href, 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ interval = setInterval("{", 10);
+ step_timeout(function(){
+ t.step(function(){
+ clearInterval(interval);
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ }, 20);
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setTimeout.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setTimeout.html
new file mode 100644
index 0000000000..1bb730e134
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setTimeout.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - compile error in setTimeout</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, location.href, 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ setTimeout("{", 10);
+ setTimeout(function(){
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ }, 20);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin-with-hash.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin-with-hash.html
new file mode 100644
index 0000000000..c367e6cb2f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin-with-hash.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - compile error in &lt;script src=...> with hash</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, document.querySelector('script[src="support/syntax-error.js#"]').src, 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ </script>
+ <script src="support/syntax-error.js#"></script>
+ <script>
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin.html
new file mode 100644
index 0000000000..71c28b584d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - compile error in &lt;script src=...></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, document.querySelector('script[src="support/syntax-error.js"]').src, 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ </script>
+ <script src="support/syntax-error.js"></script>
+ <script>
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error.html
new file mode 100644
index 0000000000..a4bdfd9c47
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - compile error in &lt;script></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, location.href, 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ </script>
+ <script>
+ for(;) {}
+ </script>
+ <script>
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js
new file mode 100644
index 0000000000..3a3ea40d77
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js
@@ -0,0 +1,25 @@
+// META: global=window,dedicatedworker
+
+promise_test(async () => {
+ const sab = new SharedArrayBuffer(64);
+ const ta = new Int32Array(sab);
+
+ const waitAsyncObj = Atomics.waitAsync(ta, 0, 0, 10);
+ assert_equals(waitAsyncObj.async, true);
+ const v = await waitAsyncObj.value;
+ assert_equals(v, "timed-out");
+}, `Atomics.waitAsync timeout in a ${self.constructor.name}`);
+
+promise_test(async () => {
+ const sab = new SharedArrayBuffer(64);
+ const ta = new Int32Array(sab);
+
+ const waitAsyncObj = Atomics.waitAsync(ta, 0, 0);
+ assert_equals(waitAsyncObj.async, true);
+
+ const worker = new Worker("resources/notify-worker.js");
+ worker.postMessage(sab);
+
+ const v = await waitAsyncObj.value;
+ assert_equals(v, "ok");
+}, `Atomics.waitAsync notification in a ${self.constructor.name}`);
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js.headers b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js.headers
new file mode 100644
index 0000000000..5f8621ef83
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Opener-Policy: same-origin
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-failure.https.any.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-failure.https.any.js
new file mode 100644
index 0000000000..fddf85dbed
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-failure.https.any.js
@@ -0,0 +1,11 @@
+// META: global=window,serviceworker
+
+test(() => {
+ // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
+ const sab = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer;
+ const ta = new Int32Array(sab);
+
+ assert_throws_js(TypeError, () => {
+ Atomics.wait(ta, 0, 0, 10);
+ });
+}, `[[CanBlock]] in a ${self.constructor.name}`);
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-success.any.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-success.any.js
new file mode 100644
index 0000000000..0da449a7cf
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-success.any.js
@@ -0,0 +1,9 @@
+// META: global=dedicatedworker,sharedworker
+
+test(() => {
+ // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
+ const sab = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer;
+ const ta = new Int32Array(sab);
+
+ assert_equals(Atomics.wait(ta, 0, 0, 10), "timed-out");
+}, `[[CanBlock]] in a ${self.constructor.name}`);
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js
new file mode 100644
index 0000000000..2780e5bc34
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js
@@ -0,0 +1,5 @@
+onmessage = (e) => {
+ const sab = e.data;
+ const ta = new Int32Array(sab);
+ Atomics.notify(ta, 0);
+};
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js.headers b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry-different-function-realm.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry-different-function-realm.html
new file mode 100644
index 0000000000..71f03a4dcf
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry-different-function-realm.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Entry settings object for promise jobs when the function realm is different from the test realm</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!-- https://github.com/whatwg/html/pull/5212 -->
+<!-- https://github.com/whatwg/html/issues/1426 -->
+
+<!-- This is what would normally be considered the entry page. However, we use functions from the
+ resources/function/function.html realm. So window.open() should resolve relative to that realm
+ inside promise jobs. -->
+
+<iframe src="resources/promise-job-entry-incumbent.html"></iframe>
+<iframe src="resources/function/function.html" id="function-frame"></iframe>
+
+<script>
+setup({ explicit_done: true });
+
+const relativeURL = "resources/window-to-open.html";
+const expectedURL = (new URL(relativeURL, document.querySelector("#function-frame").src)).href;
+
+const incumbentWindow = frames[0];
+const functionWindow = frames[1];
+const FunctionFromAnotherWindow = frames[1].Function;
+
+window.onload = () => {
+ async_test(t => {
+ const func = FunctionFromAnotherWindow(`
+ const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = arguments[0];
+
+ const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
+ w.onload = t.step_func_done(() => {
+ t.add_cleanup(() => w.close());
+ assert_equals(w.location.href, expectedURL);
+ });
+ `);
+
+ Promise.resolve([incumbentWindow, relativeURL, t, assert_equals, expectedURL]).then(func);
+ }, "Fulfillment handler on fulfilled promise");
+
+ async_test(t => {
+ const func = FunctionFromAnotherWindow(`
+ const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = arguments[0];
+
+ const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
+ w.onload = t.step_func_done(() => {
+ t.add_cleanup(() => w.close());
+ assert_equals(w.location.href, expectedURL);
+ });
+ `);
+
+ Promise.reject([incumbentWindow, relativeURL, t, assert_equals, expectedURL]).catch(func);
+ }, "Rejection handler on rejected promise");
+
+ async_test(t => {
+ let resolve;
+ const p = new Promise(r => { resolve = r; });
+
+ const func = FunctionFromAnotherWindow(`
+ const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = arguments[0];
+
+ const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
+ w.onload = t.step_func_done(() => {
+ t.add_cleanup(() => w.close());
+ assert_equals(w.location.href, expectedURL);
+ });
+ `);
+
+ p.then(func);
+ t.step_timeout(() => resolve([incumbentWindow, relativeURL, t, assert_equals, expectedURL]), 0);
+ }, "Fulfillment handler on pending-then-fulfilled promise");
+
+ async_test(t => {
+ let reject;
+ const p = new Promise((_, r) => { reject = r; });
+
+ const func = FunctionFromAnotherWindow(`
+ const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = arguments[0];
+
+ const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
+ w.onload = t.step_func_done(() => {
+ t.add_cleanup(() => w.close());
+ assert_equals(w.location.href, expectedURL);
+ });
+ `);
+
+ p.catch(func);
+ t.step_timeout(() => reject([incumbentWindow, relativeURL, t, assert_equals, expectedURL]), 0);
+ }, "Rejection handler on pending-then-rejected promise");
+
+ async_test(t => {
+ t.add_cleanup(() => { delete frames[1].args; });
+ frames[1].args = [incumbentWindow, relativeURL, t, assert_equals, expectedURL];
+
+ const func = FunctionFromAnotherWindow(`
+ const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = window.args;
+
+ const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
+ w.onload = t.step_func_done(() => {
+ t.add_cleanup(() => w.close());
+ assert_equals(w.location.href, expectedURL);
+ });
+ `);
+
+ const thenable = { then: func };
+
+ Promise.resolve(thenable);
+ }, "Thenable resolution");
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry.html
new file mode 100644
index 0000000000..6d075d674c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Entry settings object for promise jobs</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!-- https://github.com/whatwg/html/pull/5212 -->
+<!-- https://github.com/whatwg/html/issues/1426 -->
+
+<!-- This is the entry page, so window.open() should resolve relative to it, even inside promise jobs. -->
+
+<iframe src="resources/promise-job-entry-incumbent.html"></iframe>
+
+<script>
+setup({ explicit_done: true });
+
+const relativeURL = "resources/window-to-open.html";
+const expectedURL = (new URL(relativeURL, location.href)).href;
+
+const incumbentWindow = frames[0];
+
+window.onload = () => {
+ async_test(t => {
+ const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
+ w.onload = t.step_func_done(() => {
+ t.add_cleanup(() => w.close());
+ assert_equals(w.location.href, expectedURL);
+ });
+ }, "Sanity check: this all works as expected with no promises involved");
+
+ async_test(t => {
+ // No t.step_func because that could change the realms
+ Promise.resolve().then(() => {
+ const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
+ w.onload = t.step_func_done(() => {
+ t.add_cleanup(() => w.close());
+ assert_equals(w.location.href, expectedURL);
+ });
+ });
+ }, "Fulfillment handler on fulfilled promise");
+
+ async_test(t => {
+ // No t.step_func because that could change the realms
+ Promise.reject().catch(() => {
+ const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
+ w.onload = t.step_func_done(() => {
+ t.add_cleanup(() => w.close());
+ assert_equals(w.location.href, expectedURL);
+ });
+ });
+ }, "Rejection handler on rejected promise");
+
+ async_test(t => {
+ let resolve;
+ const p = new Promise(r => { resolve = r; });
+
+ // No t.step_func because that could change the realms
+ p.then(() => {
+ const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
+ w.onload = t.step_func_done(() => {
+ t.add_cleanup(() => w.close());
+ assert_equals(w.location.href, expectedURL);
+ });
+ });
+
+ t.step_timeout(resolve, 0);
+ }, "Fulfillment handler on pending-then-fulfilled promise");
+
+ async_test(t => {
+ let reject;
+ const p = new Promise((_, r) => { reject = r; });
+
+ // No t.step_func because that could change the realms
+ p.catch(() => {
+ const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
+ w.onload = t.step_func_done(() => {
+ t.add_cleanup(() => w.close());
+ assert_equals(w.location.href, expectedURL);
+ });
+ });
+
+ t.step_timeout(reject, 0);
+ }, "Rejection handler on pending-then-rejected promise");
+
+ async_test(t => {
+ const thenable = {
+ // No t.step_func because that could change the realms
+ then(f) {
+ const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL);
+ w.onload = t.step_func_done(() => {
+ t.add_cleanup(() => w.close());
+ assert_equals(w.location.href, expectedURL);
+ });
+ }
+ };
+
+ Promise.resolve(thenable);
+ }, "Thenable resolution");
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-incumbent.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-incumbent.html
new file mode 100644
index 0000000000..af00f834c1
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-incumbent.html
@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Incumbent settings object for promise jobs</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- This is the entry page. -->
+
+<iframe src="resources/promise-job-incumbent-incumbent.html"></iframe>
+<iframe src="resources/promise-job-incumbent-resolver.html"></iframe>
+
+<script>
+setup({ explicit_done: true });
+
+// postMessage should pick the incumbent page as its .source value to set on the MessageEvent, even
+// inside promise jobs.
+const expectedURL = (new URL("resources/promise-job-incumbent-incumbent.html", location.href)).href;
+
+let testId = 0;
+
+window.onload = () => {
+ const relevantWindow = frames[0].document.querySelector("#r").contentWindow;
+ const runInResolver = frames[1].runWhatYouGiveMe;
+
+ function setupTest(t) {
+ ++testId;
+ const thisTestId = testId;
+
+ relevantWindow.addEventListener("messagereceived", t.step_func(e => {
+ const [receivedTestId, receivedSourceURL] = e.detail;
+
+ if (receivedTestId !== thisTestId) {
+ return;
+ }
+
+ assert_equals(receivedSourceURL, expectedURL);
+ t.done();
+ }));
+
+ return thisTestId;
+ }
+
+ async_test(t => {
+ const thisTestId = setupTest(t);
+
+ frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*");
+ }, "Sanity check: this all works as expected with no promises involved");
+
+ async_test(t => {
+ const thisTestId = setupTest(t);
+
+ // No t.step_func because that could change the realms
+ Promise.resolve().then(() => {
+ frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*");
+ });
+ }, "Fulfillment handler on fulfilled promise");
+
+ async_test(t => {
+ const thisTestId = setupTest(t);
+
+ const p = Promise.resolve();
+ frames[0].runWindowPostMessageVeryIndirectlyWithNoUserCode(p, "then", thisTestId, "*");
+ }, "Fulfillment handler on fulfilled promise, using backup incumbent settings object stack");
+
+ async_test(t => {
+ const thisTestId = setupTest(t);
+
+ // No t.step_func because that could change the realms
+ Promise.reject().catch(() => {
+ frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*");
+ });
+ }, "Rejection handler on rejected promise");
+
+ async_test(t => {
+ const thisTestId = setupTest(t);
+
+ const p = Promise.reject();
+ frames[0].runWindowPostMessageVeryIndirectlyWithNoUserCode(p, "catch", thisTestId, "*");
+ }, "Rejection handler on rejected promise, using backup incumbent settings object stack");
+
+ // The following tests test that we derive the incumbent settings object at promise-job time from
+ // the incumbent realm at the time the handler was added, not at the time the resolve()/reject()
+ // was done. See https://github.com/whatwg/html/issues/5213 for the spec side of this issue.
+
+ async_test(t => {
+ const thisTestId = setupTest(t);
+
+ let resolve;
+ const p = new Promise(r => { resolve = r; });
+
+ // No t.step_func because that could change the realms
+ p.then(() => {
+ frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*");
+ });
+
+ t.step_timeout(() => {
+ runInResolver(resolve);
+ }, 0);
+ }, "Fulfillment handler on pending-then-fulfilled promise");
+
+ async_test(t => {
+ const thisTestId = setupTest(t);
+
+ let resolve;
+ const p = new Promise(r => { resolve = r; });
+
+ frames[0].runWindowPostMessageVeryIndirectlyWithNoUserCode(p, "then", thisTestId, "*");
+
+ t.step_timeout(() => {
+ runInResolver(resolve);
+ }, 0);
+ }, "Fulfillment handler on pending-then-fulfilled promise, using backup incumbent settings object stack");
+
+ async_test(t => {
+ const thisTestId = setupTest(t);
+
+ let reject;
+ const p = new Promise((_, r) => { reject = r; });
+
+ // No t.step_func because that could change the realms
+ p.catch(() => {
+ frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*");
+ });
+
+ t.step_timeout(() => {
+ runInResolver(reject);
+ }, 0);
+ }, "Rejection handler on pending-then-rejected promise");
+
+ async_test(t => {
+ const thisTestId = setupTest(t);
+
+ let reject;
+ const p = new Promise((_, r) => { reject = r; });
+
+ frames[0].runWindowPostMessageVeryIndirectlyWithNoUserCode(p, "catch", thisTestId, "*");
+
+ t.step_timeout(() => {
+ runInResolver(reject);
+ }, 0);
+ }, "Rejection handler on pending-then-rejected promise, using backup incumbent settings object stack");
+
+ async_test(t => {
+ const thisTestId = setupTest(t);
+
+ const thenable = {
+ // No t.step_func because that could change the realms
+ then(f) {
+ frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*");
+ }
+ };
+
+ Promise.resolve(thenable);
+ }, "Thenable resolution");
+
+ async_test(t => {
+ const thisTestId = setupTest(t);
+
+ frames[0].resolveThenableThatRunsWindowPostMessageVeryIndirectlyWithNoUserCode(testId, "*", []);
+ }, "Thenable resolution, using backup incumbent settings object stack");
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/README.md b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/README.md
new file mode 100644
index 0000000000..a89258a4e0
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/README.md
@@ -0,0 +1,5 @@
+A couple notes about the files scattered in this `resources/` directory:
+
+* The nested directory structure is necessary here so that relative URL resolution can be tested; we need different sub-paths for each document.
+
+* The semi-duplicate `window-to-open.html`s scattered throughout are present because Firefox, at least, does not fire `Window` `load` events for 404s, so we want to ensure that no matter which global is used, `window`'s `load` event is hit and our tests can proceed.
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/current.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/current.html
new file mode 100644
index 0000000000..63d9c437fc
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/current.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Current page used as a test helper</title>
+
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/resources/window-to-open.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/resources/window-to-open.html
new file mode 100644
index 0000000000..1bc4cca9a3
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/resources/window-to-open.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>If the current settings object is used this page will be opened</title>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/function.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/function.html
new file mode 100644
index 0000000000..15841d387d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/function.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Realm for a "then" function used as a test helper</title>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/resources/window-to-open.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/resources/window-to-open.html
new file mode 100644
index 0000000000..3928c1f8aa
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/resources/window-to-open.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>If the function's settings object is used this page will be opened</title>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-entry-incumbent.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-entry-incumbent.html
new file mode 100644
index 0000000000..3740c1467d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-entry-incumbent.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Incumbent page used as a test helper</title>
+
+<iframe src="relevant/relevant.html" id="r"></iframe>
+<iframe src="current/current.html" id="c"></iframe>
+
+<script>
+ const relevant = document.querySelector("#r");
+ const current = document.querySelector("#c");
+
+ window.runWindowOpenVeryIndirectly = (...args) => {
+ return current.contentWindow.open.call(relevant.contentWindow, ...args);
+ };
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-incumbent.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-incumbent.html
new file mode 100644
index 0000000000..57dd5dff10
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-incumbent.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Incumbent page used as a test helper</title>
+
+<iframe src="relevant/relevant.html" id="r"></iframe>
+<iframe src="current/current.html" id="c"></iframe>
+
+<script>
+ const relevant = document.querySelector("#r");
+ const current = document.querySelector("#c");
+
+ window.runWindowPostMessageVeryIndirectly = (...args) => {
+ return current.contentWindow.postMessage.call(relevant.contentWindow, ...args);
+ };
+
+ // This tests the backup incumbent settings object stack scenario, by avoiding putting user code on the stack.
+ window.runWindowPostMessageVeryIndirectlyWithNoUserCode = (promise, promiseMethod, ...args) => {
+ const runWindowPostMessage = current.contentWindow.postMessage.bind(relevant.contentWindow, ...args);
+ promise[promiseMethod](runWindowPostMessage);
+ };
+
+ window.resolveThenableThatRunsWindowPostMessageVeryIndirectlyWithNoUserCode = (...args) => {
+ Promise.resolve({
+ then: current.contentWindow.postMessage.bind(relevant.contentWindow, ...args)
+ });
+ };
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-resolver.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-resolver.html
new file mode 100644
index 0000000000..a730b9c3ce
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-resolver.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Incumbent page used as a test helper</title>
+
+<script>
+ window.runWhatYouGiveMe = (func) => {
+ func();
+ };
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/relevant.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/relevant.html
new file mode 100644
index 0000000000..f5965f2231
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/relevant.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Relevant page used as a test helper</title>
+
+<script>
+// promise-job-incumbent will end up posting a message to here. We need to signal back the "source".
+
+window.onmessage = e => {
+ const testId = e.data;
+ const sourceURL = e.source.document.URL;
+
+ window.dispatchEvent(new CustomEvent("messagereceived", { detail: [testId, sourceURL] }));
+};
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/resources/window-to-open.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/resources/window-to-open.html
new file mode 100644
index 0000000000..4138b5a084
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/resources/window-to-open.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>If the relevant settings object is used this page will be opened</title>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/resources/window-to-open.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/resources/window-to-open.html
new file mode 100644
index 0000000000..7743b9b578
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/resources/window-to-open.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>If the incumbent settings object is used this page will be opened</title>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/window-to-open.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/window-to-open.html
new file mode 100644
index 0000000000..ce357937f5
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/window-to-open.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>If the entry settings object is used this page will be opened</title>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setInterval.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setInterval.html
new file mode 100644
index 0000000000..8b92f7d148
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setInterval.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - runtime error in cross-origin setInterval</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var ran = false;
+ var interval;
+ window.addEventListener('error', t.step_func(e => {
+ clearInterval(interval);
+ ran = true;
+ assert_equals(e.error.constructor, ReferenceError);
+ }));
+ var script = document.createElement('script');
+ script.src = location.href.replace('://', '://www1.').replace(/\/[^\/]+$/, '/support/undefined-variable-in-setInterval.js');
+ document.body.appendChild(script);
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setTimeout.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setTimeout.html
new file mode 100644
index 0000000000..2e1a9d2315
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setTimeout.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - runtime error in cross-origin setTimeout</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var ran = false;
+ window.addEventListener('error', t.step_func(e => {
+ ran = true;
+ assert_equals(e.error.constructor, ReferenceError);
+ }));
+ var script = document.createElement('script');
+ script.src = location.href.replace('://', '://www1.').replace(/\/[^\/]+$/, '/support/undefined-variable-in-setTimeout.js');
+ document.body.appendChild(script);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin.html
new file mode 100644
index 0000000000..d63aaa6d3b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - runtime error in &lt;script src=//www1...></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(a, 'Script error.', 'first arg');
+ assert_equals(b, '', 'second arg');
+ assert_equals(c, 0, 'third arg');
+ });
+ var script = document.createElement('script');
+ script.src = location.href.replace('://', '://www1.').replace(/\/[^\/]+$/, '/support/undefined-variable.js');
+ document.body.appendChild(script);
+ onload = function(){
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(col_value, 0, 'fourth arg');
+ t_col.done();
+ });
+ };
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-data-url.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-data-url.html
new file mode 100644
index 0000000000..485ce90aa6
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-data-url.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - runtime error in &lt;script src=data:...></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, 'data:text/javascript,undefined_variable;', 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ </script>
+ <script src="data:text/javascript,undefined_variable;"></script>
+ <script>
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-attribute.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-attribute.html
new file mode 100644
index 0000000000..b4f69da7a2
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-attribute.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - runtime error in attribute</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, location.href, 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ </script>
+ <p onclick="undefined_variable;"></p>
+ <script>
+ t.step(function(){
+ var ev = document.createEvent('Event');
+ ev.initEvent('click', false, false);
+ document.querySelector('p').dispatchEvent(ev);
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-body-onerror.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-body-onerror.html
new file mode 100644
index 0000000000..e0fd1dcbd5
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-body-onerror.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+ <head>
+ <title>runtime error in &lt;body onerror></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var ran = 0;
+ </script>
+ </head>
+ <body onerror="ran++; undefined_variable_in_onerror;">
+ <div id=log></div>
+ <script>
+ undefined_variable;
+ </script>
+ <script>
+ t.step(function(){
+ assert_equals(ran, 1, 'ran');
+ t.done();
+ });
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setInterval.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setInterval.html
new file mode 100644
index 0000000000..090e1dd78e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setInterval.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - runtime error in setInterval</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ var interval;
+ window.onerror = t.step_func(function(a, b, c, d){
+ clearInterval(interval);
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, location.href, 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ interval = setInterval("undefined_variable;", 10);
+ step_timeout(function(){
+ clearInterval(interval);
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ }, 20);
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setTimeout.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setTimeout.html
new file mode 100644
index 0000000000..cebcd4346c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setTimeout.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - runtime error in setTimeout</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, location.href, 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ setTimeout("undefined_variable;", 10);
+ setTimeout(function(){
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ }, 20);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-window-onerror.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-window-onerror.html
new file mode 100644
index 0000000000..150a793b79
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-window-onerror.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html>
+ <head>
+ <title>runtime error in window.onerror</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var ran = 0;
+ window.onerror = function(){
+ ran++;
+ undefined_variable_in_onerror;
+ };
+ </script>
+ <script>
+ undefined_variable;
+ </script>
+ <script>
+ t.step(function(){
+ assert_equals(ran, 1, 'ran');
+ t.done();
+ });
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin-with-hash.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin-with-hash.html
new file mode 100644
index 0000000000..dc6ec059a5
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin-with-hash.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - runtime error in &lt;script src=...> with hash</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, document.querySelector('script[src="support/undefined-variable.js#"]').src, 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ </script>
+ <script src="support/undefined-variable.js#"></script>
+ <script>
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin.html
new file mode 100644
index 0000000000..8f3cfb70b2
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - runtime error in &lt;script src=...></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, document.querySelector('script[src="support/undefined-variable.js"]').src, 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ </script>
+ <script src="support/undefined-variable.js"></script>
+ <script>
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error.html
new file mode 100644
index 0000000000..7907494aa6
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror - runtime error in &lt;script></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var t = async_test();
+ var t_col = async_test(document.title+' (column)');
+ var ran = false;
+ var col_value;
+ window.onerror = t.step_func(function(a, b, c, d){
+ ran = true;
+ col_value = d;
+ assert_equals(typeof a, 'string', 'first arg');
+ assert_equals(b, location.href, 'second arg');
+ assert_equals(typeof c, 'number', 'third arg');
+ });
+ </script>
+ <script>
+ undefined_variable;
+ </script>
+ <script>
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+ t_col.step(function(){
+ assert_equals(typeof col_value, 'number', 'fourth arg');
+ t_col.done();
+ });
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setInterval.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setInterval.js
new file mode 100644
index 0000000000..afec114458
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setInterval.js
@@ -0,0 +1,8 @@
+interval = setInterval('{', 10);
+step_timeout(function(){
+ clearInterval(interval);
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+}, 20); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setTimeout.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setTimeout.js
new file mode 100644
index 0000000000..427542b42e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setTimeout.js
@@ -0,0 +1,7 @@
+setTimeout('{', 10);
+setTimeout(function(){
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+}, 20);
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error.js
new file mode 100644
index 0000000000..0f74a6fca6
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error.js
@@ -0,0 +1 @@
+for (;) {} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setInterval.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setInterval.js
new file mode 100644
index 0000000000..c2a017a2ab
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setInterval.js
@@ -0,0 +1,8 @@
+interval = setInterval('undefined_variable;', 10);
+step_timeout(function(){
+ clearInterval(interval);
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+}, 20); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setTimeout.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setTimeout.js
new file mode 100644
index 0000000000..6fa54cda9f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setTimeout.js
@@ -0,0 +1,7 @@
+setTimeout('undefined_variable;', 10);
+setTimeout(function(){
+ t.step(function(){
+ assert_true(ran, 'ran');
+ t.done();
+ });
+}, 20);
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable.js
new file mode 100644
index 0000000000..e73a62ceda
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable.js
@@ -0,0 +1 @@
+undefined_variable; \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/allow-crossorigin.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/allow-crossorigin.html
new file mode 100644
index 0000000000..7524604113
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/allow-crossorigin.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cors/support.js?pipe=sub"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections">
+<link rel="help" href="https://html.spec.whatwg.org/#muted-errors">
+
+<body>
+<script>
+'use strict';
+setup({
+ allow_uncaught_exception: true
+});
+
+async_test(function(t) {
+ addEventListener('unhandledrejection', t.step_func(function(e) {
+ assert_equals(e.reason, 42, 'reason should be the one given by the script');
+ t.done();
+ }));
+}, 'Promise rejection event should be received for the cross-origin CORS script');
+
+(function() {
+ var scriptEl = document.createElement('script');
+ scriptEl.src = CROSSDOMAIN + 'support/promise-access-control.py?allow=true';
+ scriptEl.crossOrigin = 'anonymous';
+ document.body.appendChild(scriptEl);
+}());
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/disallow-crossorigin.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/disallow-crossorigin.html
new file mode 100644
index 0000000000..d61618a53e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/disallow-crossorigin.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cors/support.js?pipe=sub"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections">
+<link rel="help" href="https://html.spec.whatwg.org/#muted-errors">
+
+<body>
+<script>
+'use strict';
+
+setup({
+ allow_uncaught_exception: true
+});
+
+(function() {
+ var resolveLoaded;
+ var loadedPromise = new Promise(function(resolve) { resolveLoaded = resolve; });
+
+ promise_test(function(t) {
+ var unreachedUnhandled = t.unreached_func('unhandledrejection event should never be triggered');
+ var unreachedHandled = t.unreached_func('rejectionhandled event should never be triggered');
+
+ addEventListener('unhandledrejection', unreachedUnhandled);
+ addEventListener('rejectionhandled', unreachedHandled);
+ ensureCleanup(t, unreachedUnhandled, unreachedHandled);
+
+ return loadedPromise.then(t.step_func(function() {
+ return new Promise(function(resolve) {
+ t.step_timeout(function() {
+ resolve();
+ }, 1000);
+ });
+ }));
+ }, 'Promise rejection event should be muted for cross-origin non-CORS script');
+
+ promise_test(function(t) {
+ var unreachedUnhandled = t.unreached_func('unhandledrejection event should never be triggered');
+ var unreachedHandled = t.unreached_func('rejectionhandled event should never be triggered');
+
+ addEventListener('unhandledrejection', unreachedUnhandled);
+ addEventListener('rejectionhandled', unreachedHandled);
+ ensureCleanup(t, unreachedUnhandled, unreachedHandled);
+
+ return new Promise(function(resolve) {
+ handleRejectedPromise(new Promise(function(resolve, reject) { reject(42); }));
+ t.step_timeout(function() {
+ resolve();
+ }, 1000);
+ });
+ }, 'Promise rejection should be muted if the rejected promise is handled in cross-origin non-CORS script');
+
+ promise_test(function(t) {
+ var promise = new Promise(function(resolve, reject) { reject(42); });
+ var resolveReceived;
+ var eventPromise = new Promise(function(resolve) { resolveReceived = resolve; });
+ var unhandled = t.step_func(function(e) {
+ if (e.promise === promise) {
+ handleRejectedPromise(promise);
+ resolveReceived();
+ }
+ });
+ var unreachedHandled = t.unreached_func('rejectionhandled event should never be triggered');
+
+ addEventListener('unhandledrejection', unhandled);
+ addEventListener('rejectionhandled', unreachedHandled);
+ ensureCleanup(t, unhandled, unreachedHandled);
+
+ return eventPromise.then(t.step_func(function() {
+ return new Promise(function(resolve) {
+ t.step_timeout(function() {
+ resolve();
+ }, 1000);
+ });
+ }));
+ }, 'Promise rejection should be muted if the rejected promise is handled in unhandledrejection event handler in cross-origin non-CORS script');
+
+ function ensureCleanup(t, unhandled, handled) {
+ t.add_cleanup(function() {
+ if (unhandled) {
+ removeEventListener('unhandledrejection', unhandled);
+ }
+ if (handled) {
+ removeEventListener('rejectionhandled', handled);
+ }
+ });
+ }
+
+ var scriptEl = document.createElement('script');
+ scriptEl.src = CROSSDOMAIN + 'support/promise-access-control.py?allow=false';
+ scriptEl.onload = resolveLoaded;
+ document.body.appendChild(scriptEl);
+}());
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-constructor.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-constructor.html
new file mode 100644
index 0000000000..4319deee7b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-constructor.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#the-promiserejectionevent-interface">
+<script>
+'use strict';
+
+test(function() {
+ var p = new Promise(function(resolve, reject) {});
+
+ assert_throws_js(TypeError,
+ function() {
+ PromiseRejectionEvent('', { promise: p });
+ },
+ "Calling PromiseRejectionEvent constructor without 'new' must throw");
+
+ // No custom options are passed (besides required promise).
+ assert_equals(new PromiseRejectionEvent('eventType', { promise: p }).bubbles, false);
+ assert_equals(new PromiseRejectionEvent('eventType', { promise: p }).cancelable, false);
+ assert_equals(new PromiseRejectionEvent('eventType', { promise: p }).promise, p);
+ assert_equals(new PromiseRejectionEvent('eventType', { promise: p }).reason, undefined);
+
+ // No promise is passed.
+ assert_throws_js(TypeError,
+ function() {
+ new PromiseRejectionEvent('eventType', { bubbles: false });
+ },
+ 'Cannot construct PromiseRejectionEventInit without promise');
+
+ // bubbles is passed.
+ assert_equals(new PromiseRejectionEvent('eventType', { bubbles: false, promise: p }).bubbles, false);
+ assert_equals(new PromiseRejectionEvent('eventType', { bubbles: true, promise: p }).bubbles, true);
+
+ // cancelable is passed.
+ assert_equals(new PromiseRejectionEvent('eventType', { cancelable: false, promise: p }).cancelable, false);
+ assert_equals(new PromiseRejectionEvent('eventType', { cancelable: true, promise: p }).cancelable, true);
+
+ // reason is passed.
+ var r = new Error();
+ assert_equals(new PromiseRejectionEvent('eventType', { promise: p, reason: r }).reason, r);
+ assert_equals(new PromiseRejectionEvent('eventType', { promise: p, reason: null }).reason, null);
+
+ // All initializers are passed.
+ assert_equals(new PromiseRejectionEvent('eventType', { bubbles: true, cancelable: true, promise: p, reason: r }).bubbles, true);
+ assert_equals(new PromiseRejectionEvent('eventType', { bubbles: true, cancelable: true, promise: p, reason: r }).cancelable, true);
+ assert_equals(new PromiseRejectionEvent('eventType', { bubbles: true, cancelable: true, promise: p, reason: r }).promise, p);
+ assert_equals(new PromiseRejectionEvent('eventType', { bubbles: true, cancelable: true, promise: p, reason: r }).reason, r);
+}, "This tests the constructor for the PromiseRejectionEvent DOM class.");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-during-parse.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-during-parse.html
new file mode 100644
index 0000000000..160dad9b36
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-during-parse.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Promise rejection during initial parsing of document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections">
+<body>
+<p>The script in this test is executed immediately while parsing is ongoing, and
+<a
+href="https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script">cleaning
+up after running script</a> involves queueing a task on the DOM manipulation
+task source to fire the <code>unhandledrejection</code> event. Parsing then
+completes, immediately transitioning the document's readiness state to
+"interactive," and queuing another task on the DOM manipulation task source to
+transition the state to "complete."
+</p>
+<script>
+'use strict';
+setup({ allow_uncaught_exception: true });
+
+async_test(function(t) {
+ const events = [];
+ document.addEventListener('readystatechange', t.step_func(function() {
+ events.push('readystatechange:' + document.readyState);
+ }));
+ addEventListener('unhandledrejection', t.step_func(function() {
+ events.push('unhandledrejection');
+ }));
+
+ Promise.reject(new Error('this error is intentional'));
+
+ addEventListener('load', t.step_func(function() {
+ assert_array_equals(
+ events,
+ [
+ 'readystatechange:interactive',
+ 'unhandledrejection',
+ 'readystatechange:complete'
+ ]
+ );
+ t.done();
+ }));
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-attached-in-event.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-attached-in-event.html
new file mode 100644
index 0000000000..b151bd812f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-attached-in-event.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections">
+<script>
+'use strict';
+setup({
+ allow_uncaught_exception: true
+});
+async_test(function(t) {
+ var e = new Error('e');
+ var p = Promise.reject(e);
+
+ window.onunhandledrejection = function(evt) {
+ t.step(function() {
+ assert_equals(evt.promise, p);
+ assert_equals(evt.reason, e);
+ });
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p.then(unreached, function(reason) {
+ t.step(function() {
+ assert_equals(reason, e);
+ });
+ t.step_timeout(function() { t.done(); }, 10);
+ });
+ };
+
+ window.onrejectionhandled = t.unreached_func('rejectionhandled event should not be invoked');
+}, 'Attaching a handler in unhandledrejection should not trigger rejectionhandled.');
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-iframe.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-iframe.html
new file mode 100644
index 0000000000..c749eadef4
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-iframe.html
@@ -0,0 +1,146 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<div id="log"></div><br>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+'use strict';
+
+setup({
+ allow_uncaught_exception: true
+});
+
+async_test(function(t) {
+ createIframeAndStartTest(t, function(w) {
+ let e = new Error();
+ let promise = new w.Promise(function(_, reject) {
+ setTimeout(function() {
+ reject(e);
+ }, 1);
+ });
+
+ let unhandled = function(evt) {
+ if (evt.promise === promise) {
+ t.step(function() {
+ assert_equals(evt.reason, e);
+ assert_equals(evt.promise, promise);
+ });
+ t.done();
+ }
+ };
+ let handled = function(evt) {
+ if (evt.promise === promise) {
+ t.step(function() {
+ assert_unreached('rejectionhandled event is not supposed to be triggered');
+ });
+ }
+ };
+
+ w.addEventListener('unhandledrejection', unhandled);
+ w.addEventListener('rejectionhandled', handled);
+ ensureCleanup(t, w, unhandled, handled);
+ });
+}, "unhandledrejection: promise is created in iframe and being rejected elsewhere");
+
+async_test(function(t) {
+ createIframeAndStartTest(t, function(w) {
+ let e = new Error();
+ let promise = w.Promise.reject(e);
+
+ let unhandled = function(evt) {
+ if (evt.promise === promise) {
+ t.step(function() {
+ assert_unreached('unhandledrejection event is not supposed to be triggered');
+ });
+ }
+ };
+ let handled = function(evt) {
+ if (evt.promise === promise) {
+ t.step(function() {
+ assert_unreached('rejectionhandled event is not supposed to be triggered');
+ });
+ }
+ };
+
+ w.addEventListener('unhandledrejection', unhandled);
+ w.addEventListener('rejectionhandled', handled);
+ ensureCleanup(t, w, unhandled, handled);
+
+ promise.catch(function() {});
+ setTimeout(function() {
+ t.done();
+ }, 10);
+ });
+}, 'no unhandledrejection/rejectionhandled: promise is created in iframe and being rejected elsewhere');
+
+async_test(function(t) {
+ createIframeAndStartTest(t, function(w) {
+ let e = new Error();
+ let promise = w.Promise.reject(e);
+ var unhandledPromises = [];
+ var unhandledReasons = [];
+ var handledPromises = [];
+ var handledReasons = [];
+
+ let unhandled = function(evt) {
+ if (evt.promise === promise) {
+ t.step(function() {
+ unhandledPromises.push(evt.promise);
+ unhandledReasons.push(evt.reason);
+
+ setTimeout(function() {
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ promise.then(unreached, function(reason) {
+ assert_equals(reason, e);
+ setTimeout(function() {
+ assert_array_equals(handledPromises, [promise]);
+ assert_array_equals(handledReasons, [e]);
+ t.done();
+ }, 10);
+ });
+ }, 10);
+ });
+ }
+ };
+ let handled = function(evt) {
+ if (evt.promise === promise) {
+ t.step(function() {
+ assert_array_equals(unhandledPromises, [promise]);
+ assert_array_equals(unhandledReasons, [e]);
+ handledPromises.push(evt.promise);
+ handledReasons.push(evt.reason);
+ });
+ }
+ };
+
+ w.addEventListener('unhandledrejection', unhandled);
+ w.addEventListener('rejectionhandled', handled);
+ ensureCleanup(t, w, unhandled, handled);
+ });
+}, 'delayed handling: promise is created in iframe and being rejected elsewhere');
+
+// Helpers
+
+function createIframeAndStartTest(t, runTest) {
+ var iframe = document.createElement("iframe");
+ iframe.onload = function() {
+ t.add_cleanup(() => iframe.remove());
+ runTest(iframe.contentWindow);
+ };
+ iframe.srcdoc = '';
+ document.documentElement.appendChild(iframe);
+}
+
+function ensureCleanup(t, win, unhandled, handled) {
+ t.add_cleanup(function() {
+ if (unhandled) {
+ win.removeEventListener('unhandledrejection', unhandled);
+ }
+ if (handled) {
+ win.removeEventListener('rejectionhandled', handled);
+ }
+ });
+}
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-onerror.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-onerror.html
new file mode 100644
index 0000000000..b6c02d27c9
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-onerror.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#runtime-script-errors">
+<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections">
+<script>
+'use strict';
+setup({
+ allow_uncaught_exception: true
+});
+async_test(function(t) {
+ var e = new Error('e');
+ var e2 = new Error('e2');
+
+ window.onerror = function (msg, url, line, col, error) {
+ t.step(function() {
+ assert_true(msg.includes('e2'));
+ assert_equals(error, e2);
+ });
+ t.done();
+ };
+
+ window.onrejectionhandled = function() {
+ // This should cause onerror
+ throw e2;
+ };
+
+ var p = Promise.reject(e);
+ queueTask(function() {
+ queueTask(t.step_func(function() {
+ // This will cause onrejectionhandled
+ p.catch(function() {});
+ }));
+ });
+}, 'Throwing inside an unhandledrejection handler invokes the error handler.');
+
+// This function queues a task in "DOM manipulation task source"
+function queueTask(f) {
+ var d = document.createElement("details");
+ d.ontoggle = function() {
+ f();
+ };
+
+ d.setAttribute("open", "");
+}
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.dedicatedworker.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.dedicatedworker.html
new file mode 100644
index 0000000000..b6a4a9f3e6
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.dedicatedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Promise rejection events tests: in a dedicated worker context</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections">
+
+<script>
+'use strict';
+fetch_tests_from_worker(new Worker('support/promise-rejection-events.js'));
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.html
new file mode 100644
index 0000000000..2fdfe26025
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Promise rejection events tests: in a Window context</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections">
+
+<script src="support/promise-rejection-events.js"></script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.serviceworker.https.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.serviceworker.https.html
new file mode 100644
index 0000000000..9d12125928
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.serviceworker.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Promise rejection events tests: in a service worker context</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections">
+
+<script>
+'use strict';
+service_worker_test('support/promise-rejection-events.js', 'Service worker setup');
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.sharedworker.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.sharedworker.html
new file mode 100644
index 0000000000..d832d1822f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.sharedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Promise rejection events tests: in a shared worker context</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections">
+
+<script>
+'use strict';
+fetch_tests_from_worker(new SharedWorker('support/promise-rejection-events.js'));
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-resolution-order.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-resolution-order.html
new file mode 100644
index 0000000000..314063025f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-resolution-order.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Promise rejection ordering</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint">
+<body>
+<p>A microtask checkpoint should notify about rejected promises. After
+<a
+href="https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script">cleaning
+up after running script</a> involves running a microtask checkpoint. So the order of unhandledrejection
+should occur before error2.
+</p>
+
+<script>
+'use strict';
+setup({ allow_uncaught_exception: true });
+
+async_test(function(t) {
+ const events = [];
+ addEventListener('unhandledrejection', t.step_func(() => {
+ events.push('unhandledrejection');
+ }));
+
+ function insertInvalidScript(id) {
+ // Inserting <script> with an empty source schedules dispatching an 'error' event on
+ // the dom manipulation task source.
+ let script = document.createElement('script');
+ script.setAttribute('src', ' ');
+ script.addEventListener('error', t.step_func(() => {
+ events.push(`error${id}`);
+
+ // This will be the end of the test. Verify the results are correct.
+ if (id == 2) {
+ assert_array_equals(
+ events,
+ [
+ 'raf1',
+ 'resolve1',
+ 'raf2',
+ 'resolve2',
+ 'error1',
+ 'unhandledrejection',
+ 'error2'
+ ]
+ );
+ t.done();
+ }
+ }));
+ document.body.append(script);
+ }
+
+ requestAnimationFrame(t.step_func(() => {
+ events.push('raf1');
+ Promise.reject();
+ Promise.resolve(0).then(t.step_func(() => {
+ events.push('resolve1');
+ }));
+ insertInvalidScript(1);
+ }));
+
+ requestAnimationFrame(t.step_func(() => {
+ events.push('raf2');
+ Promise.resolve(0).then(t.step_func(() => {
+ events.push('resolve2');
+ }));
+ insertInvalidScript(2);
+ }));
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-access-control.py b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-access-control.py
new file mode 100644
index 0000000000..cf8ed5e492
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-access-control.py
@@ -0,0 +1,18 @@
+def main(request, response):
+ allow = request.GET.first(b"allow", b"false")
+
+ headers = [(b"Content-Type", b"application/javascript")]
+ if allow != b"false":
+ headers.append((b"Access-Control-Allow-Origin", b"*"))
+
+ body = b"""
+ function handleRejectedPromise(promise) {
+ promise.catch(() => {});
+ }
+
+ (function() {
+ new Promise(function(resolve, reject) { reject(42); });
+ })();
+ """
+
+ return headers, body
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-rejection-events.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-rejection-events.js
new file mode 100644
index 0000000000..036e1784db
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-rejection-events.js
@@ -0,0 +1,961 @@
+'use strict';
+
+if (self.importScripts) {
+ importScripts('/resources/testharness.js');
+}
+
+setup({
+ allow_uncaught_exception: true
+});
+
+//
+// Straightforward unhandledrejection tests
+//
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledSucceed(t, e, function() { return p; });
+
+ p = Promise.reject(e);
+}, 'unhandledrejection: from Promise.reject');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledSucceed(t, e, function() { return p; });
+
+ p = new Promise(function(_, reject) {
+ reject(e);
+ });
+}, 'unhandledrejection: from a synchronous rejection in new Promise');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledSucceed(t, e, function() { return p; });
+
+ p = new Promise(function(_, reject) {
+ queueTask(function() {
+ reject(e);
+ });
+ });
+}, 'unhandledrejection: from a task-delayed rejection');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledSucceed(t, e, function() { return p; });
+
+ p = new Promise(function(_, reject) {
+ setTimeout(function() {
+ reject(e);
+ }, 1);
+ });
+}, 'unhandledrejection: from a setTimeout-delayed rejection');
+
+async_test(function(t) {
+ var e = new Error();
+ var e2 = new Error();
+ var promise2;
+
+ onUnhandledSucceed(t, e2, function() { return promise2; });
+
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ promise2 = Promise.reject(e).then(unreached, function(reason) {
+ t.step(function() {
+ assert_equals(reason, e);
+ });
+ throw e2;
+ });
+}, 'unhandledrejection: from a throw in a rejection handler chained off of Promise.reject');
+
+async_test(function(t) {
+ var e = new Error();
+ var e2 = new Error();
+ var promise2;
+
+ onUnhandledSucceed(t, e2, function() { return promise2; });
+
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ promise2 = new Promise(function(_, reject) {
+ setTimeout(function() {
+ reject(e);
+ }, 1);
+ }).then(unreached, function(reason) {
+ t.step(function() {
+ assert_equals(reason, e);
+ });
+ throw e2;
+ });
+}, 'unhandledrejection: from a throw in a rejection handler chained off of a setTimeout-delayed rejection');
+
+async_test(function(t) {
+ var e = new Error();
+ var e2 = new Error();
+ var promise2;
+
+ onUnhandledSucceed(t, e2, function() { return promise2; });
+
+ var promise = new Promise(function(_, reject) {
+ setTimeout(function() {
+ reject(e);
+ mutationObserverMicrotask(function() {
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ promise2 = promise.then(unreached, function(reason) {
+ t.step(function() {
+ assert_equals(reason, e);
+ });
+ throw e2;
+ });
+ });
+ }, 1);
+ });
+}, 'unhandledrejection: from a throw in a rejection handler attached one microtask after a setTimeout-delayed rejection');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledSucceed(t, e, function() { return p; });
+
+ p = Promise.resolve().then(function() {
+ return Promise.reject(e);
+ });
+}, 'unhandledrejection: from returning a Promise.reject-created rejection in a fulfillment handler');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledSucceed(t, e, function() { return p; });
+
+ p = Promise.resolve().then(function() {
+ throw e;
+ });
+}, 'unhandledrejection: from a throw in a fulfillment handler');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledSucceed(t, e, function() { return p; });
+
+ p = Promise.resolve().then(function() {
+ return new Promise(function(_, reject) {
+ setTimeout(function() {
+ reject(e);
+ }, 1);
+ });
+ });
+}, 'unhandledrejection: from returning a setTimeout-delayed rejection in a fulfillment handler');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledSucceed(t, e, function() { return p; });
+
+ p = Promise.all([Promise.reject(e)]);
+}, 'unhandledrejection: from Promise.reject, indirected through Promise.all');
+
+async_test(function(t) {
+ var p;
+
+ var unhandled = function(ev) {
+ if (ev.promise === p) {
+ t.step(function() {
+ assert_equals(ev.reason.name, 'InvalidStateError');
+ assert_equals(ev.promise, p);
+ });
+ t.done();
+ }
+ };
+ addEventListener('unhandledrejection', unhandled);
+ ensureCleanup(t, unhandled);
+
+ p = createImageBitmap(new Blob());
+}, 'unhandledrejection: from createImageBitmap which is UA triggered');
+
+//
+// Negative unhandledrejection/rejectionhandled tests with immediate attachment
+//
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p = Promise.reject(e).then(unreached, function() {});
+}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise from Promise.reject');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p = Promise.all([Promise.reject(e)]).then(unreached, function() {});
+}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise from ' +
+ 'Promise.reject, indirecting through Promise.all');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p = new Promise(function(_, reject) {
+ reject(e);
+ }).then(unreached, function() {});
+}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a synchronously-rejected ' +
+ 'promise created with new Promise');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p = Promise.resolve().then(function() {
+ throw e;
+ }).then(unreached, function(reason) {
+ t.step(function() {
+ assert_equals(reason, e);
+ });
+ });
+}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' +
+ 'throwing in a fulfillment handler');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p = Promise.resolve().then(function() {
+ return Promise.reject(e);
+ }).then(unreached, function(reason) {
+ t.step(function() {
+ assert_equals(reason, e);
+ });
+ });
+}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' +
+ 'returning a Promise.reject-created promise in a fulfillment handler');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p = Promise.resolve().then(function() {
+ return new Promise(function(_, reject) {
+ setTimeout(function() {
+ reject(e);
+ }, 1);
+ });
+ }).then(unreached, function(reason) {
+ t.step(function() {
+ assert_equals(reason, e);
+ });
+ });
+}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' +
+ 'returning a setTimeout-delayed rejection in a fulfillment handler');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ queueTask(function() {
+ p = Promise.resolve().then(function() {
+ return Promise.reject(e);
+ })
+ .catch(function() {});
+ });
+}, 'no unhandledrejection/rejectionhandled: all inside a queued task, a rejection handler attached synchronously to ' +
+ 'a promise created from returning a Promise.reject-created promise in a fulfillment handler');
+
+async_test(function(t) {
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p = createImageBitmap(new Blob()).then(unreached, function() {});
+}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' +
+ 'createImageBitmap');
+
+//
+// Negative unhandledrejection/rejectionhandled tests with microtask-delayed attachment
+//
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ p = Promise.reject(e);
+ mutationObserverMicrotask(function() {
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p.then(unreached, function() {});
+ });
+}, 'delayed handling: a microtask delay before attaching a handler prevents both events (Promise.reject-created ' +
+ 'promise)');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ p = new Promise(function(_, reject) {
+ reject(e);
+ });
+ mutationObserverMicrotask(function() {
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p.then(unreached, function() {});
+ });
+}, 'delayed handling: a microtask delay before attaching a handler prevents both events (immediately-rejected new ' +
+ 'Promise-created promise)');
+
+async_test(function(t) {
+ var e = new Error();
+ var p1;
+ var p2;
+
+ onUnhandledFail(t, function() { return p1; });
+ onUnhandledFail(t, function() { return p2; });
+
+ p1 = new Promise(function(_, reject) {
+ mutationObserverMicrotask(function() {
+ reject(e);
+ });
+ });
+ p2 = Promise.all([p1]);
+ mutationObserverMicrotask(function() {
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p2.then(unreached, function() {});
+ });
+}, 'delayed handling: a microtask delay before attaching the handler, and before rejecting the promise, indirected ' +
+ 'through Promise.all');
+
+//
+// Negative unhandledrejection/rejectionhandled tests with nested-microtask-delayed attachment
+//
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ p = Promise.reject(e);
+ mutationObserverMicrotask(function() {
+ Promise.resolve().then(function() {
+ mutationObserverMicrotask(function() {
+ Promise.resolve().then(function() {
+ p.catch(function() {});
+ });
+ });
+ });
+ });
+}, 'microtask nesting: attaching a handler inside a combination of mutationObserverMicrotask + promise microtasks');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ queueTask(function() {
+ p = Promise.reject(e);
+ mutationObserverMicrotask(function() {
+ Promise.resolve().then(function() {
+ mutationObserverMicrotask(function() {
+ Promise.resolve().then(function() {
+ p.catch(function() {});
+ });
+ });
+ });
+ });
+ });
+}, 'microtask nesting: attaching a handler inside a combination of mutationObserverMicrotask + promise microtasks, ' +
+ 'all inside a queueTask');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ setTimeout(function() {
+ p = Promise.reject(e);
+ mutationObserverMicrotask(function() {
+ Promise.resolve().then(function() {
+ mutationObserverMicrotask(function() {
+ Promise.resolve().then(function() {
+ p.catch(function() {});
+ });
+ });
+ });
+ });
+ }, 0);
+}, 'microtask nesting: attaching a handler inside a combination of mutationObserverMicrotask + promise microtasks, ' +
+ 'all inside a setTimeout');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ p = Promise.reject(e);
+ Promise.resolve().then(function() {
+ mutationObserverMicrotask(function() {
+ Promise.resolve().then(function() {
+ mutationObserverMicrotask(function() {
+ p.catch(function() {});
+ });
+ });
+ });
+ });
+}, 'microtask nesting: attaching a handler inside a combination of promise microtasks + mutationObserverMicrotask');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ queueTask(function() {
+ p = Promise.reject(e);
+ Promise.resolve().then(function() {
+ mutationObserverMicrotask(function() {
+ Promise.resolve().then(function() {
+ mutationObserverMicrotask(function() {
+ p.catch(function() {});
+ });
+ });
+ });
+ });
+ });
+}, 'microtask nesting: attaching a handler inside a combination of promise microtasks + mutationObserverMicrotask, ' +
+ 'all inside a queueTask');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ setTimeout(function() {
+ p = Promise.reject(e);
+ Promise.resolve().then(function() {
+ mutationObserverMicrotask(function() {
+ Promise.resolve().then(function() {
+ mutationObserverMicrotask(function() {
+ p.catch(function() {});
+ });
+ });
+ });
+ });
+ }, 0);
+}, 'microtask nesting: attaching a handler inside a combination of promise microtasks + mutationObserverMicrotask, ' +
+ 'all inside a setTimeout');
+
+
+// For workers, queueTask() involves posting tasks to other threads, so
+// the following tests don't work there.
+
+if ('document' in self) {
+ //
+ // Negative unhandledrejection/rejectionhandled tests with task-delayed attachment
+ //
+
+ async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ var _reject;
+ p = new Promise(function(_, reject) {
+ _reject = reject;
+ });
+ _reject(e);
+ queueTask(function() {
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p.then(unreached, function() {});
+ });
+ }, 'delayed handling: a task delay before attaching a handler prevents unhandledrejection');
+
+ async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ p = Promise.reject(e);
+ queueTask(function() {
+ Promise.resolve().then(function() {
+ p.catch(function() {});
+ });
+ });
+ }, 'delayed handling: queueTask after promise creation/rejection, plus promise microtasks, is not too late to ' +
+ 'attach a rejection handler');
+
+ async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ queueTask(function() {
+ Promise.resolve().then(function() {
+ Promise.resolve().then(function() {
+ Promise.resolve().then(function() {
+ Promise.resolve().then(function() {
+ p.catch(function() {});
+ });
+ });
+ });
+ });
+ });
+ p = Promise.reject(e);
+ }, 'delayed handling: queueTask before promise creation/rejection, plus many promise microtasks, is not too ' +
+ 'late to attach a rejection handler');
+
+ async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledFail(t, function() { return p; });
+
+ p = Promise.reject(e);
+ queueTask(function() {
+ Promise.resolve().then(function() {
+ Promise.resolve().then(function() {
+ Promise.resolve().then(function() {
+ Promise.resolve().then(function() {
+ p.catch(function() {});
+ });
+ });
+ });
+ });
+ });
+ }, 'delayed handling: queueTask after promise creation/rejection, plus many promise microtasks, is not too ' +
+ 'late to attach a rejection handler');
+}
+
+//
+// Positive unhandledrejection/rejectionhandled tests with delayed attachment
+//
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledSucceed(t, e, function() { return p; });
+
+ var _reject;
+ p = new Promise(function(_, reject) {
+ _reject = reject;
+ });
+ _reject(e);
+ queueTask(function() {
+ queueTask(function() {
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p.then(unreached, function() {});
+ });
+ });
+}, 'delayed handling: a nested-task delay before attaching a handler causes unhandledrejection');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledSucceed(t, e, function() { return p; });
+
+ p = Promise.reject(e);
+ queueTask(function() {
+ queueTask(function() {
+ Promise.resolve().then(function() {
+ p.catch(function() {});
+ });
+ });
+ });
+}, 'delayed handling: a nested-queueTask after promise creation/rejection, plus promise microtasks, is too ' +
+ 'late to attach a rejection handler');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledSucceed(t, e, function() { return p; });
+
+ queueTask(function() {
+ queueTask(function() {
+ Promise.resolve().then(function() {
+ Promise.resolve().then(function() {
+ Promise.resolve().then(function() {
+ Promise.resolve().then(function() {
+ p.catch(function() {});
+ });
+ });
+ });
+ });
+ });
+ });
+ p = Promise.reject(e);
+}, 'delayed handling: a nested-queueTask before promise creation/rejection, plus many promise microtasks, is ' +
+ 'too late to attach a rejection handler');
+
+async_test(function(t) {
+ var e = new Error();
+ var p;
+
+ onUnhandledSucceed(t, e, function() { return p; });
+
+ p = Promise.reject(e);
+ queueTask(function() {
+ queueTask(function() {
+ Promise.resolve().then(function() {
+ Promise.resolve().then(function() {
+ Promise.resolve().then(function() {
+ Promise.resolve().then(function() {
+ p.catch(function() {});
+ });
+ });
+ });
+ });
+ });
+ });
+}, 'delayed handling: a nested-queueTask after promise creation/rejection, plus many promise microtasks, is ' +
+ 'too late to attach a rejection handler');
+
+async_test(function(t) {
+ var unhandledPromises = [];
+ var unhandledReasons = [];
+ var e = new Error();
+ var p;
+
+ var unhandled = function(ev) {
+ if (ev.promise === p) {
+ t.step(function() {
+ unhandledPromises.push(ev.promise);
+ unhandledReasons.push(ev.reason);
+ });
+ }
+ };
+ var handled = function(ev) {
+ if (ev.promise === p) {
+ t.step(function() {
+ assert_array_equals(unhandledPromises, [p]);
+ assert_array_equals(unhandledReasons, [e]);
+ assert_equals(ev.promise, p);
+ assert_equals(ev.reason, e);
+ });
+ }
+ };
+ addEventListener('unhandledrejection', unhandled);
+ addEventListener('rejectionhandled', handled);
+ ensureCleanup(t, unhandled, handled);
+
+ p = new Promise(function() {
+ throw e;
+ });
+ setTimeout(function() {
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p.then(unreached, function(reason) {
+ assert_equals(reason, e);
+ setTimeout(function() { t.done(); }, 10);
+ });
+ }, 10);
+}, 'delayed handling: delaying handling by setTimeout(,10) will cause both events to fire');
+
+async_test(function(t) {
+ var unhandledPromises = [];
+ var unhandledReasons = [];
+ var p;
+
+ var unhandled = function(ev) {
+ if (ev.promise === p) {
+ t.step(function() {
+ unhandledPromises.push(ev.promise);
+ unhandledReasons.push(ev.reason.name);
+ });
+ }
+ };
+ var handled = function(ev) {
+ if (ev.promise === p) {
+ t.step(function() {
+ assert_array_equals(unhandledPromises, [p]);
+ assert_array_equals(unhandledReasons, ['InvalidStateError']);
+ assert_equals(ev.promise, p);
+ assert_equals(ev.reason.name, 'InvalidStateError');
+ });
+ }
+ };
+ addEventListener('unhandledrejection', unhandled);
+ addEventListener('rejectionhandled', handled);
+ ensureCleanup(t, unhandled, handled);
+
+ p = createImageBitmap(new Blob());
+ setTimeout(function() {
+ var unreached = t.unreached_func('promise should not be fulfilled');
+ p.then(unreached, function(reason) {
+ assert_equals(reason.name, 'InvalidStateError');
+ setTimeout(function() { t.done(); }, 10);
+ });
+ }, 10);
+}, 'delayed handling: delaying handling rejected promise created from createImageBitmap will cause both events to fire');
+
+//
+// Miscellaneous tests about integration with the rest of the platform
+//
+
+async_test(function(t) {
+ var e = new Error();
+ var l = function(ev) {
+ var order = [];
+ mutationObserverMicrotask(function() {
+ order.push(1);
+ });
+ setTimeout(function() {
+ order.push(2);
+ t.step(function() {
+ assert_array_equals(order, [1, 2]);
+ });
+ t.done();
+ }, 1);
+ };
+ addEventListener('unhandledrejection', l);
+ ensureCleanup(t, l);
+ Promise.reject(e);
+}, 'mutationObserverMicrotask vs. queueTask ordering is not disturbed inside unhandledrejection events');
+
+// For workers, queueTask() involves posting tasks to other threads, so
+// the following tests don't work there.
+
+if ('document' in self) {
+
+ // For the next two see https://github.com/domenic/unhandled-rejections-browser-spec/issues/2#issuecomment-121121695
+ // and the following comments.
+
+ async_test(function(t) {
+ var sequenceOfEvents = [];
+
+ addEventListener('unhandledrejection', l);
+ ensureCleanup(t, l);
+
+ var p1 = Promise.reject();
+ var p2;
+ queueTask(function() {
+ p2 = Promise.reject();
+ queueTask(function() {
+ sequenceOfEvents.push('queueTask');
+ checkSequence();
+ });
+ });
+
+ function l(ev) {
+ if (ev.promise === p1 || ev.promise === p2) {
+ sequenceOfEvents.push(ev.promise);
+ checkSequence();
+ }
+ }
+
+ function checkSequence() {
+ if (sequenceOfEvents.length === 3) {
+ t.step(function() {
+ assert_array_equals(sequenceOfEvents, [p1, 'queueTask', p2]);
+ });
+ t.done();
+ }
+ }
+ }, 'queueTask ordering vs. the task queued for unhandled rejection notification (1)');
+
+ async_test(function(t) {
+ var sequenceOfEvents = [];
+
+ addEventListener('unhandledrejection', l);
+ ensureCleanup(t, l);
+
+ var p2;
+ queueTask(function() {
+ p2 = Promise.reject();
+ queueTask(function() {
+ sequenceOfEvents.push('queueTask');
+ checkSequence();
+ });
+ });
+
+ function l(ev) {
+ if (ev.promise == p2) {
+ sequenceOfEvents.push(ev.promise);
+ checkSequence();
+ }
+ }
+
+ function checkSequence() {
+ if (sequenceOfEvents.length === 2) {
+ t.step(function() {
+ assert_array_equals(sequenceOfEvents, ['queueTask', p2]);
+ });
+ t.done();
+ }
+ }
+ }, 'queueTask ordering vs. the task queued for unhandled rejection notification (2)');
+
+ async_test(function(t) {
+ var sequenceOfEvents = [];
+
+
+ addEventListener('unhandledrejection', unhandled);
+ addEventListener('rejectionhandled', handled);
+ ensureCleanup(t, unhandled, handled);
+
+ var p = Promise.reject();
+
+ function unhandled(ev) {
+ if (ev.promise === p) {
+ sequenceOfEvents.push('unhandled');
+ checkSequence();
+ setTimeout(function() {
+ queueTask(function() {
+ sequenceOfEvents.push('task before catch');
+ checkSequence();
+ });
+
+ p.catch(function() {
+ sequenceOfEvents.push('catch');
+ checkSequence();
+ });
+
+ queueTask(function() {
+ sequenceOfEvents.push('task after catch');
+ checkSequence();
+ });
+
+ sequenceOfEvents.push('after catch');
+ checkSequence();
+ }, 10);
+ }
+ }
+
+ function handled(ev) {
+ if (ev.promise === p) {
+ sequenceOfEvents.push('handled');
+ checkSequence();
+ }
+ }
+
+ function checkSequence() {
+ if (sequenceOfEvents.length === 6) {
+ t.step(function() {
+ assert_array_equals(sequenceOfEvents,
+ ['unhandled', 'after catch', 'catch', 'task before catch', 'handled', 'task after catch']);
+ });
+ t.done();
+ }
+ }
+ }, 'rejectionhandled is dispatched from a queued task, and not immediately');
+}
+
+//
+// HELPERS
+//
+
+// This function queues a task in "DOM manipulation task source" in window
+// context, but not in workers.
+function queueTask(f) {
+ if ('document' in self) {
+ var d = document.createElement("details");
+ d.ontoggle = function() {
+ f();
+ };
+ d.setAttribute("open", "");
+ } else {
+ // We need to fix this to use something that can queue tasks in
+ // "DOM manipulation task source" to ensure the order is correct
+ var channel = new MessageChannel();
+ channel.port1.onmessage = function() { channel.port1.close(); f(); };
+ channel.port2.postMessage('abusingpostmessageforfunandprofit');
+ channel.port2.close();
+ }
+}
+
+function mutationObserverMicrotask(f) {
+ if ('document' in self) {
+ var observer = new MutationObserver(function() { f(); });
+ var node = document.createTextNode('');
+ observer.observe(node, { characterData: true });
+ node.data = 'foo';
+ } else {
+ // We don't have mutation observers on workers, so just post a promise-based
+ // microtask.
+ Promise.resolve().then(function() { f(); });
+ }
+}
+
+function onUnhandledSucceed(t, expectedReason, expectedPromiseGetter) {
+ var l = function(ev) {
+ if (ev.promise === expectedPromiseGetter()) {
+ t.step(function() {
+ assert_equals(ev.reason, expectedReason);
+ assert_equals(ev.promise, expectedPromiseGetter());
+ });
+ t.done();
+ }
+ };
+ addEventListener('unhandledrejection', l);
+ ensureCleanup(t, l);
+}
+
+function onUnhandledFail(t, expectedPromiseGetter) {
+ var unhandled = function(evt) {
+ if (evt.promise === expectedPromiseGetter()) {
+ t.step(function() {
+ assert_unreached('unhandledrejection event is not supposed to be triggered');
+ });
+ }
+ };
+ var handled = function(evt) {
+ if (evt.promise === expectedPromiseGetter()) {
+ t.step(function() {
+ assert_unreached('rejectionhandled event is not supposed to be triggered');
+ });
+ }
+ };
+ addEventListener('unhandledrejection', unhandled);
+ addEventListener('rejectionhandled', handled);
+ ensureCleanup(t, unhandled, handled);
+ setTimeout(function() {
+ t.done();
+ }, 10);
+}
+
+function ensureCleanup(t, unhandled, handled) {
+ t.add_cleanup(function() {
+ if (unhandled)
+ removeEventListener('unhandledrejection', unhandled);
+ if (handled)
+ removeEventListener('rejectionhandled', handled);
+ });
+}
+
+done();
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-parse-error.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-parse-error.html
new file mode 100644
index 0000000000..3c21df49c9
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-parse-error.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror: parse errors</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!--
+
+ In https://html.spec.whatwg.org/multipage/#creating-scripts ,
+ step 3 describes parsing the script, and step 5 says:
+ # Otherwise, report the error using the onerror event handler of
+ # the script's global object. If the error is still not handled
+ # after this, then the error may be reported to the user.
+ which links to
+ https://html.spec.whatwg.org/multipage/#report-the-error ,
+ which describes what to do when onerror is a Function.
+
+ -->
+ </head>
+ <body>
+
+ <div id="log"></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var error_count = 0;
+ window.onerror = function(msg, url, lineno) {
+ ++error_count;
+ test(function() {assert_equals(url, window.location.href)},
+ "correct url passed to window.onerror");
+ test(function() {assert_equals(lineno, 34)},
+ "correct line number passed to window.onerror");
+ };
+ </script>
+ <script>This script does not parse correctly.</script>
+ <script>
+ test(function() {assert_equals(error_count, 1)},
+ "correct number of calls to window.onerror");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error-throw.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error-throw.html
new file mode 100644
index 0000000000..4b2bc1f22c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error-throw.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror: runtime scripterrors</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!--
+
+ https://html.spec.whatwg.org/multipage/#runtime-script-errors
+ says what to do for uncaught runtime script errors, and just below
+ describes what to do when onerror is a Function.
+
+ -->
+ </head>
+ <body>
+
+ <div id="log"></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var error_count = 0;
+ window.onerror = function(msg, url, lineno) {
+ ++error_count;
+ test(function() {assert_equals(url, window.location.href)},
+ "correct url passed to window.onerror");
+ test(function() {assert_equals(lineno, 36)},
+ "correct line number passed to window.onerror");
+ };
+ </script>
+ <script>
+ try {
+ // This error is caught, so it should NOT trigger onerror.
+ throw "foo";
+ } catch (ex) {
+ }
+ // This error is NOT caught, so it should trigger onerror.
+ throw "bar";
+ </script>
+ <script>
+ test(function() {assert_equals(error_count, 1)},
+ "correct number of calls to window.onerror");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error.html
new file mode 100644
index 0000000000..1fdab521ae
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<html>
+ <head>
+ <title>window.onerror: runtime scripterrors</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!--
+
+ https://html.spec.whatwg.org/multipage/#runtime-script-errors
+ says what to do for uncaught runtime script errors, and just below
+ describes what to do when onerror is a Function.
+
+ -->
+ </head>
+ <body>
+
+ <div id="log"></div>
+ <script>
+ setup({allow_uncaught_exception:true});
+ var error_count = 0;
+ window.onerror = function(msg, url, lineno) {
+ ++error_count;
+ test(function() {assert_equals(url, window.location.href)},
+ "correct url passed to window.onerror");
+ test(function() {assert_equals(lineno, 36)},
+ "correct line number passed to window.onerror");
+ };
+ </script>
+ <script>
+ try {
+ // This error is caught, so it should NOT trigger onerror.
+ window.nonexistentproperty.oops();
+ } catch (ex) {
+ }
+ // This error is NOT caught, so it should trigger onerror.
+ window.nonexistentproperty.oops();
+ </script>
+ <script>
+ test(function() {assert_equals(error_count, 1)},
+ "correct number of calls to window.onerror");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-1.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-1.html
new file mode 100644
index 0000000000..65a1a02b11
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-1.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>
+ When a listener from window A is added to an event target in window B via the
+ addEventListener function from window B, errors in that listener should be
+ reported to window A.
+</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe></iframe>
+<iframe></iframe>
+<script>
+test(function() {
+ var f = new frames[0].Function("thereIsNoSuchCallable()");
+ frames[1].document.addEventListener("myevent", f);
+ var frame0ErrorFired = false;
+ var frame1ErrorFired = false;
+ var ourErrorFired = false;
+ frames[0].addEventListener("error", function() {
+ frame0ErrorFired = true;
+ });
+ frames[1].addEventListener("error", function() {
+ frame1ErrorFired = true;
+ });
+ addEventListener("error", function() {
+ ourErrorFired = true;
+ });
+ frames[1].document.dispatchEvent(new Event("myevent"));
+ assert_true(frame0ErrorFired);
+ assert_false(frame1ErrorFired);
+ assert_false(ourErrorFired);
+}, "The error event from an event listener should fire on that listener's global");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-2.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-2.html
new file mode 100644
index 0000000000..6c5476542b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-2.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>
+ When a listener from window A is added to an event target in window B via the
+ addEventListener function from window A, errors in that listener should be
+ reported to window A.
+</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe></iframe>
+<iframe></iframe>
+<script>
+test(function() {
+ var f = new frames[0].Function("thereIsNoSuchCallable()");
+ frames[0].document.addEventListener.call(frames[1].document, "myevent", f);
+ var frame0ErrorFired = false;
+ var frame1ErrorFired = false;
+ var ourErrorFired = false;
+ frames[0].addEventListener("error", function() {
+ frame0ErrorFired = true;
+ });
+ frames[1].addEventListener("error", function() {
+ frame1ErrorFired = true;
+ });
+ addEventListener("error", function() {
+ ourErrorFired = true;
+ });
+ frames[1].document.dispatchEvent(new Event("myevent"));
+ assert_true(frame0ErrorFired);
+ assert_false(frame1ErrorFired);
+ assert_false(ourErrorFired);
+}, "The error event from an event listener should fire on that listener's global");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-3.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-3.html
new file mode 100644
index 0000000000..5e78baa8de
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-3.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>
+ When a listener from window A is added to an event target in window A via the
+ addEventListener function from window A, errors in that listener should be
+ reported to window A.
+</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe></iframe>
+<iframe></iframe>
+<script>
+test(function() {
+ var f = new frames[1].Function("thereIsNoSuchCallable()");
+ frames[1].document.addEventListener("myevent", f);
+ var frame0ErrorFired = false;
+ var frame1ErrorFired = false;
+ var ourErrorFired = false;
+ frames[0].addEventListener("error", function() {
+ frame0ErrorFired = true;
+ });
+ frames[1].addEventListener("error", function() {
+ frame1ErrorFired = true;
+ });
+ addEventListener("error", function() {
+ ourErrorFired = true;
+ });
+ frames[1].document.dispatchEvent(new Event("myevent"));
+ assert_false(frame0ErrorFired);
+ assert_true(frame1ErrorFired);
+ assert_false(ourErrorFired);
+}, "The error event from an event listener should fire on that listener's global");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-4.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-4.html
new file mode 100644
index 0000000000..a5f35d613f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-4.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>
+ When a listener from window A is added to an event target in window A via the
+ addEventListener function from window B, errors in that listener should be
+ reported to window A.
+</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe></iframe>
+<iframe></iframe>
+<script>
+test(function() {
+ var f = new frames[1].Function("thereIsNoSuchCallable()");
+ frames[0].document.addEventListener.call(frames[1].document, "myevent", f);
+ var frame0ErrorFired = false;
+ var frame1ErrorFired = false;
+ var ourErrorFired = false;
+ frames[0].addEventListener("error", function() {
+ frame0ErrorFired = true;
+ });
+ frames[1].addEventListener("error", function() {
+ frame1ErrorFired = true;
+ });
+ addEventListener("error", function() {
+ ourErrorFired = true;
+ });
+ frames[1].document.dispatchEvent(new Event("myevent"));
+ assert_false(frame0ErrorFired);
+ assert_true(frame1ErrorFired);
+ assert_false(ourErrorFired);
+}, "The error event from an event listener should fire on that listener's global");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-5.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-5.html
new file mode 100644
index 0000000000..da93e782ca
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-5.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>window.onerror listener reports the exception in global object of its callback</title>
+<link rel=help href="https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe></iframe>
+<iframe></iframe>
+<iframe></iframe>
+<script>
+setup({ allow_uncaught_exception: true });
+
+window.onload = () => {
+ test(() => {
+ window.onerrorCalls = [];
+ window.onerror = () => { onerrorCalls.push("top"); };
+ frames[0].onerror = new frames[1].Function(`top.onerrorCalls.push("frame0"); throw new parent.frames[2].Error("PASS");`);
+ frames[1].onerror = () => { onerrorCalls.push("frame1"); };
+ frames[2].onerror = () => { onerrorCalls.push("frame2"); };
+
+ frames[0].dispatchEvent(new ErrorEvent("error", { error: new Error("foo") }));
+ assert_array_equals(onerrorCalls, ["frame0", "frame1"]);
+ });
+};
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/reporterror-cross-realm-method.html b/testing/web-platform/tests/html/webappapis/scripting/reporterror-cross-realm-method.html
new file mode 100644
index 0000000000..6e2c2aae8f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/reporterror-cross-realm-method.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>self.reportError() dispatches an "error" event for this's relevant global object</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#dom-reporterror">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+setup({ allow_uncaught_exception: true });
+
+async_test(t => {
+ window.addEventListener("error", t.unreached_func("'error' event should not be dispatched for top window!"));
+
+ const iframe = document.createElement("iframe");
+ iframe.onload = t.step_func_done(() => {
+ let eventFired = false;
+ const error = new TypeError("foo");
+ const otherWindow = iframe.contentWindow;
+ otherWindow.addEventListener("error", t.step_func(event => {
+ assert_equals(event.error, error);
+ eventFired = true;
+ }));
+
+ window.reportError.call(otherWindow, error);
+ assert_true(eventFired);
+ });
+ document.body.append(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/reporterror-in-detached-window-crash.html b/testing/web-platform/tests/html/webappapis/scripting/reporterror-in-detached-window-crash.html
new file mode 100644
index 0000000000..33662caa4d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/reporterror-in-detached-window-crash.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>window.reportError() doesn't crash when window is detached</title>
+<link rel="help" href="https://crbug.com/1445375">
+<body>
+<script>
+let i = document.createElement("iframe");
+document.body.appendChild(i);
+let i_win = i.contentWindow;
+i.remove();
+i_win.reportError("an error");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/scripting/reporterror.any.js b/testing/web-platform/tests/html/webappapis/scripting/reporterror.any.js
new file mode 100644
index 0000000000..b9e7ba25bc
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/scripting/reporterror.any.js
@@ -0,0 +1,49 @@
+setup({ allow_uncaught_exception:true });
+
+[
+ 1,
+ new TypeError(),
+ undefined
+].forEach(throwable => {
+ test(t => {
+ let happened = false;
+ self.addEventListener("error", t.step_func(e => {
+ assert_true(e.message !== "");
+ assert_equals(e.filename, new URL("reporterror.any.js", location.href).href);
+ assert_greater_than(e.lineno, 0);
+ assert_greater_than(e.colno, 0);
+ assert_equals(e.error, throwable);
+ happened = true;
+ }), { once:true });
+ self.reportError(throwable);
+ assert_true(happened);
+ }, `self.reportError(${throwable})`);
+});
+
+test(() => {
+ assert_throws_js(TypeError, () => self.reportError());
+}, `self.reportError() (without arguments) throws`);
+
+test(() => {
+ // Workaround for https://github.com/web-platform-tests/wpt/issues/32105
+ let invoked = false;
+ self.reportError({
+ get name() {
+ invoked = true;
+ assert_unreached('get name')
+ },
+ get message() {
+ invoked = true;
+ assert_unreached('get message');
+ },
+ get fileName() {
+ invoked = true;
+ assert_unreached('get fileName');
+ },
+ get lineNumber() {
+ invoked = true;
+ assert_unreached('get lineNumber');
+ }
+ });
+ assert_false(invoked);
+}, `self.reportError() doesn't invoke getters`);
diff --git a/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
new file mode 100644
index 0000000000..00a86fa74b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
@@ -0,0 +1,45 @@
+/**
+ * Runs a collection of tests that determine if an API implements structured clone
+ * correctly.
+ *
+ * The `runner` parameter has the following properties:
+ * - `setup()`: An optional function run once before testing starts
+ * - `teardown()`: An option function run once after all tests are done
+ * - `preTest()`: An optional, async function run before a test
+ * - `postTest()`: An optional, async function run after a test is done
+ * - `structuredClone(obj, transferList)`: Required function that somehow
+ * structurally clones an object.
+ * Must return a promise.
+ * - `hasDocument`: When true, disables tests that require a document. True by default.
+ */
+
+function runStructuredCloneBatteryOfTests(runner) {
+ const defaultRunner = {
+ setup() {},
+ preTest() {},
+ postTest() {},
+ teardown() {},
+ hasDocument: true
+ };
+ runner = Object.assign({}, defaultRunner, runner);
+
+ let setupPromise = runner.setup();
+ const allTests = structuredCloneBatteryOfTests.map(test => {
+
+ if (!runner.hasDocument && test.requiresDocument) {
+ return;
+ }
+
+ return new Promise(resolve => {
+ promise_test(async t => {
+ test = await test;
+ await setupPromise;
+ await runner.preTest(test);
+ await test.f(runner, t)
+ await runner.postTest(test);
+ resolve();
+ }, test.description);
+ }).catch(_ => {});
+ });
+ Promise.all(allTests).then(_ => runner.teardown());
+}
diff --git a/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
new file mode 100644
index 0000000000..23cf4f651a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
@@ -0,0 +1,169 @@
+structuredCloneBatteryOfTests.push({
+ description: 'ArrayBuffer',
+ async f(runner) {
+ const buffer = new Uint8Array([1]).buffer;
+ const copy = await runner.structuredClone(buffer, [buffer]);
+ assert_equals(buffer.byteLength, 0);
+ assert_equals(copy.byteLength, 1);
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'MessagePort',
+ async f(runner) {
+ const {port1, port2} = new MessageChannel();
+ const copy = await runner.structuredClone(port2, [port2]);
+ const msg = new Promise(resolve => port1.onmessage = resolve);
+ copy.postMessage('ohai');
+ assert_equals((await msg).data, 'ohai');
+ }
+});
+
+// TODO: ImageBitmap
+
+structuredCloneBatteryOfTests.push({
+ description: 'A detached ArrayBuffer cannot be transferred',
+ async f(runner, t) {
+ const buffer = new ArrayBuffer();
+ await runner.structuredClone(buffer, [buffer]);
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(buffer, [buffer])
+ );
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'A detached platform object cannot be transferred',
+ async f(runner, t) {
+ const {port1} = new MessageChannel();
+ await runner.structuredClone(port1, [port1]);
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(port1, [port1])
+ );
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Transferring a non-transferable platform object fails',
+ async f(runner, t) {
+ const blob = new Blob();
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(blob, [blob])
+ );
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'An object whose interface is deleted from the global object must still be received',
+ async f(runner) {
+ const {port1} = new MessageChannel();
+ const messagePortInterface = globalThis.MessagePort;
+ delete globalThis.MessagePort;
+ try {
+ const transfer = await runner.structuredClone(port1, [port1]);
+ assert_true(transfer instanceof messagePortInterface);
+ } finally {
+ globalThis.MessagePort = messagePortInterface;
+ }
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'A subclass instance will be received as its closest transferable superclass',
+ async f(runner) {
+ // MessagePort doesn't have a constructor, so we must use something else.
+
+ // Make sure that ReadableStream is transferable before we test its subclasses.
+ try {
+ const stream = new ReadableStream();
+ await runner.structuredClone(stream, [stream]);
+ } catch(err) {
+ if (err instanceof DOMException && err.code === DOMException.DATA_CLONE_ERR) {
+ throw new OptionalFeatureUnsupportedError("ReadableStream isn't transferable");
+ } else {
+ throw err;
+ }
+ }
+
+ class ReadableStreamSubclass extends ReadableStream {}
+ const original = new ReadableStreamSubclass();
+ const transfer = await runner.structuredClone(original, [original]);
+ assert_equals(Object.getPrototypeOf(transfer), ReadableStream.prototype);
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Resizable ArrayBuffer is transferable',
+ async f(runner) {
+ const buffer = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const copy = await runner.structuredClone(buffer, [buffer]);
+ assert_equals(buffer.byteLength, 0);
+ assert_equals(copy.byteLength, 16);
+ assert_equals(copy.maxByteLength, 1024);
+ assert_true(copy.resizable);
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Length-tracking TypedArray is transferable',
+ async f(runner) {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const ta = new Uint8Array(ab);
+ const copy = await runner.structuredClone(ta, [ab]);
+ assert_equals(ab.byteLength, 0);
+ assert_equals(copy.buffer.byteLength, 16);
+ assert_equals(copy.buffer.maxByteLength, 1024);
+ assert_true(copy.buffer.resizable);
+ copy.buffer.resize(32);
+ assert_equals(copy.byteLength, 32);
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Length-tracking DataView is transferable',
+ async f(runner) {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const dv = new DataView(ab);
+ const copy = await runner.structuredClone(dv, [ab]);
+ assert_equals(ab.byteLength, 0);
+ assert_equals(copy.buffer.byteLength, 16);
+ assert_equals(copy.buffer.maxByteLength, 1024);
+ assert_true(copy.buffer.resizable);
+ copy.buffer.resize(32);
+ assert_equals(copy.byteLength, 32);
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Transferring OOB TypedArray throws',
+ async f(runner, t) {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const ta = new Uint8Array(ab, 8);
+ ab.resize(0);
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(ta, [ab])
+ );
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Transferring OOB DataView throws',
+ async f(runner, t) {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const dv = new DataView(ab, 8);
+ ab.resize(0);
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(dv, [ab])
+ );
+ }
+});
diff --git a/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests.js b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
new file mode 100644
index 0000000000..923ac9dc16
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
@@ -0,0 +1,753 @@
+/* This file is mostly a remix of @zcorpan’s web worker test suite */
+
+structuredCloneBatteryOfTests = [];
+
+function check(description, input, callback, requiresDocument = false) {
+ structuredCloneBatteryOfTests.push({
+ description,
+ async f(runner) {
+ let newInput = input;
+ if (typeof input === 'function') {
+ newInput = input();
+ }
+ const copy = await runner.structuredClone(newInput);
+ await callback(copy, newInput);
+ },
+ requiresDocument
+ });
+}
+
+function compare_primitive(actual, input) {
+ assert_equals(actual, input);
+}
+function compare_Array(callback) {
+ return async function(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Array, 'instanceof Array');
+ assert_not_equals(actual, input);
+ assert_equals(actual.length, input.length, 'length');
+ await callback(actual, input);
+ }
+}
+
+function compare_Object(callback) {
+ return async function(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Object, 'instanceof Object');
+ assert_false(actual instanceof Array, 'instanceof Array');
+ assert_not_equals(actual, input);
+ await callback(actual, input);
+ }
+}
+
+function enumerate_props(compare_func) {
+ return async function(actual, input) {
+ for (const x in input) {
+ await compare_func(actual[x], input[x]);
+ }
+ };
+}
+
+check('primitive undefined', undefined, compare_primitive);
+check('primitive null', null, compare_primitive);
+check('primitive true', true, compare_primitive);
+check('primitive false', false, compare_primitive);
+check('primitive string, empty string', '', compare_primitive);
+check('primitive string, lone high surrogate', '\uD800', compare_primitive);
+check('primitive string, lone low surrogate', '\uDC00', compare_primitive);
+check('primitive string, NUL', '\u0000', compare_primitive);
+check('primitive string, astral character', '\uDBFF\uDFFD', compare_primitive);
+check('primitive number, 0.2', 0.2, compare_primitive);
+check('primitive number, 0', 0, compare_primitive);
+check('primitive number, -0', -0, compare_primitive);
+check('primitive number, NaN', NaN, compare_primitive);
+check('primitive number, Infinity', Infinity, compare_primitive);
+check('primitive number, -Infinity', -Infinity, compare_primitive);
+check('primitive number, 9007199254740992', 9007199254740992, compare_primitive);
+check('primitive number, -9007199254740992', -9007199254740992, compare_primitive);
+check('primitive number, 9007199254740994', 9007199254740994, compare_primitive);
+check('primitive number, -9007199254740994', -9007199254740994, compare_primitive);
+check('primitive BigInt, 0n', 0n, compare_primitive);
+check('primitive BigInt, -0n', -0n, compare_primitive);
+check('primitive BigInt, -9007199254740994000n', -9007199254740994000n, compare_primitive);
+check('primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n', -9007199254740994000900719925474099400090071992547409940009007199254740994000n, compare_primitive);
+
+check('Array primitives', [undefined,
+ null,
+ true,
+ false,
+ '',
+ '\uD800',
+ '\uDC00',
+ '\u0000',
+ '\uDBFF\uDFFD',
+ 0.2,
+ 0,
+ -0,
+ NaN,
+ Infinity,
+ -Infinity,
+ 9007199254740992,
+ -9007199254740992,
+ 9007199254740994,
+ -9007199254740994,
+ -12n,
+ -0n,
+ 0n], compare_Array(enumerate_props(compare_primitive)));
+check('Object primitives', {'undefined':undefined,
+ 'null':null,
+ 'true':true,
+ 'false':false,
+ 'empty':'',
+ 'high surrogate':'\uD800',
+ 'low surrogate':'\uDC00',
+ 'nul':'\u0000',
+ 'astral':'\uDBFF\uDFFD',
+ '0.2':0.2,
+ '0':0,
+ '-0':-0,
+ 'NaN':NaN,
+ 'Infinity':Infinity,
+ '-Infinity':-Infinity,
+ '9007199254740992':9007199254740992,
+ '-9007199254740992':-9007199254740992,
+ '9007199254740994':9007199254740994,
+ '-9007199254740994':-9007199254740994}, compare_Object(enumerate_props(compare_primitive)));
+
+function compare_Boolean(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Boolean, 'instanceof Boolean');
+ assert_equals(String(actual), String(input), 'converted to primitive');
+ assert_not_equals(actual, input);
+}
+check('Boolean true', new Boolean(true), compare_Boolean);
+check('Boolean false', new Boolean(false), compare_Boolean);
+check('Array Boolean objects', [new Boolean(true), new Boolean(false)], compare_Array(enumerate_props(compare_Boolean)));
+check('Object Boolean objects', {'true':new Boolean(true), 'false':new Boolean(false)}, compare_Object(enumerate_props(compare_Boolean)));
+
+function compare_obj(what) {
+ const Type = self[what];
+ return function(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Type, 'instanceof '+what);
+ assert_equals(Type(actual), Type(input), 'converted to primitive');
+ assert_not_equals(actual, input);
+ };
+}
+check('String empty string', new String(''), compare_obj('String'));
+check('String lone high surrogate', new String('\uD800'), compare_obj('String'));
+check('String lone low surrogate', new String('\uDC00'), compare_obj('String'));
+check('String NUL', new String('\u0000'), compare_obj('String'));
+check('String astral character', new String('\uDBFF\uDFFD'), compare_obj('String'));
+check('Array String objects', [new String(''),
+ new String('\uD800'),
+ new String('\uDC00'),
+ new String('\u0000'),
+ new String('\uDBFF\uDFFD')], compare_Array(enumerate_props(compare_obj('String'))));
+check('Object String objects', {'empty':new String(''),
+ 'high surrogate':new String('\uD800'),
+ 'low surrogate':new String('\uDC00'),
+ 'nul':new String('\u0000'),
+ 'astral':new String('\uDBFF\uDFFD')}, compare_Object(enumerate_props(compare_obj('String'))));
+
+check('Number 0.2', new Number(0.2), compare_obj('Number'));
+check('Number 0', new Number(0), compare_obj('Number'));
+check('Number -0', new Number(-0), compare_obj('Number'));
+check('Number NaN', new Number(NaN), compare_obj('Number'));
+check('Number Infinity', new Number(Infinity), compare_obj('Number'));
+check('Number -Infinity', new Number(-Infinity), compare_obj('Number'));
+check('Number 9007199254740992', new Number(9007199254740992), compare_obj('Number'));
+check('Number -9007199254740992', new Number(-9007199254740992), compare_obj('Number'));
+check('Number 9007199254740994', new Number(9007199254740994), compare_obj('Number'));
+check('Number -9007199254740994', new Number(-9007199254740994), compare_obj('Number'));
+// BigInt does not have a non-throwing constructor
+check('BigInt -9007199254740994n', Object(-9007199254740994n), compare_obj('BigInt'));
+
+check('Array Number objects', [new Number(0.2),
+ new Number(0),
+ new Number(-0),
+ new Number(NaN),
+ new Number(Infinity),
+ new Number(-Infinity),
+ new Number(9007199254740992),
+ new Number(-9007199254740992),
+ new Number(9007199254740994),
+ new Number(-9007199254740994)], compare_Array(enumerate_props(compare_obj('Number'))));
+check('Object Number objects', {'0.2':new Number(0.2),
+ '0':new Number(0),
+ '-0':new Number(-0),
+ 'NaN':new Number(NaN),
+ 'Infinity':new Number(Infinity),
+ '-Infinity':new Number(-Infinity),
+ '9007199254740992':new Number(9007199254740992),
+ '-9007199254740992':new Number(-9007199254740992),
+ '9007199254740994':new Number(9007199254740994),
+ '-9007199254740994':new Number(-9007199254740994)}, compare_Object(enumerate_props(compare_obj('Number'))));
+
+function compare_Date(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Date, 'instanceof Date');
+ assert_equals(Number(actual), Number(input), 'converted to primitive');
+ assert_not_equals(actual, input);
+}
+check('Date 0', new Date(0), compare_Date);
+check('Date -0', new Date(-0), compare_Date);
+check('Date -8.64e15', new Date(-8.64e15), compare_Date);
+check('Date 8.64e15', new Date(8.64e15), compare_Date);
+check('Array Date objects', [new Date(0),
+ new Date(-0),
+ new Date(-8.64e15),
+ new Date(8.64e15)], compare_Array(enumerate_props(compare_Date)));
+check('Object Date objects', {'0':new Date(0),
+ '-0':new Date(-0),
+ '-8.64e15':new Date(-8.64e15),
+ '8.64e15':new Date(8.64e15)}, compare_Object(enumerate_props(compare_Date)));
+
+function compare_RegExp(expected_source) {
+ // XXX ES6 spec doesn't define exact serialization for `source` (it allows several ways to escape)
+ return function(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof RegExp, 'instanceof RegExp');
+ assert_equals(actual.global, input.global, 'global');
+ assert_equals(actual.ignoreCase, input.ignoreCase, 'ignoreCase');
+ assert_equals(actual.multiline, input.multiline, 'multiline');
+ assert_equals(actual.source, expected_source, 'source');
+ assert_equals(actual.sticky, input.sticky, 'sticky');
+ assert_equals(actual.unicode, input.unicode, 'unicode');
+ assert_equals(actual.lastIndex, 0, 'lastIndex');
+ assert_not_equals(actual, input);
+ }
+}
+function func_RegExp_flags_lastIndex() {
+ const r = /foo/gim;
+ r.lastIndex = 2;
+ return r;
+}
+function func_RegExp_sticky() {
+ return new RegExp('foo', 'y');
+}
+function func_RegExp_unicode() {
+ return new RegExp('foo', 'u');
+}
+check('RegExp flags and lastIndex', func_RegExp_flags_lastIndex, compare_RegExp('foo'));
+check('RegExp sticky flag', func_RegExp_sticky, compare_RegExp('foo'));
+check('RegExp unicode flag', func_RegExp_unicode, compare_RegExp('foo'));
+check('RegExp empty', new RegExp(''), compare_RegExp('(?:)'));
+check('RegExp slash', new RegExp('/'), compare_RegExp('\\/'));
+check('RegExp new line', new RegExp('\n'), compare_RegExp('\\n'));
+check('Array RegExp object, RegExp flags and lastIndex', [func_RegExp_flags_lastIndex()], compare_Array(enumerate_props(compare_RegExp('foo'))));
+check('Array RegExp object, RegExp sticky flag', function() { return [func_RegExp_sticky()]; }, compare_Array(enumerate_props(compare_RegExp('foo'))));
+check('Array RegExp object, RegExp unicode flag', function() { return [func_RegExp_unicode()]; }, compare_Array(enumerate_props(compare_RegExp('foo'))));
+check('Array RegExp object, RegExp empty', [new RegExp('')], compare_Array(enumerate_props(compare_RegExp('(?:)'))));
+check('Array RegExp object, RegExp slash', [new RegExp('/')], compare_Array(enumerate_props(compare_RegExp('\\/'))));
+check('Array RegExp object, RegExp new line', [new RegExp('\n')], compare_Array(enumerate_props(compare_RegExp('\\n'))));
+check('Object RegExp object, RegExp flags and lastIndex', {'x':func_RegExp_flags_lastIndex()}, compare_Object(enumerate_props(compare_RegExp('foo'))));
+check('Object RegExp object, RegExp sticky flag', function() { return {'x':func_RegExp_sticky()}; }, compare_Object(enumerate_props(compare_RegExp('foo'))));
+check('Object RegExp object, RegExp unicode flag', function() { return {'x':func_RegExp_unicode()}; }, compare_Object(enumerate_props(compare_RegExp('foo'))));
+check('Object RegExp object, RegExp empty', {'x':new RegExp('')}, compare_Object(enumerate_props(compare_RegExp('(?:)'))));
+check('Object RegExp object, RegExp slash', {'x':new RegExp('/')}, compare_Object(enumerate_props(compare_RegExp('\\/'))));
+check('Object RegExp object, RegExp new line', {'x':new RegExp('\n')}, compare_Object(enumerate_props(compare_RegExp('\\n'))));
+
+function compare_Error(actual, input) {
+ assert_true(actual instanceof Error, "Checking instanceof");
+ assert_equals(actual.constructor, input.constructor, "Checking constructor");
+ assert_equals(actual.name, input.name, "Checking name");
+ assert_equals(actual.hasOwnProperty("message"), input.hasOwnProperty("message"), "Checking message existence");
+ assert_equals(actual.message, input.message, "Checking message");
+ assert_equals(actual.foo, undefined, "Checking for absence of custom property");
+}
+
+check('Empty Error object', new Error, compare_Error);
+
+const errorConstructors = [Error, EvalError, RangeError, ReferenceError,
+ SyntaxError, TypeError, URIError];
+for (const constructor of errorConstructors) {
+ check(`${constructor.name} object`, () => {
+ let error = new constructor("Error message here");
+ error.foo = "testing";
+ return error;
+ }, compare_Error);
+}
+
+async function compare_Blob(actual, input, expect_File) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Blob, 'instanceof Blob');
+ if (!expect_File)
+ assert_false(actual instanceof File, 'instanceof File');
+ assert_equals(actual.size, input.size, 'size');
+ assert_equals(actual.type, input.type, 'type');
+ assert_not_equals(actual, input);
+ const ab1 = await new Response(actual).arrayBuffer();
+ const ab2 = await new Response(input).arrayBuffer();
+ assert_equals(ab1.byteLength, ab2.byteLength, 'byteLength');
+ const ta1 = new Uint8Array(ab1);
+ const ta2 = new Uint8Array(ab2);
+ for(let i = 0; i < ta1.size; i++) {
+ assert_equals(ta1[i], ta2[i]);
+ }
+}
+function func_Blob_basic() {
+ return new Blob(['foo'], {type:'text/x-bar'});
+}
+check('Blob basic', func_Blob_basic, compare_Blob);
+
+function b(str) {
+ return parseInt(str, 2);
+}
+function encode_cesu8(codeunits) {
+ // http://www.unicode.org/reports/tr26/ section 2.2
+ // only the 3-byte form is supported
+ const rv = [];
+ codeunits.forEach(function(codeunit) {
+ rv.push(b('11100000') + ((codeunit & b('1111000000000000')) >> 12));
+ rv.push(b('10000000') + ((codeunit & b('0000111111000000')) >> 6));
+ rv.push(b('10000000') + (codeunit & b('0000000000111111')));
+ });
+ return rv;
+}
+function func_Blob_bytes(arr) {
+ return function() {
+ const buffer = new ArrayBuffer(arr.length);
+ const view = new DataView(buffer);
+ for (let i = 0; i < arr.length; ++i) {
+ view.setUint8(i, arr[i]);
+ }
+ return new Blob([view]);
+ };
+}
+check('Blob unpaired high surrogate (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xD800])), compare_Blob);
+check('Blob unpaired low surrogate (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xDC00])), compare_Blob);
+check('Blob paired surrogates (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xD800, 0xDC00])), compare_Blob);
+
+function func_Blob_empty() {
+ return new Blob(['']);
+}
+check('Blob empty', func_Blob_empty , compare_Blob);
+function func_Blob_NUL() {
+ return new Blob(['\u0000']);
+}
+check('Blob NUL', func_Blob_NUL, compare_Blob);
+
+check('Array Blob object, Blob basic', [func_Blob_basic()], compare_Array(enumerate_props(compare_Blob)));
+check('Array Blob object, Blob unpaired high surrogate (invalid utf-8)', [func_Blob_bytes([0xD800])()], compare_Array(enumerate_props(compare_Blob)));
+check('Array Blob object, Blob unpaired low surrogate (invalid utf-8)', [func_Blob_bytes([0xDC00])()], compare_Array(enumerate_props(compare_Blob)));
+check('Array Blob object, Blob paired surrogates (invalid utf-8)', [func_Blob_bytes([0xD800, 0xDC00])()], compare_Array(enumerate_props(compare_Blob)));
+check('Array Blob object, Blob empty', [func_Blob_empty()], compare_Array(enumerate_props(compare_Blob)));
+check('Array Blob object, Blob NUL', [func_Blob_NUL()], compare_Array(enumerate_props(compare_Blob)));
+check('Array Blob object, two Blobs', [func_Blob_basic(), func_Blob_empty()], compare_Array(enumerate_props(compare_Blob)));
+
+check('Object Blob object, Blob basic', {'x':func_Blob_basic()}, compare_Object(enumerate_props(compare_Blob)));
+check('Object Blob object, Blob unpaired high surrogate (invalid utf-8)', {'x':func_Blob_bytes([0xD800])()}, compare_Object(enumerate_props(compare_Blob)));
+check('Object Blob object, Blob unpaired low surrogate (invalid utf-8)', {'x':func_Blob_bytes([0xDC00])()}, compare_Object(enumerate_props(compare_Blob)));
+check('Object Blob object, Blob paired surrogates (invalid utf-8)', {'x':func_Blob_bytes([0xD800, 0xDC00])() }, compare_Object(enumerate_props(compare_Blob)));
+check('Object Blob object, Blob empty', {'x':func_Blob_empty()}, compare_Object(enumerate_props(compare_Blob)));
+check('Object Blob object, Blob NUL', {'x':func_Blob_NUL()}, compare_Object(enumerate_props(compare_Blob)));
+
+async function compare_File(actual, input) {
+ assert_true(actual instanceof File, 'instanceof File');
+ assert_equals(actual.name, input.name, 'name');
+ assert_equals(actual.lastModified, input.lastModified, 'lastModified');
+ await compare_Blob(actual, input, true);
+}
+function func_File_basic() {
+ return new File(['foo'], 'bar', {type:'text/x-bar', lastModified:42});
+}
+check('File basic', func_File_basic, compare_File);
+
+function compare_FileList(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof FileList, 'instanceof FileList');
+ assert_equals(actual.length, input.length, 'length');
+ assert_not_equals(actual, input);
+ // XXX when there's a way to populate or construct a FileList,
+ // check the items in the FileList
+}
+function func_FileList_empty() {
+ const input = document.createElement('input');
+ input.type = 'file';
+ return input.files;
+}
+check('FileList empty', func_FileList_empty, compare_FileList, true);
+check('Array FileList object, FileList empty', () => ([func_FileList_empty()]), compare_Array(enumerate_props(compare_FileList)), true);
+check('Object FileList object, FileList empty', () => ({'x':func_FileList_empty()}), compare_Object(enumerate_props(compare_FileList)), true);
+
+function compare_ArrayBuffer(actual, input) {
+ assert_true(actual instanceof ArrayBuffer, 'instanceof ArrayBuffer');
+ assert_equals(actual.byteLength, input.byteLength, 'byteLength');
+ assert_equals(actual.maxByteLength, input.maxByteLength, 'maxByteLength');
+ assert_equals(actual.resizable, input.resizable, 'resizable');
+ assert_equals(actual.growable, input.growable, 'growable');
+}
+
+function compare_ArrayBufferView(view) {
+ const Type = self[view];
+ return function(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Type, 'instanceof '+view);
+ assert_equals(actual.length, input.length, 'length');
+ assert_equals(actual.byteLength, input.byteLength, 'byteLength');
+ assert_equals(actual.byteOffset, input.byteOffset, 'byteOffset');
+ assert_not_equals(actual.buffer, input.buffer, 'buffer');
+ for (let i = 0; i < actual.length; ++i) {
+ assert_equals(actual[i], input[i], 'actual['+i+']');
+ }
+ };
+}
+function compare_ImageData(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_equals(actual.width, input.width, 'width');
+ assert_equals(actual.height, input.height, 'height');
+ assert_not_equals(actual.data, input.data, 'data');
+ compare_ArrayBufferView('Uint8ClampedArray')(actual.data, input.data, null);
+}
+function func_ImageData_1x1_transparent_black() {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+ return ctx.createImageData(1, 1);
+}
+check('ImageData 1x1 transparent black', func_ImageData_1x1_transparent_black, compare_ImageData, true);
+function func_ImageData_1x1_non_transparent_non_black() {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+ const imagedata = ctx.createImageData(1, 1);
+ imagedata.data[0] = 100;
+ imagedata.data[1] = 101;
+ imagedata.data[2] = 102;
+ imagedata.data[3] = 103;
+ return imagedata;
+}
+check('ImageData 1x1 non-transparent non-black', func_ImageData_1x1_non_transparent_non_black, compare_ImageData, true);
+check('Array ImageData object, ImageData 1x1 transparent black', () => ([func_ImageData_1x1_transparent_black()]), compare_Array(enumerate_props(compare_ImageData)), true);
+check('Array ImageData object, ImageData 1x1 non-transparent non-black', () => ([func_ImageData_1x1_non_transparent_non_black()]), compare_Array(enumerate_props(compare_ImageData)), true);
+check('Object ImageData object, ImageData 1x1 transparent black', () => ({'x':func_ImageData_1x1_transparent_black()}), compare_Object(enumerate_props(compare_ImageData)), true);
+check('Object ImageData object, ImageData 1x1 non-transparent non-black', () => ({'x':func_ImageData_1x1_non_transparent_non_black()}), compare_Object(enumerate_props(compare_ImageData)), true);
+
+
+check('Array sparse', new Array(10), compare_Array(enumerate_props(compare_primitive)));
+check('Array with non-index property', function() {
+ const rv = [];
+ rv.foo = 'bar';
+ return rv;
+}, compare_Array(enumerate_props(compare_primitive)));
+check('Object with index property and length', {'0':'foo', 'length':1}, compare_Object(enumerate_props(compare_primitive)));
+function check_circular_property(prop) {
+ return function(actual) {
+ assert_equals(actual[prop], actual);
+ };
+}
+check('Array with circular reference', function() {
+ const rv = [];
+ rv[0] = rv;
+ return rv;
+}, compare_Array(check_circular_property('0')));
+check('Object with circular reference', function() {
+ const rv = {};
+ rv['x'] = rv;
+ return rv;
+}, compare_Object(check_circular_property('x')));
+function check_identical_property_values(prop1, prop2) {
+ return function(actual) {
+ assert_equals(actual[prop1], actual[prop2]);
+ };
+}
+check('Array with identical property values', function() {
+ const obj = {}
+ return [obj, obj];
+}, compare_Array(check_identical_property_values('0', '1')));
+check('Object with identical property values', function() {
+ const obj = {}
+ return {'x':obj, 'y':obj};
+}, compare_Object(check_identical_property_values('x', 'y')));
+
+function check_absent_property(prop) {
+ return function(actual) {
+ assert_false(prop in actual);
+ };
+}
+check('Object with property on prototype', function() {
+ const Foo = function() {};
+ Foo.prototype = {'foo':'bar'};
+ return new Foo();
+}, compare_Object(check_absent_property('foo')));
+
+check('Object with non-enumerable property', function() {
+ const rv = {};
+ Object.defineProperty(rv, 'foo', {value:'bar', enumerable:false, writable:true, configurable:true});
+ return rv;
+}, compare_Object(check_absent_property('foo')));
+
+function check_writable_property(prop) {
+ return function(actual, input) {
+ assert_equals(actual[prop], input[prop]);
+ actual[prop] += ' baz';
+ assert_equals(actual[prop], input[prop] + ' baz');
+ };
+}
+check('Object with non-writable property', function() {
+ const rv = {};
+ Object.defineProperty(rv, 'foo', {value:'bar', enumerable:true, writable:false, configurable:true});
+ return rv;
+}, compare_Object(check_writable_property('foo')));
+
+function check_configurable_property(prop) {
+ return function(actual, input) {
+ assert_equals(actual[prop], input[prop]);
+ delete actual[prop];
+ assert_false('prop' in actual);
+ };
+}
+check('Object with non-configurable property', function() {
+ const rv = {};
+ Object.defineProperty(rv, 'foo', {value:'bar', enumerable:true, writable:true, configurable:false});
+ return rv;
+}, compare_Object(check_configurable_property('foo')));
+
+structuredCloneBatteryOfTests.push({
+ description: 'Object with a getter that throws',
+ async f(runner, t) {
+ const exception = new Error();
+ const testObject = {
+ get testProperty() {
+ throw exception;
+ }
+ };
+ await promise_rejects_exactly(
+ t,
+ exception,
+ runner.structuredClone(testObject)
+ );
+ }
+});
+
+/* The tests below are inspired by @zcorpan’s work but got some
+more substantial changed due to their previous async setup */
+
+function get_canvas_1x1_transparent_black() {
+ const canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = 1;
+ return canvas;
+}
+
+function get_canvas_1x1_non_transparent_non_black() {
+ const canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = 1;
+ const ctx = canvas.getContext('2d');
+ const imagedata = ctx.getImageData(0, 0, 1, 1);
+ imagedata.data[0] = 100;
+ imagedata.data[1] = 101;
+ imagedata.data[2] = 102;
+ imagedata.data[3] = 103;
+ return canvas;
+}
+
+function compare_ImageBitmap(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof ImageBitmap, 'instanceof ImageBitmap');
+ assert_not_equals(actual, input);
+ // XXX paint the ImageBitmap on a canvas and check the data
+}
+
+structuredCloneBatteryOfTests.push({
+ description: 'ImageBitmap 1x1 transparent black',
+ async f(runner) {
+ const canvas = get_canvas_1x1_transparent_black();
+ const bm = await createImageBitmap(canvas);
+ const copy = await runner.structuredClone(bm);
+ compare_ImageBitmap(bm, copy);
+ },
+ requiresDocument: true
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'ImageBitmap 1x1 non-transparent non-black',
+ async f(runner) {
+ const canvas = get_canvas_1x1_non_transparent_non_black();
+ const bm = await createImageBitmap(canvas);
+ const copy = await runner.structuredClone(bm);
+ compare_ImageBitmap(bm, copy);
+ },
+ requiresDocument: true
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Array ImageBitmap object, ImageBitmap 1x1 transparent black',
+ async f(runner) {
+ const canvas = get_canvas_1x1_transparent_black();
+ const bm = [await createImageBitmap(canvas)];
+ const copy = await runner.structuredClone(bm);
+ compare_Array(enumerate_props(compare_ImageBitmap))(bm, copy);
+ },
+ requiresDocument: true
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Array ImageBitmap object, ImageBitmap 1x1 transparent non-black',
+ async f(runner) {
+ const canvas = get_canvas_1x1_non_transparent_non_black();
+ const bm = [await createImageBitmap(canvas)];
+ const copy = await runner.structuredClone(bm);
+ compare_Array(enumerate_props(compare_ImageBitmap))(bm, copy);
+ },
+ requiresDocument: true
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Object ImageBitmap object, ImageBitmap 1x1 transparent black',
+ async f(runner) {
+ const canvas = get_canvas_1x1_transparent_black();
+ const bm = {x: await createImageBitmap(canvas)};
+ const copy = await runner.structuredClone(bm);
+ compare_Object(enumerate_props(compare_ImageBitmap))(bm, copy);
+ },
+ requiresDocument: true
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Object ImageBitmap object, ImageBitmap 1x1 transparent non-black',
+ async f(runner) {
+ const canvas = get_canvas_1x1_non_transparent_non_black();
+ const bm = {x: await createImageBitmap(canvas)};
+ const copy = await runner.structuredClone(bm);
+ compare_Object(enumerate_props(compare_ImageBitmap))(bm, copy);
+ },
+ requiresDocument: true
+});
+
+check('ObjectPrototype must lose its exotic-ness when cloned',
+ () => Object.prototype,
+ (copy, original) => {
+ assert_not_equals(copy, original);
+ assert_true(copy instanceof Object);
+
+ const newProto = { some: 'proto' };
+ // Must not throw:
+ Object.setPrototypeOf(copy, newProto);
+
+ assert_equals(Object.getPrototypeOf(copy), newProto);
+ }
+);
+
+structuredCloneBatteryOfTests.push({
+ description: 'Serializing a non-serializable platform object fails',
+ async f(runner, t) {
+ const request = new Response();
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(request)
+ );
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'An object whose interface is deleted from the global must still deserialize',
+ async f(runner) {
+ const blob = new Blob();
+ const blobInterface = globalThis.Blob;
+ delete globalThis.Blob;
+ try {
+ const copy = await runner.structuredClone(blob);
+ assert_true(copy instanceof blobInterface);
+ } finally {
+ globalThis.Blob = blobInterface;
+ }
+ }
+});
+
+check(
+ 'A subclass instance will deserialize as its closest serializable superclass',
+ () => {
+ class FileSubclass extends File {}
+ return new FileSubclass([], "");
+ },
+ (copy) => {
+ assert_equals(Object.getPrototypeOf(copy), File.prototype);
+ }
+);
+
+check(
+ 'Resizable ArrayBuffer',
+ () => {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ assert_true(ab.resizable);
+ return ab;
+ },
+ compare_ArrayBuffer);
+
+structuredCloneBatteryOfTests.push({
+ description: 'Growable SharedArrayBuffer',
+ async f(runner) {
+ const sab = createBuffer('SharedArrayBuffer', 16, { maxByteLength: 1024 });
+ assert_true(sab.growable);
+ try {
+ const copy = await runner.structuredClone(sab);
+ compare_ArrayBuffer(sab, copy);
+ } catch (e) {
+ // If we're cross-origin isolated, cloning SABs should not fail.
+ if (e instanceof DOMException && e.code === DOMException.DATA_CLONE_ERR) {
+ assert_false(self.crossOriginIsolated);
+ } else {
+ throw e;
+ }
+ }
+ }
+});
+
+check(
+ 'Length-tracking TypedArray',
+ () => {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ assert_true(ab.resizable);
+ return new Uint8Array(ab);
+ },
+ compare_ArrayBufferView('Uint8Array'));
+
+check(
+ 'Length-tracking DataView',
+ () => {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ assert_true(ab.resizable);
+ return new DataView(ab);
+ },
+ compare_ArrayBufferView('DataView'));
+
+structuredCloneBatteryOfTests.push({
+ description: 'Serializing OOB TypedArray throws',
+ async f(runner, t) {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const ta = new Uint8Array(ab, 8);
+ ab.resize(0);
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(ta)
+ );
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Serializing OOB DataView throws',
+ async f(runner, t) {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const dv = new DataView(ab, 8);
+ ab.resize(0);
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(dv)
+ );
+ }
+});
diff --git a/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-cross-realm-method.html b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-cross-realm-method.html
new file mode 100644
index 0000000000..d80010e0df
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-cross-realm-method.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>self.structuredClone() uses this's relevant Realm for deserialization</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/structured-data.html#structured-cloning">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+const iframe = document.createElement("iframe");
+iframe.onload = () => {
+ const otherWindow = iframe.contentWindow;
+ for (const key of ["Object", "Array", "Date", "RegExp"]) {
+ test(() => {
+ const cloned = otherWindow.structuredClone.call(window, new otherWindow[key]);
+ assert_true(cloned instanceof window[key]);
+ }, `${key} instance`);
+ }
+};
+document.body.append(iframe);
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-detached-window-crash.html b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-detached-window-crash.html
new file mode 100644
index 0000000000..75971023d2
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-detached-window-crash.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>window.structuredClone() doesn't crash when window is detached</title>
+<link rel="help" href="https://crbug.com/1445375">
+<body>
+<script>
+let i = document.createElement("iframe");
+document.body.appendChild(i);
+let i_win = i.contentWindow;
+i.remove();
+i_win.structuredClone("some data");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone.any.js b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone.any.js
new file mode 100644
index 0000000000..1358a71fc0
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone.any.js
@@ -0,0 +1,14 @@
+// META: title=structuredClone() tests
+// META: script=/common/sab.js
+// META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
+// META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
+// META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
+
+runStructuredCloneBatteryOfTests({
+ structuredClone: (obj, transfer) => {
+ return new Promise(resolve => {
+ resolve(self.structuredClone(obj, { transfer }));
+ });
+ },
+ hasDocument: typeof document !== "undefined",
+});
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/clientinformation.window.js b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/clientinformation.window.js
new file mode 100644
index 0000000000..197a3b13e9
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/clientinformation.window.js
@@ -0,0 +1,8 @@
+test(() => {
+ assert_equals(window.clientInformation, window.navigator);
+}, "window.clientInformation exists and equals window.navigator");
+
+test(() => {
+ window.clientInformation = 1;
+ assert_equals(window.clientInformation, 1);
+}, "window.clientInformation is Replaceable");
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/get-navigatorlanguage-manual.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/get-navigatorlanguage-manual.html
new file mode 100644
index 0000000000..4bdab91121
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/get-navigatorlanguage-manual.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>NavigatorLanguage: navigator.language returns the user's preferred language</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#navigatorlanguage">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h2>Precondition</h2>
+<p>The user agent's preferred language is set as English (en).</p>
+<div id="log"></div>
+<script>
+ test(function() {
+ assert_equals(navigator.language, "en");
+ });
+</script>
+
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/historical.https.window.js b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/historical.https.window.js
new file mode 100644
index 0000000000..f6b9db078e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/historical.https.window.js
@@ -0,0 +1,16 @@
+[
+ "registerContentHandler",
+ "isProtocolHandlerRegistered",
+ "isContentHandlerRegistered",
+ "unregisterContentHandler"
+].forEach(method => {
+ test(() => {
+ assert_false(method in self.navigator);
+ }, method + "() is removed");
+});
+
+test(() => {
+ let called = false;
+ self.navigator.registerProtocolHandler("web+test", "%s", { toString: () => called = true });
+ assert_false(called);
+}, "registerProtocolHandler has no third argument");
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-indexed.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-indexed.html
new file mode 100644
index 0000000000..a971fe9d1c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-indexed.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for lack of indexed getter on Navigator</title>
+<link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-navigator-object">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_false("0" in window.navigator);
+ assert_equals(window.navigator[0], undefined);
+}, "window.navigator[0] should not exist");
+test(function() {
+ window.navigator[0] = "pass";
+ assert_true("0" in window.navigator);
+ assert_equals(window.navigator[0], "pass");
+}, "window.navigator[0] should be settable");
+test(function() {
+ assert_false("-1" in window.navigator);
+ assert_equals(window.navigator[-1], undefined);
+}, "window.navigator[-1] should not exist");
+test(function() {
+ window.navigator[-1] = "pass";
+ assert_true("-1" in window.navigator);
+ assert_equals(window.navigator[-1], "pass");
+}, "window.navigator[-1] should be settable");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-window-controls-overlay.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-window-controls-overlay.html
new file mode 100644
index 0000000000..9cff8d3163
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-window-controls-overlay.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset='utf-8'>
+<title>navigator.windowControlsOverlay</title>
+
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+
+<script>
+ test(function(){
+ assert_idl_attribute(navigator, 'windowControlsOverlay');
+ }, 'the windowControlsOverlay object should exist on the navigator object');
+
+ test(function(){
+ assert_idl_attribute(navigator.windowControlsOverlay, 'visible');
+ }, 'visible should be a member of the windowControlsOverlay object');
+
+ test(function(){
+ assert_false(navigator.windowControlsOverlay.visible);
+ }, 'visible should be false');
+
+ test(function(){
+ assert_idl_attribute(navigator.windowControlsOverlay, 'getTitlebarAreaRect');
+ }, 'getTitlebarAreaRect should be a method of the windowControlsOverlay object');
+
+ test(function(){
+ var rect = navigator.windowControlsOverlay.getTitlebarAreaRect();
+ assert_true(rect instanceof DOMRect);
+ }, 'getTitlebarAreaRect return type should be DOMRect');
+
+ test(function(){
+ var rect = navigator.windowControlsOverlay.getTitlebarAreaRect();
+ assert_equals(rect.x, 0);
+ assert_equals(rect.y, 0);
+ assert_equals(rect.width, 0);
+ assert_equals(rect.height, 0);
+ }, 'getTitlebarAreaRect should return a empty DOMRect');
+
+ test(function(){
+ assert_idl_attribute(navigator.windowControlsOverlay, 'ongeometrychange');
+ }, 'ongeometrychange should be a member of the windowControlsOverlay object');
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator.any.js b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator.any.js
new file mode 100644
index 0000000000..07bccb7880
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator.any.js
@@ -0,0 +1,106 @@
+ var compatibilityMode;
+ if (navigator.userAgent.includes("Chrome")) {
+ compatibilityMode = "Chrome";
+ } else if (navigator.userAgent.includes("WebKit")) {
+ compatibilityMode = "WebKit";
+ } else {
+ compatibilityMode = "Gecko";
+ }
+
+ test(function() {
+ assert_equals(navigator.appCodeName, "Mozilla");
+ }, "appCodeName");
+
+ test(function() {
+ assert_equals(navigator.appName, "Netscape");
+ }, "appName");
+
+ test(function() {
+ assert_equals(typeof navigator.appVersion, "string",
+ "navigator.appVersion should be a string");
+ }, "appVersion");
+
+ test(function() {
+ assert_equals(typeof navigator.platform, "string",
+ "navigator.platform should be a string");
+ }, "platform");
+
+ test(function() {
+ assert_equals(navigator.product, "Gecko");
+ }, "product");
+
+ test(function() {
+ if ("window" in self) {
+ if (compatibilityMode == "Gecko") {
+ assert_equals(navigator.productSub, "20100101");
+ } else {
+ assert_equals(navigator.productSub, "20030107");
+ }
+ } else {
+ assert_false("productSub" in navigator);
+ }
+ }, "productSub");
+
+ test(function() {
+ assert_equals(typeof navigator.userAgent, "string",
+ "navigator.userAgent should be a string");
+ }, "userAgent type");
+
+ async_test(function() {
+ var request = new XMLHttpRequest();
+ request.onload = this.step_func_done(function() {
+ assert_equals("User-Agent: " + navigator.userAgent + "\n",
+ request.response,
+ "userAgent should return the value sent in the " +
+ "User-Agent header");
+ });
+ request.open("GET", "/xhr/resources/inspect-headers.py?" +
+ "filter_name=User-Agent");
+ request.send();
+ }, "userAgent value");
+
+ test(function() {
+ if ("window" in self) {
+ if (compatibilityMode == "Chrome") {
+ assert_equals(navigator.vendor, "Google Inc.");
+ } else if (compatibilityMode == "WebKit") {
+ assert_equals(navigator.vendor, "Apple Computer, Inc.");
+ } else {
+ assert_equals(navigator.vendor, "");
+ }
+ } else {
+ assert_false("vendor" in navigator);
+ }
+ }, "vendor");
+
+ test(function() {
+ if ("window" in self) {
+ assert_equals(navigator.vendorSub, "");
+ } else {
+ assert_false("vendorSub" in navigator);
+ }
+ }, "vendorSub");
+
+ // "If the navigator compatibility mode is Gecko, then the user agent must
+ // also support the following partial interface" (taintEnabled() and oscpu)
+ // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=22555 and
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27820
+
+ test(function() {
+ if ("window" in self && compatibilityMode == "Gecko") {
+ assert_false(navigator.taintEnabled());
+ } else {
+ assert_false("taintEnabled" in navigator);
+ }
+ }, "taintEnabled");
+
+ test(function() {
+ if ("window" in self && compatibilityMode == "Gecko") {
+ assert_equals(typeof navigator.oscpu, "string",
+ "navigator.oscpu should be a string");
+ } else {
+ assert_false("oscpu" in navigator);
+ }
+ }, "oscpu");
+
+done()
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.https.html
new file mode 100644
index 0000000000..10b3dd2249
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.https.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ test(t => {
+ assert_true("userAgentData" in navigator);
+ assert_true("NavigatorUAData" in window);
+ assert_equals(typeof self.NavigatorUAData, "function")
+ }, "navigator.userAgentData is exposed.");
+
+ promise_test(async t => {
+ let didMicrotaskRun = false;
+ const uaData = navigator.userAgentData;
+ const brandRegex = /^[a-zA-Z ()\-.\/:;=?_]+$/;
+ for (brandVersionPair of uaData.brands) {
+ assert_equals(typeof brandVersionPair.brand, "string", "brand should be a string");
+ assert_regexp_match(brandVersionPair.brand, brandRegex, "brand should not contain unexpected characters");
+ assert_equals(typeof brandVersionPair.version, "string", "version should be a string");
+ }
+ assert_equals(typeof uaData.mobile, "boolean", "mobile should be a boolean");
+
+ const highEntropyData = await uaData.getHighEntropyValues([
+ "platformVersion", "architecture", "model", "uaFullVersion", "fullVersionList", "formFactor"]);
+ assert_equals(typeof highEntropyData["platform"], "string", "Platform brand should be a string");
+ assert_equals(typeof highEntropyData["platformVersion"], "string", "Platform version should be a string");
+ assert_equals(typeof highEntropyData["architecture"], "string", "Architecture should be a string");
+ assert_equals(typeof highEntropyData["model"], "string", "Model should be a string");
+ assert_equals(typeof highEntropyData["uaFullVersion"], "string", "UAFullVersion should be a string");
+ for (formFactor of highEntropyData['formFactor']) {
+ assert_equals(typeof formFactor, "string", "Each FormFactor should be a string");
+ }
+ for (brandVersionPair of highEntropyData['fullVersionList']) {
+ assert_equals(typeof brandVersionPair.brand, "string", "brand should be a string");
+ assert_regexp_match(brandVersionPair.brand, brandRegex, "brand should not contain unexpected characters");
+ assert_equals(typeof brandVersionPair.version, "string", "version should be a string");
+ }
+ const highEntropyData2 = await uaData.getHighEntropyValues([]);
+ assert_equals(typeof highEntropyData["platform"], "string", "Platform brand should be a string");
+ assert_false("platformVersion" in highEntropyData2, "Platform version should be an empty string");
+ assert_false("architecture" in highEntropyData2, "Architecture should be an empty string");
+ assert_false("model" in highEntropyData2, "Model should be an empty string");
+ assert_false("uaFullVersion" in highEntropyData2, "UAFullVersion should be an empty string");
+ assert_false("formFactor" in highEntropyData2, "FormFactor should be an empty string");
+ assert_false("fullVersionList" in highEntropyData2, "fullVersionList should not be present");
+ let finalPromise = uaData.getHighEntropyValues([]).then(() => {
+ assert_true(didMicrotaskRun, "getHighEntropyValues queued on a task");
+ });
+ await Promise.resolve().then(function() {
+ didMicrotaskRun = true;
+ });
+ return finalPromise;
+ }, "navigator.userAgentData returns a UserAgentMetadata object.");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.tentative.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.tentative.html
new file mode 100644
index 0000000000..dd4c531070
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.tentative.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ test(t => {
+ assert_false("getUserAgent" in navigator);
+ }, "navigator.getUserAgent() is not available in non-secure contexts.");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-false-manual.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-false-manual.html
new file mode 100644
index 0000000000..adcea90a36
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-false-manual.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>navigator.cookieEnabled false</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/#dom-navigator-cookieenabled">
+<meta name="flags" content="interact">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<h2>Preconditions</h2>
+<p>Disable cookies in browser settings.</p>
+
+<script>
+ test(() => {
+ assert_false(navigator.cookieEnabled);
+ }, "navigator.cookieEnabled is false when cookies are disabled");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-true.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-true.html
new file mode 100644
index 0000000000..fc7f49d143
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-true.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>navigator.cookieEnabled true</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/#dom-navigator-cookieenabled">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+ test(() => {
+ assert_true(navigator.cookieEnabled);
+ }, "navigator.cookieEnabled is true when cookies are enabled");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorlanguage.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorlanguage.html
new file mode 100644
index 0000000000..d56df8a3d8
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorlanguage.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>NavigatorLanguage: the most preferred language is the one returned by navigator.language</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#navigatorlanguage">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ test(function() {
+ assert_true("language" in navigator);
+ assert_true("languages" in navigator);
+
+ assert_equals(navigator.languages[0], navigator.language,
+ "navigator.languages is the most preferred language first");
+
+ });
+</script>
+
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/per-global.window.js b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/per-global.window.js
new file mode 100644
index 0000000000..016aef8c4e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/per-global.window.js
@@ -0,0 +1,4 @@
+// META: script=/common/object-association.js
+
+testIsPerWindow("navigator");
+testIsPerWindow("clientInformation");
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/plugins-and-mimetypes.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/plugins-and-mimetypes.html
new file mode 100644
index 0000000000..af05015801
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/plugins-and-mimetypes.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Navigator.plugins and navigator.mimeTypes behavior</title>
+<link rel="author" href="mailto:domenic@chromium.org">
+<link rel="help" href="https://github.com/whatwg/html/pull/6738">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+test(() => {
+ assert_true('pdfViewerEnabled' in navigator, "property exists");
+ assert_equals(typeof navigator.pdfViewerEnabled, 'boolean', "property is boolean");
+}, "navigator.pdfViewerEnabled exists");
+
+const pluginNames = [
+ "PDF Viewer",
+ "Chrome PDF Viewer",
+ "Chromium PDF Viewer",
+ "Microsoft Edge PDF Viewer",
+ "WebKit built-in PDF"
+];
+
+const mimeTypes = [
+ "application/pdf",
+ "text/pdf"
+];
+
+if (navigator.pdfViewerEnabled) {
+ test(() => {
+ assert_equals(navigator.mimeTypes.length, mimeTypes.length, "length");
+
+ for (let i = 0; i < mimeTypes.length; ++i) {
+ const mimeType = mimeTypes[i];
+ const mimeTypeObject = navigator.mimeTypes.item(i);
+
+ assert_equals(mimeTypeObject.type, mimeType, `${i}th type`);
+ assert_equals(mimeTypeObject.description, "Portable Document Format", `${i}th description`);
+ assert_equals(mimeTypeObject.suffixes, "pdf", `${i}th suffixes`);
+ assert_equals(mimeTypeObject, navigator.mimeTypes.namedItem(mimeType), `mimeTypes.item(${i}) matches namedItem("${mimeType}")`);
+ assert_equals(mimeTypeObject.enabledPlugin, navigator.plugins[0], `${i}th enabledPlugin matches 0th Plugin`)
+ }
+ }, "navigator.mimeTypes contains the hard-coded list");
+
+ test(() => {
+ assert_equals(navigator.plugins.length, pluginNames.length, "length");
+
+ for (let i = 0; i < pluginNames.length; ++i) {
+ const pluginName = pluginNames[i];
+ const pluginObject = navigator.plugins.item(i);
+
+ assert_equals(pluginObject.name, pluginName, `${i}th name`);
+ assert_equals(pluginObject.description, "Portable Document Format", `${i}th description`);
+ assert_equals(pluginObject.filename, "internal-pdf-viewer", `${i}th filename`);
+ assert_equals(pluginObject, navigator.plugins.namedItem(pluginName), `plugins.item(${i}) matches namedItem("${pluginName}")`);
+
+ for (let j = 0; j < mimeTypes.length; ++j) {
+ const mimeType = mimeTypes[j];
+ assert_equals(pluginObject.item(j).type, navigator.mimeTypes[j].type, `item(${j}) on plugin(${i}) (${pluginObject.name})`);
+ assert_equals(pluginObject.namedItem(mimeType).type, navigator.mimeTypes.item(j).type, `namedItem("${mimeType}") on plugin(${i})`);
+ }
+ }
+ }, "navigator.plugins contains the hard-coded list");
+} else {
+ test(() => {
+ assert_equals(navigator.mimeTypes.length, 0, "length");
+ assert_equals(navigator.mimeTypes.item(0), null, "item");
+
+ for (const mimeType of mimeTypes) {
+ assert_equals(navigator.mimeTypes.namedItem(mimeType), null, `namedItem("${mimeType}")`);
+ }
+ }, "navigator.mimeTypes is empty");
+
+ test(() => {
+ assert_equals(navigator.plugins.length, 0, "length");
+ assert_equals(navigator.plugins.item(0), null, "item");
+
+ for (const pluginName of pluginNames) {
+ assert_equals(navigator.plugins.namedItem(pluginName), null, `namedItem("${pluginName}")`);
+ }
+ }, "navigator.plugins is empty");
+}
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-manual.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-manual.https.html
new file mode 100644
index 0000000000..1561786569
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-manual.https.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
+<meta charset=windows-1254>
+<meta name=timeout content=long>
+<title>registerProtocolHandler() and a handler with %s in the fragment</title>
+<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>
+// Configure expectations for individual test
+window.type = "fragment";
+window.noSW = false;
+</script>
+<script src=resources/handler-tools.js></script>
+<ol>
+ <li><p>First, register the handler: <button onclick='register()'>Register</button>.
+ <li><p>Then, run the test: <button onclick='runTest()'>Run</button>.
+ <li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>.
+</ol>
+<div id=log></div>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw-manual.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw-manual.https.html
new file mode 100644
index 0000000000..be3a6be666
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw-manual.https.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
+<meta charset=windows-1254>
+<meta name=timeout content=long>
+<title>registerProtocolHandler() and a handler with %s in the fragment (does not use a service worker)</title>
+<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>
+// Configure expectations for individual test
+window.type = "fragment";
+window.noSW = true;
+</script>
+<script src=resources/handler-tools.js></script>
+<ol>
+ <li><p>First, register the handler: <button onclick='register()'>Register</button>.
+ <li><p>Then, run the test: <button onclick='runTest()'>Run</button>.
+ <li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>.
+</ol>
+<div id=log></div>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw.https.html
new file mode 100644
index 0000000000..61b9722ec2
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw.https.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
+<meta charset=windows-1254>
+<meta name=timeout content=long>
+<title>registerProtocolHandler() and a handler with %s in the fragment (does not use a service worker)</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=/service-workers/service-worker/resources/test-helpers.sub.js></script>
+<script>
+'use strict';
+promise_setup(async () => {
+ await test_driver.set_rph_registration_mode("autoAccept");
+ await test_driver.bless('handler registration');
+ register();
+});
+// Configure expectations for individual test
+window.type = "fragment";
+window.noSW = true;
+</script>
+<script src=resources/handler-tools.js></script>
+<script>
+runTest();
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment.https.html
new file mode 100644
index 0000000000..1eaefaa45f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment.https.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
+<meta charset=windows-1254>
+<meta name=timeout content=long>
+<title>registerProtocolHandler() and a handler with %s in the fragment</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=/service-workers/service-worker/resources/test-helpers.sub.js></script>
+<script>
+'use strict';
+promise_setup(async () => {
+ await test_driver.set_rph_registration_mode("autoAccept");
+ await test_driver.bless('handler registration');
+ register();
+});
+// Configure expectations for individual test
+window.type = "fragment";
+window.noSW = false;
+</script>
+<script src=resources/handler-tools.js></script>
+<script>
+runTest();
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path-manual.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path-manual.https.html
new file mode 100644
index 0000000000..085c5723ec
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path-manual.https.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
+<meta charset=windows-1254>
+<meta name=timeout content=long>
+<title>registerProtocolHandler() and a handler with %s in the path</title>
+<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>
+// Configure expectations for individual test
+window.type = "path";
+window.noSW = false;
+</script>
+<script src=resources/handler-tools.js></script>
+<ol>
+ <li><p>First, register the handler: <button onclick='register()'>Register</button>.
+ <li><p>Then, run the test: <button onclick='runTest()'>Run</button>.
+ <li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>.
+</ol>
+<div id=log></div>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path.https.html
new file mode 100644
index 0000000000..a86e79e42e
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path.https.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
+<meta charset=windows-1254>
+<meta name=timeout content=long>
+<title>registerProtocolHandler() and a handler with %s in the path</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=/service-workers/service-worker/resources/test-helpers.sub.js></script>
+<script>
+'use strict';
+promise_setup(async () => {
+ await test_driver.set_rph_registration_mode("autoAccept");
+ await test_driver.bless('handler registration');
+ register();
+});
+// Configure expectations for individual test
+window.type = "path";
+window.noSW = false;
+</script>
+<script src=resources/handler-tools.js></script>
+<script>
+runTest();
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-manual.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-manual.https.html
new file mode 100644
index 0000000000..8ce65a5bad
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-manual.https.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
+<meta charset=windows-1254>
+<meta name=timeout content=long>
+<title>registerProtocolHandler() and a handler with %s in the query</title>
+<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>
+// Configure expectations for individual test
+window.type = "query";
+window.noSW = false;
+</script>
+<script src=resources/handler-tools.js></script>
+<ol>
+ <li><p>First, register the handler: <button onclick='register()'>Register</button>.
+ <li><p>Then, run the test: <button onclick='runTest()'>Run</button>.
+ <li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>.
+</ol>
+<div id=log></div>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw-manual.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw-manual.https.html
new file mode 100644
index 0000000000..9b4473fb89
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw-manual.https.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
+<meta charset=windows-1254>
+<meta name=timeout content=long>
+<title>registerProtocolHandler() and a handler with %s in the query (does not use a service worker)</title>
+<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>
+// Configure expectations for individual test
+window.type = "query";
+window.noSW = true;
+</script>
+<script src=resources/handler-tools.js></script>
+<ol>
+ <li><p>First, register the handler: <button onclick='register()'>Register</button>.
+ <li><p>Then, run the test: <button onclick='runTest()'>Run</button>.
+ <li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>.
+</ol>
+<div id=log></div>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw.https.html
new file mode 100644
index 0000000000..97f67e94ec
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw.https.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
+<meta charset=windows-1254>
+<meta name=timeout content=long>
+<title>registerProtocolHandler() and a handler with %s in the query (does not use a service worker)</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=/service-workers/service-worker/resources/test-helpers.sub.js></script>
+<script>
+'use strict';
+promise_setup(async () => {
+ await test_driver.set_rph_registration_mode("autoAccept");
+ await test_driver.bless('handler registration');
+ register();
+});
+// Configure expectations for individual test
+window.type = "query";
+window.noSW = true;
+</script>
+<script src=resources/handler-tools.js></script>
+<script>
+runTest();
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query.https.html
new file mode 100644
index 0000000000..8c7b2e1cfd
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query.https.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
+<meta charset=windows-1254>
+<meta name=timeout content=long>
+<title>registerProtocolHandler() and a handler with %s in the query</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=/service-workers/service-worker/resources/test-helpers.sub.js></script>
+<script>
+'use strict';
+promise_setup(async () => {
+ await test_driver.set_rph_registration_mode("autoAccept");
+ await test_driver.bless('handler registration');
+ register();
+});
+// Configure expectations for individual test
+window.type = "query";
+window.noSW = false;
+</script>
+<script src=resources/handler-tools.js></script>
+<script>
+runTest();
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html
new file mode 100644
index 0000000000..add78d2fd4
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html
@@ -0,0 +1,218 @@
+<!DOCTYPE html>
+<meta charset='utf-8'>
+<title>protocol handlers</title>
+
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+
+<noscript><p>Enable JavaScript and reload.</p></noscript>
+
+<p><strong>Note:</strong> If your browser limits the number of handler
+registration requests on a page, you might need to disable or significantly
+increase that limit for the tests below to run.</p>
+
+<script>
+test(() => {
+ assert_idl_attribute(navigator, 'registerProtocolHandler');
+}, 'the registerProtocolHandler method should exist on the navigator object');
+
+test(() => {
+ assert_idl_attribute(navigator, 'unregisterProtocolHandler');
+}, 'the unregisterProtocolHandler method should exist on the navigator object');
+
+/* URL argument */
+[
+ '%s',
+ 'foo/%s',
+ `%s${location.href}`,
+ location.href.replace(location.protocol,
+ `${location.protocol[0]}%s${location.protocol.substring(1)}`),
+ location.href.replace(location.protocol, `${location.protocol}%s`),
+ location.href + '/%s',
+ location.href + '#%s',
+ location.href + '?foo=%s',
+ location.href + '?foo=%s&bar',
+ location.href + '/%s/bar/baz/',
+ location.href + '/%s/bar/baz/?foo=1337&bar#baz',
+ location.href + '/%s/foo/%s/',
+].forEach(url => {
+ test(() => {
+ navigator.registerProtocolHandler('tel', url, 'foo');
+ }, 'registerProtocolHandler: Valid URL "' + url + '" should work.');
+
+ test(() => {
+ navigator.unregisterProtocolHandler('tel', url);
+ }, 'unregisterProtocolHandler: Valid URL "' + url + '" should work.');
+});
+
+/* Invalid URLs */
+[
+ '',
+ '%S',
+ 'http://%s.com',
+ 'http://%s.example.com',
+ location.href.replace(location.hostname, `%s${location.hostname}`),
+ location.href.replace(location.port, `%s${location.port}`),
+ location.href + '',
+ location.href + '/%',
+ location.href + '/%a',
+ 'http://example.com',
+ 'http://[v8.:::]//url=%s',
+ 'https://test:test/',
+].forEach(url => {
+ test(() => {
+ assert_throws_dom('SYNTAX_ERR', () => { navigator.registerProtocolHandler('mailto', url, 'foo'); });
+ assert_throws_dom('SECURITY_ERR', () => { navigator.registerProtocolHandler('x', url, 'foo'); });
+ }, `registerProtocolHandler: Invalid URL "${url}" should throw (but after scheme)`);
+
+ test(() => {
+ assert_throws_dom('SYNTAX_ERR', () => { navigator.unregisterProtocolHandler('mailto', url); });
+ assert_throws_dom('SECURITY_ERR', () => { navigator.unregisterProtocolHandler('x', url, 'foo'); });
+ }, `unregisterProtocolHandler: Invalid URL "${url}" should throw (but after scheme)`);
+});
+
+[
+ 'http://example.com/%s',
+ 'https://example.com/%s',
+ 'http://foobar.example.com/%s',
+ 'mailto:%s@example.com',
+ 'mailto:%s',
+ `chrome://${location.host}/%s`,
+ `foo://${location.host}/%s`,
+ URL.createObjectURL(new Blob()) + "#%s",
+].forEach(url => {
+ const title = url.startsWith("blob:") ? "blob: URL" : url;
+ test(() => {
+ assert_throws_dom('SECURITY_ERR', () => { navigator.registerProtocolHandler('mailto', url, 'foo'); });
+ }, `registerProtocolHandler: Invalid URL "${title}" should throw SECURITY_ERR.`);
+
+ test(() => {
+ assert_throws_dom('SECURITY_ERR', () => { navigator.unregisterProtocolHandler('mailto', url); });
+ }, `unregisterProtocolHandler: Invalid URL "${title}" should throw SECURITY_ERR.`);
+});
+
+/* Protocol argument */
+
+/* Overriding any of the following protocols must never be allowed. That would
+ * break the browser. */
+[
+ 'about',
+ 'attachment',
+ 'blob',
+ 'chrome',
+ 'cid',
+ 'data',
+ 'file',
+ 'http',
+ 'https',
+ 'javascript',
+ 'livescript',
+ 'mid',
+ 'mocha',
+ 'moz-icon',
+ 'opera',
+ 'operamail',
+ 'res',
+ 'resource',
+ 'shttp',
+ 'tcl',
+ 'vbscript',
+ 'view-source',
+ 'ws',
+ 'wss',
+ 'wyciwyg',
+ /* other invalid schemes */
+ 'unrecognized',
+ 'mаilto', /* a cyrillic "а" */
+ 'mailto:',
+ 'mailto://',
+ 'mailto' + String.fromCharCode(0),
+ 'mailtoo' + String.fromCharCode(8),
+ 'mailto' + String.fromCharCode(10),
+ 'http://',
+ 'ssh:/',
+ 'magnet:+',
+ 'tel:sip',
+ 'foo',
+ 'fweb+oo',
+ /* web+ prefixed schemes must be followed by 1+ ascii alphas */
+ 'web+',
+ 'web+1',
+ 'web+namewithid123',
+ 'web+namewithtrailingspace ',
+ 'web+préfixewithaccent', // é is not ascii alpha
+ 'web+Kelvinsign', // ASCII-lower KELVIN SIGN is not k
+ 'web+latinsmallletterlongſ', // ASCII-lower LATIN SMALL LETTER LONG S is not s
+ 'web+dots.are.forbidden',
+ 'web+dashes-are-forbidden',
+ 'web+underscores_are_forbidden',
+ 'web+spaces are forbidden',
+ 'web+non*alpha*are*forbidden',
+ 'web+digits123areforbidden',
+].forEach(scheme => {
+ test(() => {
+ // https://test:test/ does not parse and does not contain %s, but the scheme check happens first
+ assert_throws_dom('SECURITY_ERR', () => { navigator.registerProtocolHandler(scheme, 'https://test:test/', 'foo'); });
+ }, 'registerProtocolHandler: Attempting to override the "' + scheme + '" protocol should throw SECURITY_ERR.');
+
+ test(() => {
+ assert_throws_dom('SECURITY_ERR', () => { navigator.unregisterProtocolHandler(scheme, 'https://test:test/'); });
+ }, 'unregisterProtocolHandler: Attempting to override the "' + scheme + '" protocol should throw SECURITY_ERR.');
+});
+
+/* The following protocols must be possible to override.
+ * We're just testing that the call goes through here. Whether or not they
+ * actually work as handlers is covered by the interactive tests. */
+
+[
+ /* safelisted schemes listed in
+ * https://html.spec.whatwg.org/multipage/system-state.html#safelisted-scheme */
+ 'bitcoin',
+ 'ftp',
+ 'ftps',
+ 'geo',
+ 'im',
+ 'irc',
+ 'ircs',
+ 'magnet',
+ 'mailto',
+ 'matrix',
+ 'mms',
+ 'news',
+ 'nntp',
+ 'openpgp4fpr',
+ 'sftp',
+ 'sip',
+ 'sms',
+ 'smsto',
+ 'ssh',
+ 'tel',
+ 'urn',
+ 'webcal',
+ 'wtai',
+ 'xmpp',
+ /* other valid schemes */
+ 'BitcoIn',
+ 'Irc',
+ 'MagneT',
+ 'Matrix',
+ 'SmsTo',
+ 'TEL',
+ 'teL',
+ 'WebCAL',
+ 'WTAI',
+ 'web+myprotocol',
+ 'web+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', // all alphas
+ 'web+UpperCasedIsLowercased',
+ 'WEB+seeabove',
+ 'WeB+SeEaBoVe'
+].forEach(scheme => {
+ test(() => {
+ navigator.registerProtocolHandler(scheme, location.href + '/%s', "foo");
+ }, 'registerProtocolHandler: overriding the "' + scheme + '" protocol should work');
+
+ test(() => {
+ navigator.unregisterProtocolHandler(scheme, location.href + '/%s');
+ }, 'unregisterProtocolHandler: overriding the "' + scheme + '" protocol should work');
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.tentative.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.tentative.https.html
new file mode 100644
index 0000000000..0120aaa12f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.tentative.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset='utf-8'>
+<title>protocol handlers</title>
+
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+
+<script>
+// This should be merged into protocol.https.html when/if
+// https://github.com/whatwg/html/pull/5482 is approved.
+[
+ 'cabal',
+ 'dat',
+ 'did',
+ 'dweb',
+ 'ethereum',
+ 'hyper',
+ 'ipfs',
+ 'ipns',
+ 'ssb',
+].forEach(scheme => {
+ test(() => {
+ navigator.registerProtocolHandler(scheme, location.href + '/%s', "foo");
+ }, 'registerProtocolHandler: overriding the "' + scheme + '" protocol should work');
+
+ test(() => {
+ navigator.unregisterProtocolHandler(scheme, location.href + '/%s');
+ }, 'unregisterProtocolHandler: overriding the "' + scheme + '" protocol should work');
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-sw.js b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-sw.js
new file mode 100644
index 0000000000..5fd915d17f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-sw.js
@@ -0,0 +1,3 @@
+onfetch = e => {
+ e.respondWith(fetch("handler.html"));
+}
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-tools.js b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-tools.js
new file mode 100644
index 0000000000..88c62ec373
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-tools.js
@@ -0,0 +1,53 @@
+// These can be used in an environment that has these global variables defined:
+// * type (one of "path", "query", or "fragment")
+// * noSW (a boolean)
+
+if (type === "path" && noSW) {
+ throw new Error("There is no support for a path handler without a service worker.");
+}
+
+const swString = noSW ? "" : "sw";
+const handler = {
+ "path": "PSS%sPSE/?QES\u2020QEE#FES\u2020FEE",
+ "query": "?QES\u2020QEEPSS%sPSE#FES\u2020FEE",
+ "fragment": "?QES\u2020QEE#FES\u2020FEEPSS%sPSE"
+}[type];
+const scheme = `web+wpt${type}${swString}`;
+
+function register() {
+ const handlerURL = noSW ? `resources/handler.html${handler}${type}` : `resources/handler/${type}/${handler}`;
+ navigator.registerProtocolHandler(scheme, handlerURL, `WPT ${type} handler${noSW ? ", without service worker" : ""}`);
+}
+
+function runTest({ includeNull = false } = {}) {
+ promise_test(async t => {
+ const bc = new BroadcastChannel(`protocol-handler-${type}${swString}`);
+ if (!noSW) {
+ const reg = await service_worker_unregister_and_register(t, "resources/handler-sw.js", "resources/handler/");
+ t.add_cleanup(async () => await reg.unregister());
+ await wait_for_state(t, reg.installing, 'activated');
+ }
+ const a = document.body.appendChild(document.createElement("a"));
+ const codePoints = [];
+ let i = includeNull ? 0 : 1;
+ for (; i < 0x82; i++) {
+ codePoints.push(String.fromCharCode(i));
+ }
+ a.href = `${scheme}:${codePoints.join("")}`;
+ a.target = "_blank";
+ a.click();
+ await new Promise(resolve => {
+ bc.onmessage = t.step_func(e => {
+ resultingURL = e.data;
+ assert_equals(stringBetweenMarkers(resultingURL, "QES", "QEE"), "%86", "query baseline");
+ assert_equals(stringBetweenMarkers(resultingURL, "FES", "FEE"), "%E2%80%A0", "fragment baseline");
+ assert_equals(stringBetweenMarkers(resultingURL, "PSS", "PSE"), `${encodeURIComponent(scheme)}%3A${includeNull ? "%2500" : ""}%2501%2502%2503%2504%2505%2506%2507%2508%250B%250C%250E%250F%2510%2511%2512%2513%2514%2515%2516%2517%2518%2519%251A%251B%251C%251D%251E%251F%20!%22%23%24%25%26${type === "query" ? "%27" : "'"}()*%2B%2C-.%2F0123456789%3A%3B%253C%3D%253E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%2560abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%257F%25C2%2580%25C2%2581`, "actual test");
+ resolve();
+ });
+ });
+ });
+}
+
+function stringBetweenMarkers(string, start, end) {
+ return string.substring(string.indexOf(start) + start.length, string.indexOf(end));
+}
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler.html
new file mode 100644
index 0000000000..552e541784
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<p>This popup can be closed if it does not close itself.
+<p>
+<script>
+// This resource either gets navigated to through a service worker as a result of a URL that looks
+// like:
+// https://.../html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler/{type}/...
+// (the host is excluded to not upset the lint tool)
+// or it gets navigated to directly with the type appended to the end of the URL. In that case type
+// can only be fragment or query.
+
+let type = null;
+let swString = null;
+if (new URL(document.URL).pathname.endsWith("handler.html")) {
+ swString = "";
+ type = (document.URL.endsWith("fragment")) ? "fragment" : "query";
+} else {
+ type = document.URL.split("/")[9];
+ swString = "sw";
+}
+new BroadcastChannel(`protocol-handler-${type}${swString}`).postMessage(document.URL);
+window.close();
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/secure_context.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/secure_context.html
new file mode 100644
index 0000000000..685f5d19d7
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/secure_context.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ test(t => {
+ assert_false('registerProtocolHandler' in navigator);
+ assert_equals(navigator.registerProtocolHandler, undefined);
+ }, "navigator.registerProtocolHandler does not exist in non-secure contexts.");
+
+ test(t => {
+ assert_false('unregisterProtocolHandler' in navigator);
+ assert_equals(navigator.unregisterProtocolHandler, undefined);
+ }, "navigator.unregisterProtocolHandler does not exist in non-secure contexts.");
+ </script>
+</head>
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/README.md b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/README.md
new file mode 100644
index 0000000000..10ae3e5f03
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/README.md
@@ -0,0 +1 @@
+`self.crossOriginIsolated` is tested in `html/cross-origin-opener-policy/coep.https.html`, `html/cross-origin-opener-policy/no-https.html`, `html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-coop-coep.https.any.js`, and `html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-worker-success.js`.
diff --git a/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/Worker_Self_Origin.html b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/Worker_Self_Origin.html
new file mode 100644
index 0000000000..22b28b3e35
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/Worker_Self_Origin.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test workers self.origin</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function assertOriginWorker(workerSource, expectedOrigin, testName) {
+ async_test(function(t) {
+ w = new Worker(workerSource);
+ w.onmessage = t.step_func(function(e) {
+ assert_equals(e.data, expectedOrigin);
+ t.done();
+ });
+ }, testName + ' Worker');
+}
+
+function assertOriginSharedWorker(workerSource, expectedOrigin, testName) {
+ async_test(function(t) {
+ w = new SharedWorker(workerSource);
+ w.port.start();
+ w.port.onmessage = t.step_func(function(e) {
+ assert_equals(e.data, expectedOrigin);
+ t.done();
+ });
+ }, testName + ' SharedWorker');
+}
+
+// Test same-origin workers
+assertOriginWorker("./support/WorkerSelfOriginWorker.js", self.origin, "Same Origin");
+assertOriginSharedWorker("./support/WorkerSelfOriginSharedWorker.js", self.origin, "Same Origin");
+
+// Test data url workers have opaque origin
+assertOriginWorker("data:application/javascript,postMessage(self.origin);", "null", "Data Url");
+assertOriginSharedWorker("data:application/javascript,onconnect = function(e) { e.ports[0].postMessage(self.origin); }", "null", "Data Url");
+
+// Test blob url workers
+blob = new Blob(["postMessage(self.origin);"]);
+blobUrl = URL.createObjectURL(blob);
+assertOriginWorker(blobUrl, self.origin, "Blob Url");
+
+blob = new Blob(["onconnect = function(e) { e.ports[0].postMessage(self.origin); }"]);
+blobUrl = URL.createObjectURL(blob);
+assertOriginSharedWorker(blobUrl, self.origin, "Blob Url");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginSharedWorker.js b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginSharedWorker.js
new file mode 100644
index 0000000000..3acc57102a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginSharedWorker.js
@@ -0,0 +1,5 @@
+// Post back the location of the worker
+
+onconnect = function(e) {
+ e.ports[0].postMessage(self.origin);
+}
diff --git a/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginWorker.js b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginWorker.js
new file mode 100644
index 0000000000..1a69b55d3f
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginWorker.js
@@ -0,0 +1,4 @@
+// Post back the location of the worker
+
+postMessage(self.origin);
+
diff --git a/testing/web-platform/tests/html/webappapis/timers/clearinterval-from-callback.any.js b/testing/web-platform/tests/html/webappapis/timers/clearinterval-from-callback.any.js
new file mode 100644
index 0000000000..bf4eb7cf5a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/timers/clearinterval-from-callback.any.js
@@ -0,0 +1,19 @@
+async_test((t) => {
+ let wasPreviouslyCalled = false;
+
+ const handle = setInterval(
+ t.step_func(() => {
+ if (!wasPreviouslyCalled) {
+ wasPreviouslyCalled = true;
+
+ clearInterval(handle);
+
+ // Make the test succeed after the callback would've run next.
+ setInterval(t.step_func_done(), 750);
+ } else {
+ assert_unreached();
+ }
+ }),
+ 500
+ );
+}, "Clearing an interval from the callback should still clear it.");
diff --git a/testing/web-platform/tests/html/webappapis/timers/cleartimeout-clearinterval.any.js b/testing/web-platform/tests/html/webappapis/timers/cleartimeout-clearinterval.any.js
new file mode 100644
index 0000000000..44551aa8a1
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/timers/cleartimeout-clearinterval.any.js
@@ -0,0 +1,29 @@
+async_test((t) => {
+ const handle = setTimeout(
+ t.step_func(() => {
+ assert_unreached("Timeout was not canceled");
+ }),
+ 0
+ );
+
+ clearInterval(handle);
+
+ setTimeout(() => {
+ t.done();
+ }, 100);
+}, "Clear timeout with clearInterval");
+
+async_test((t) => {
+ const handle = setInterval(
+ t.step_func(() => {
+ assert_unreached("Interval was not canceled");
+ }),
+ 0
+ );
+
+ clearTimeout(handle);
+
+ setTimeout(() => {
+ t.done();
+ }, 100);
+}, "Clear interval with clearTimeout");
diff --git a/testing/web-platform/tests/html/webappapis/timers/evil-spec-example.any.js b/testing/web-platform/tests/html/webappapis/timers/evil-spec-example.any.js
new file mode 100644
index 0000000000..17215e218a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/timers/evil-spec-example.any.js
@@ -0,0 +1,12 @@
+var t = async_test("Interaction of setTimeout and WebIDL")
+function finishTest() {
+ assert_equals(log, "ONE TWO ")
+ t.done()
+}
+var log = '';
+function logger(s) { log += s + ' '; }
+
+setTimeout({ toString: function () {
+ setTimeout("logger('ONE')", 100);
+ return "logger('TWO'); t.step(finishTest)";
+} }, 100);
diff --git a/testing/web-platform/tests/html/webappapis/timers/missing-timeout-setinterval.any.js b/testing/web-platform/tests/html/webappapis/timers/missing-timeout-setinterval.any.js
new file mode 100644
index 0000000000..33a1cc073c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/timers/missing-timeout-setinterval.any.js
@@ -0,0 +1,34 @@
+function timeout_trampoline(t, timeout, message) {
+ t.step_timeout(function() {
+ // Yield in case we managed to be called before the second interval callback.
+ t.step_timeout(function() {
+ assert_unreached(message);
+ }, timeout);
+ }, timeout);
+}
+
+async_test(function(t) {
+ let ctr = 0;
+ let h = setInterval(t.step_func(function() {
+ if (++ctr == 2) {
+ clearInterval(h);
+ t.done();
+ return;
+ }
+ }) /* no interval */);
+
+ timeout_trampoline(t, 100, "Expected setInterval callback to be called two times");
+}, "Calling setInterval with no interval should be the same as if called with 0 interval");
+
+async_test(function(t) {
+ let ctr = 0;
+ let h = setInterval(t.step_func(function() {
+ if (++ctr == 2) {
+ clearInterval(h);
+ t.done();
+ return;
+ }
+ }), undefined);
+
+ timeout_trampoline(t, 100, "Expected setInterval callback to be called two times");
+}, "Calling setInterval with undefined interval should be the same as if called with 0 interval");
diff --git a/testing/web-platform/tests/html/webappapis/timers/negative-setinterval.any.js b/testing/web-platform/tests/html/webappapis/timers/negative-setinterval.any.js
new file mode 100644
index 0000000000..5646140c2a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/timers/negative-setinterval.any.js
@@ -0,0 +1,12 @@
+setup({ single_test: true });
+var i = 0;
+var interval;
+function next() {
+ i++;
+ if (i === 20) {
+ clearInterval(interval);
+ done();
+ }
+}
+setTimeout(assert_unreached, 1000);
+interval = setInterval(next, -100);
diff --git a/testing/web-platform/tests/html/webappapis/timers/negative-settimeout.any.js b/testing/web-platform/tests/html/webappapis/timers/negative-settimeout.any.js
new file mode 100644
index 0000000000..da191f1bf0
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/timers/negative-settimeout.any.js
@@ -0,0 +1,3 @@
+setup({ single_test: true });
+setTimeout(done, -100);
+setTimeout(assert_unreached, 10);
diff --git a/testing/web-platform/tests/html/webappapis/timers/setinterval-cross-realm-callback-report-exception.html b/testing/web-platform/tests/html/webappapis/timers/setinterval-cross-realm-callback-report-exception.html
new file mode 100644
index 0000000000..4a780fc932
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/timers/setinterval-cross-realm-callback-report-exception.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>window.setInterval() 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></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 id = frames[0].setInterval(new frames[1].Function(`
+ parent.clearThisInterval();
+ throw new parent.frames[2].Error("PASS");
+ `), 4);
+ window.clearThisInterval = () => { frames[0].clearInterval(id); };
+
+ t.step_timeout(() => {
+ assert_array_equals(onerrorCalls, ["frame1"]);
+ t.done();
+ }, 8);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/timers/settimeout-cross-realm-callback-report-exception.html b/testing/web-platform/tests/html/webappapis/timers/settimeout-cross-realm-callback-report-exception.html
new file mode 100644
index 0000000000..b4860151a6
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/timers/settimeout-cross-realm-callback-report-exception.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>window.setTimeout() 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></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(() => {
+ frames[0].setTimeout(new frames[1].Function(`throw new parent.frames[2].Error("PASS");`), 4);
+
+ t.step_timeout(() => {
+ assert_array_equals(onerrorCalls, ["frame1"]);
+ t.done();
+ }, 8);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/timers/type-long-setinterval.any.js b/testing/web-platform/tests/html/webappapis/timers/type-long-setinterval.any.js
new file mode 100644
index 0000000000..164527f18b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/timers/type-long-setinterval.any.js
@@ -0,0 +1,8 @@
+setup({ single_test: true });
+var interval;
+function next() {
+ clearInterval(interval);
+ done();
+}
+interval = setInterval(next, Math.pow(2, 32));
+setTimeout(assert_unreached, 100);
diff --git a/testing/web-platform/tests/html/webappapis/timers/type-long-settimeout.any.js b/testing/web-platform/tests/html/webappapis/timers/type-long-settimeout.any.js
new file mode 100644
index 0000000000..9092f13f3b
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/timers/type-long-settimeout.any.js
@@ -0,0 +1,3 @@
+setup({ single_test: true });
+setTimeout(done, Math.pow(2, 32));
+setTimeout(assert_unreached, 100);
diff --git a/testing/web-platform/tests/html/webappapis/update-rendering/child-document-raf-order.html b/testing/web-platform/tests/html/webappapis/update-rendering/child-document-raf-order.html
new file mode 100644
index 0000000000..222c1af444
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/update-rendering/child-document-raf-order.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<meta charset=UTF-8>
+<title>Ordering of steps in "Update the Rendering" - child document requestAnimationFrame order</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id=log></div>
+
+<!--
+
+This test tests the interaction of just two substeps of the "Update the
+rendering" steps in
+https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering
+
+These are:
+
+ 1. Let docs be the list of Document objects associated with the event
+ loop in question, sorted arbitrarily except that the following
+ conditions must be met:
+
+ - Any Document B that is nested through a Document A must be listed
+ after A in the list.
+
+ - If there are two documents A and B whose browsing contexts are
+ both nested browsing contexts and their browsing context
+ containers are both elements in the same Document C, then the
+ order of A and B in the list must match the relative tree order of
+ their respective browsing context containers in C.
+
+ In the steps below that iterate over docs, each Document must be
+ processed in the order it is found in the list.
+
+and later:
+
+10. For each fully active Document in docs, run the animation frame
+ callbacks for that Document, passing in now as the timestamp.
+
+
+It tests this by setting up a tree of three documents, two children and
+one parent, and testing for the relative order of the animation frame
+callbacks for each.
+
+-->
+
+<script>
+
+async_test(function (t) {
+ step_timeout(setup, 0);
+
+ let first_frame, second_frame;
+
+ let notification_sequence = [];
+
+ function setup() {
+ // Start by creating two iframes. To test (a little bit) the rule
+ // about iteration being in document order, insert them in the reverse
+ // order of creation.
+ let body = document.body;
+ function make_iframe() {
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("srcdoc", "<body onload='parent.child_ready()'>");
+ iframe.setAttribute("width", "30");
+ iframe.setAttribute("height", "15");
+ return iframe;
+ }
+ second_frame = make_iframe();
+ body.prepend(second_frame);
+ first_frame = make_iframe();
+ body.prepend(first_frame);
+
+ let children_waiting = 2;
+ window.child_ready = function() {
+ if (--children_waiting == 0) {
+ // Call requestAnimationFrame in neither the order nor the reverse
+ // of the order in which we expect to be called (which is parent,
+ // first, second).
+ first_frame.contentWindow.requestAnimationFrame(first_child_raf);
+ second_frame.contentWindow.requestAnimationFrame(second_child_raf);
+ window.requestAnimationFrame(parent_raf);
+ }
+ };
+ }
+
+ let parent_raf = t.step_func(function() {
+ notification_sequence.push("parent_raf");
+
+ // Request another notification to help ensure we're getting expected behavior.
+ window.requestAnimationFrame(parent_raf);
+ });
+
+ let first_child_raf = t.step_func(function() {
+ notification_sequence.push("first_child_raf");
+
+ // Request another notification to help ensure we're getting expected behavior.
+ first_frame.contentWindow.requestAnimationFrame(first_child_raf);
+ });
+
+ let second_child_raf = t.step_func(function() {
+ notification_sequence.push("second_child_raf");
+
+ // Request another notification to help ensure we're getting expected behavior.
+ second_frame.contentWindow.requestAnimationFrame(second_child_raf);
+
+ step_timeout(finish, 0);
+ });
+
+ let finish = t.step_func(function() {
+ assert_array_equals(notification_sequence,
+ ["parent_raf", "first_child_raf", "second_child_raf"],
+ "expected order of notifications");
+ t.done();
+ });
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html
new file mode 100644
index 0000000000..693cde2192
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html
@@ -0,0 +1,24 @@
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+ setup({ single_test: true });
+ function handleEvent(e) {
+ assert_equals(e.data, 'pass');
+ done();
+ }
+ function on_iframe_load() {
+ var frameWin = document.getElementById('confirmFrame').contentWindow;
+ frameWin.postMessage('Confirm', '*');
+ }
+ window.addEventListener('message', handleEvent);
+</script>
+
+<body>
+ <iframe id='confirmFrame'
+ src='http://{{hosts[alt][www]}}:{{ports[http][0]}}/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/confirm.html'
+ onload='on_iframe_load()'></iframe>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html
new file mode 100644
index 0000000000..151def9b5a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html
@@ -0,0 +1,24 @@
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+ setup({ single_test: true });
+ function handleEvent(e) {
+ assert_equals(e.data, 'pass');
+ done();
+ }
+ function on_iframe_load() {
+ var frameWin = document.getElementById('promptFrame').contentWindow;
+ frameWin.postMessage('Prompt', '*');
+ }
+ window.addEventListener('message', handleEvent);
+</script>
+
+<body>
+ <iframe id='promptFrame'
+ src='http://{{hosts[alt][www]}}:{{ports[http][0]}}/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/prompt.html'
+ onload='on_iframe_load()'></iframe>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/confirm.html b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/confirm.html
new file mode 100644
index 0000000000..80b2c61b39
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/confirm.html
@@ -0,0 +1,11 @@
+<script>
+ function handleEvent(e) {
+ var conf = window.confirm('Confirm Dialog');
+ if (conf == false) {
+ window.parent.postMessage('pass', '*');
+ } else {
+ window.parent.postMessage('fail', '*');
+ }
+ }
+ window.addEventListener('message', handleEvent);
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/prompt.html b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/prompt.html
new file mode 100644
index 0000000000..db40774770
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/prompt.html
@@ -0,0 +1,11 @@
+<script>
+ function handleEvent(e) {
+ var conf = window.prompt('Prompt Dialog');
+ if (conf == null) {
+ window.parent.postMessage('pass', '*');
+ } else {
+ window.parent.postMessage('fail', '*');
+ }
+ }
+ window.addEventListener('message', handleEvent);
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/newline-normalization-manual.html b/testing/web-platform/tests/html/webappapis/user-prompts/newline-normalization-manual.html
new file mode 100644
index 0000000000..55cb5ce527
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/user-prompts/newline-normalization-manual.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Newline normalization in simple dialogs</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#simple-dialogs">
+
+<p>The dialogs should all contain text looking like:</p>
+
+<pre>Line 1.1
+Line 1.2
+Line 1.3
+Line 1.4
+
+Line 2.1</pre>
+
+<script>
+"use strict";
+
+for (const func of [alert, confirm, prompt]) {
+ func('Line 1.1\nLine 1.2\rLine 1.3\r\nLine 1.4\n\rLine 2.1');
+}
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/print-during-beforeunload.html b/testing/web-platform/tests/html/webappapis/user-prompts/print-during-beforeunload.html
new file mode 100644
index 0000000000..5925bdde4d
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/user-prompts/print-during-beforeunload.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>print() during beforeunload</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+async_test(t => {
+ const w = window.open("resources/print-during-event.sub.html?event=beforeunload");
+ t.add_cleanup(() => w.close());
+
+ const messages = [];
+ window.addEventListener("message", t.step_func(({ data }) => {
+ messages.push(data);
+
+ if (messages.length === 1) {
+ assert_array_equals(messages, ["start"]);
+ w.location.href = "resources/destination.html";
+ } else if (messages.length === 2) {
+ // The test passes if we've reached this point because the print() dialog did not block the navigation.
+ assert_array_equals(messages, ["start", "destination"]);
+ t.done();
+ }
+ }));
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/print-during-unload.html b/testing/web-platform/tests/html/webappapis/user-prompts/print-during-unload.html
new file mode 100644
index 0000000000..81259a9fe0
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/user-prompts/print-during-unload.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>print() during unload</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+async_test(t => {
+ const w = window.open("resources/print-during-event.sub.html?event=unload");
+ t.add_cleanup(() => w.close());
+
+ const messages = [];
+ window.addEventListener("message", t.step_func(({ data }) => {
+ messages.push(data);
+
+ if (messages.length === 1) {
+ assert_array_equals(messages, ["start"]);
+ w.location.href = "resources/destination.html";
+ } else if (messages.length === 2) {
+ // The test passes if we've reached this point because the print() dialog did not block the navigation.
+ assert_array_equals(messages, ["start", "destination"]);
+ t.done();
+ }
+ }));
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/print-in-detached-frame.html b/testing/web-platform/tests/html/webappapis/user-prompts/print-in-detached-frame.html
new file mode 100644
index 0000000000..474f4f4d2a
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/user-prompts/print-in-detached-frame.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>print() in a detached iframe</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+test(() => {
+ const iframe = document.createElement("iframe");
+ document.body.append(iframe);
+ const print = iframe.contentWindow.print;
+ iframe.remove();
+
+ print();
+});
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/print-manual.html b/testing/web-platform/tests/html/webappapis/user-prompts/print-manual.html
new file mode 100644
index 0000000000..67cbd0dd48
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/user-prompts/print-manual.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Printing</title>
+<link rel=help href="https://html.spec.whatwg.org/#printing">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({explicit_timeout: true})
+</script>
+
+<p>Click on the button below.</p>
+<p>If the browser offers to print the current page, dismiss the prompt to
+return to this page. The following should then appear in the box marked as
+"Output":</p>
+<pre>
+before calling print()
+beforeprint
+afterprint
+after calling print()
+</pre>
+<p>If no user dialog appears, either the above or the following should be
+printed in the box marked as "Output":</p>
+<pre>
+before calling print()
+after calling print()
+</pre>
+<p>The test passes if the above criteria are satisfied and "PASS" appears in a
+table below this paragraph. The test fails otherwise.</p>
+
+<button onclick="testBtn(this)">Click here!</button>
+<h3>Output</h3>
+<pre id=output></pre>
+
+<script>
+"use strict";
+// This test is actually synchronous, but we use async_test()'s nice
+// infrastructure around t.step() to capture errors in event listeners. This is
+// necessary since it seems like in Chrome, exceptions in beforeprint/afterprint
+// event listeners are not propagated to error events. See
+// https://crbug.com/977828.
+const t = async_test();
+const log = [];
+function out(str) {
+ log.push(str);
+ output.appendChild(document.createTextNode(str + "\n"));
+}
+function testBtn(btn) {
+ btn.remove();
+ window.addEventListener("beforeprint", function (ev) {
+ // t.step_func() does not preserve `this`, which we test below.
+ t.step(() => {
+ out("beforeprint");
+ assert_equals(Object.getPrototypeOf(ev), Event.prototype);
+ assert_equals(this, window);
+ assert_equals(ev.target, window);
+ assert_equals(ev.type, "beforeprint");
+ });
+ });
+ window.addEventListener("afterprint", function (ev) {
+ // t.step_func() does not preserve `this`, which we test below.
+ t.step(() => {
+ out("afterprint");
+ assert_equals(Object.getPrototypeOf(ev), Event.prototype);
+ assert_equals(this, window);
+ assert_equals(ev.target, window);
+ assert_equals(ev.type, "afterprint");
+ });
+ });
+ out("before calling print()");
+ print();
+ out("after calling print()");
+ t.step(() => {
+ try {
+ assert_array_equals(log, [
+ "before calling print()",
+ "beforeprint",
+ "afterprint",
+ "after calling print()",
+ ]);
+ } catch (err) {
+ try {
+ assert_array_equals(log, [
+ "before calling print()",
+ "after calling print()",
+ ]);
+ } catch (err) {
+ assert_unreached("Output does not match either possibility");
+ }
+ }
+ });
+ t.done();
+}
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/resources/destination.html b/testing/web-platform/tests/html/webappapis/user-prompts/resources/destination.html
new file mode 100644
index 0000000000..87fb35cea3
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/user-prompts/resources/destination.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<script>
+"use strict";
+
+opener.postMessage("destination", "*");
+</script>
diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/resources/print-during-event.sub.html b/testing/web-platform/tests/html/webappapis/user-prompts/resources/print-during-event.sub.html
new file mode 100644
index 0000000000..6f6a78b03c
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/user-prompts/resources/print-during-event.sub.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Page that tries to print during an event</title>
+
+<script>
+"use strict";
+
+window.on{{GET[event]}} = () => {
+ try {
+ window.print();
+ } catch (e) {
+ window.opener.postMessage(`error: ${e.message}`);
+ }
+};
+
+window.opener.postMessage("start", "*");
+</script>