summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/dom
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /testing/web-platform/tests/dom
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/dom')
-rw-r--r--testing/web-platform/tests/dom/META.yml5
-rw-r--r--testing/web-platform/tests/dom/abort/AbortSignal.any.js40
-rw-r--r--testing/web-platform/tests/dom/abort/abort-signal-any.any.js4
-rw-r--r--testing/web-platform/tests/dom/abort/abort-signal-timeout.html19
-rw-r--r--testing/web-platform/tests/dom/abort/crashtests/timeout-close.html22
-rw-r--r--testing/web-platform/tests/dom/abort/event.any.js190
-rw-r--r--testing/web-platform/tests/dom/abort/reason-constructor.html12
-rw-r--r--testing/web-platform/tests/dom/abort/resources/abort-signal-any-tests.js185
-rw-r--r--testing/web-platform/tests/dom/attributes-are-nodes.html55
-rw-r--r--testing/web-platform/tests/dom/collections/HTMLCollection-as-prototype.html29
-rw-r--r--testing/web-platform/tests/dom/collections/HTMLCollection-delete.html45
-rw-r--r--testing/web-platform/tests/dom/collections/HTMLCollection-empty-name.html65
-rw-r--r--testing/web-platform/tests/dom/collections/HTMLCollection-iterator.html45
-rw-r--r--testing/web-platform/tests/dom/collections/HTMLCollection-live-mutations.window.js93
-rw-r--r--testing/web-platform/tests/dom/collections/HTMLCollection-own-props.html109
-rw-r--r--testing/web-platform/tests/dom/collections/HTMLCollection-supported-property-indices.html179
-rw-r--r--testing/web-platform/tests/dom/collections/HTMLCollection-supported-property-names.html135
-rw-r--r--testing/web-platform/tests/dom/collections/domstringmap-supported-property-names.html54
-rw-r--r--testing/web-platform/tests/dom/collections/namednodemap-supported-property-names.html30
-rw-r--r--testing/web-platform/tests/dom/common.js1083
-rw-r--r--testing/web-platform/tests/dom/constants.js11
-rw-r--r--testing/web-platform/tests/dom/eventPathRemoved.html16
-rw-r--r--testing/web-platform/tests/dom/events/AddEventListenerOptions-once.any.js96
-rw-r--r--testing/web-platform/tests/dom/events/AddEventListenerOptions-passive.any.js134
-rw-r--r--testing/web-platform/tests/dom/events/AddEventListenerOptions-signal.any.js143
-rw-r--r--testing/web-platform/tests/dom/events/Body-FrameSet-Event-Handlers.html123
-rw-r--r--testing/web-platform/tests/dom/events/CustomEvent.html35
-rw-r--r--testing/web-platform/tests/dom/events/Event-cancelBubble.html132
-rw-r--r--testing/web-platform/tests/dom/events/Event-constants.html23
-rw-r--r--testing/web-platform/tests/dom/events/Event-constructors.any.js120
-rw-r--r--testing/web-platform/tests/dom/events/Event-defaultPrevented-after-dispatch.html44
-rw-r--r--testing/web-platform/tests/dom/events/Event-defaultPrevented.html55
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-bubble-canceled.html59
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-bubbles-false.html98
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-bubbles-true.html108
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-click.html425
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-click.tentative.html78
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-detached-click.html20
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-detached-input-and-change.html190
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-handlers-changed.html91
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-listener-order.window.js20
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-multiple-cancelBubble.html51
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-multiple-stopPropagation.html51
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-omitted-capture.html70
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-on-disabled-elements.html251
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-order-at-target.html31
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-order.html26
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-other-document.html23
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-propagation-stopped.html59
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-redispatch.html124
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-reenter.html66
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-target-moved.html73
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-target-removed.html72
-rw-r--r--testing/web-platform/tests/dom/events/Event-dispatch-throwing.html51
-rw-r--r--testing/web-platform/tests/dom/events/Event-init-while-dispatching.html83
-rw-r--r--testing/web-platform/tests/dom/events/Event-initEvent.html136
-rw-r--r--testing/web-platform/tests/dom/events/Event-isTrusted.any.js11
-rw-r--r--testing/web-platform/tests/dom/events/Event-propagation.html48
-rw-r--r--testing/web-platform/tests/dom/events/Event-returnValue.html64
-rw-r--r--testing/web-platform/tests/dom/events/Event-stopImmediatePropagation.html34
-rw-r--r--testing/web-platform/tests/dom/events/Event-stopPropagation-cancel-bubbling.html20
-rw-r--r--testing/web-platform/tests/dom/events/Event-subclasses-constructors.html179
-rw-r--r--testing/web-platform/tests/dom/events/Event-timestamp-cross-realm-getter.html27
-rw-r--r--testing/web-platform/tests/dom/events/Event-timestamp-high-resolution.html16
-rw-r--r--testing/web-platform/tests/dom/events/Event-timestamp-high-resolution.https.html16
-rw-r--r--testing/web-platform/tests/dom/events/Event-timestamp-safe-resolution.html49
-rw-r--r--testing/web-platform/tests/dom/events/Event-type-empty.html35
-rw-r--r--testing/web-platform/tests/dom/events/Event-type.html22
-rw-r--r--testing/web-platform/tests/dom/events/EventListener-addEventListener.sub.window.js9
-rw-r--r--testing/web-platform/tests/dom/events/EventListener-handleEvent-cross-realm.html75
-rw-r--r--testing/web-platform/tests/dom/events/EventListener-handleEvent.html102
-rw-r--r--testing/web-platform/tests/dom/events/EventListener-incumbent-global-1.sub.html20
-rw-r--r--testing/web-platform/tests/dom/events/EventListener-incumbent-global-2.sub.html20
-rw-r--r--testing/web-platform/tests/dom/events/EventListener-incumbent-global-subframe-1.sub.html13
-rw-r--r--testing/web-platform/tests/dom/events/EventListener-incumbent-global-subframe-2.sub.html13
-rw-r--r--testing/web-platform/tests/dom/events/EventListener-incumbent-global-subsubframe.sub.html20
-rw-r--r--testing/web-platform/tests/dom/events/EventListener-invoke-legacy.html66
-rw-r--r--testing/web-platform/tests/dom/events/EventListenerOptions-capture.html98
-rw-r--r--testing/web-platform/tests/dom/events/EventTarget-add-listener-platform-object.html32
-rw-r--r--testing/web-platform/tests/dom/events/EventTarget-add-remove-listener.any.js21
-rw-r--r--testing/web-platform/tests/dom/events/EventTarget-addEventListener.any.js9
-rw-r--r--testing/web-platform/tests/dom/events/EventTarget-constructible.any.js62
-rw-r--r--testing/web-platform/tests/dom/events/EventTarget-dispatchEvent-returnvalue.html71
-rw-r--r--testing/web-platform/tests/dom/events/EventTarget-dispatchEvent.html104
-rw-r--r--testing/web-platform/tests/dom/events/EventTarget-removeEventListener.any.js8
-rw-r--r--testing/web-platform/tests/dom/events/EventTarget-this-of-listener.html182
-rw-r--r--testing/web-platform/tests/dom/events/KeyEvent-initKeyEvent.html23
-rw-r--r--testing/web-platform/tests/dom/events/event-disabled-dynamic.html21
-rw-r--r--testing/web-platform/tests/dom/events/event-global-extra.window.js90
-rw-r--r--testing/web-platform/tests/dom/events/event-global-is-still-set-when-coercing-beforeunload-result.html23
-rw-r--r--testing/web-platform/tests/dom/events/event-global-is-still-set-when-reporting-exception-onerror.html43
-rw-r--r--testing/web-platform/tests/dom/events/event-global-set-before-handleEvent-lookup.window.js19
-rw-r--r--testing/web-platform/tests/dom/events/event-global.html117
-rw-r--r--testing/web-platform/tests/dom/events/event-global.worker.js14
-rw-r--r--testing/web-platform/tests/dom/events/focus-event-document-move.html33
-rw-r--r--testing/web-platform/tests/dom/events/keypress-dispatch-crash.html15
-rw-r--r--testing/web-platform/tests/dom/events/legacy-pre-activation-behavior.window.js10
-rw-r--r--testing/web-platform/tests/dom/events/mouse-event-retarget.html26
-rw-r--r--testing/web-platform/tests/dom/events/no-focus-events-at-clicking-editable-content-in-link.html80
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-body.html19
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-div.html35
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-document.html19
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-root.html19
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-window.html19
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-body.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-div.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-document.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-root.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-window.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-body.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-div.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-document.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-root.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-window.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-body.html18
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-div.html34
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-document.html18
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-root.html18
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-window.html18
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-body.html19
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-div.html35
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-document.html19
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-root.html19
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-window.html19
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-body.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-div.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-document.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-root.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-window.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-body.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-div.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-document.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-root.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-window.html25
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-body.html18
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-div.html34
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-document.html18
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-root.html18
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-window.html18
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/resources/scrolling.js34
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/resources/touching.js34
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/resources/wait-for.js15
-rw-r--r--testing/web-platform/tests/dom/events/non-cancelable-when-passive/synthetic-events-cancelable.html34
-rw-r--r--testing/web-platform/tests/dom/events/passive-by-default.html50
-rw-r--r--testing/web-platform/tests/dom/events/preventDefault-during-activation-behavior.html53
-rw-r--r--testing/web-platform/tests/dom/events/relatedTarget.window.js81
-rw-r--r--testing/web-platform/tests/dom/events/replace-event-listener-null-browsing-context-crash.html16
-rw-r--r--testing/web-platform/tests/dom/events/resources/empty-document.html3
-rw-r--r--testing/web-platform/tests/dom/events/resources/event-global-extra-frame.html9
-rw-r--r--testing/web-platform/tests/dom/events/resources/event-global-is-still-set-when-coercing-beforeunload-result-frame.html6
-rw-r--r--testing/web-platform/tests/dom/events/resources/prefixed-animation-event-tests.js366
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/iframe-chains.html48
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/input-text-scroll-event-when-using-arrow-keys.html71
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/overscroll-deltas.html147
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-document.html62
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.html92
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.html65
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-window.html52
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/scroll_support.js278
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-sequence-of-scrolls.tentative.html91
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-snap.html87
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-mandatory-snap-point-after-load.html87
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-programmatic-scroll.html135
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-scrollIntoView.html124
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-document.html106
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-element-with-overscroll-behavior.html173
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-window.html69
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/scrollend-event-for-user-scroll.html187
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/scrollend-event-handler-content-attributes.html108
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-after-removing-scroller.tentative.html84
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-on-no-scroll.html114
-rw-r--r--testing/web-platform/tests/dom/events/shadow-relatedTarget.html30
-rw-r--r--testing/web-platform/tests/dom/events/webkit-animation-end-event.html20
-rw-r--r--testing/web-platform/tests/dom/events/webkit-animation-iteration-event.html23
-rw-r--r--testing/web-platform/tests/dom/events/webkit-animation-start-event.html20
-rw-r--r--testing/web-platform/tests/dom/events/webkit-transition-end-event.html21
-rw-r--r--testing/web-platform/tests/dom/historical.html222
-rw-r--r--testing/web-platform/tests/dom/idlharness-shadowrealm.window.js2
-rw-r--r--testing/web-platform/tests/dom/idlharness.any.js25
-rw-r--r--testing/web-platform/tests/dom/idlharness.window.js52
-rw-r--r--testing/web-platform/tests/dom/interface-objects.html46
-rw-r--r--testing/web-platform/tests/dom/lists/DOMTokenList-Iterable.html34
-rw-r--r--testing/web-platform/tests/dom/lists/DOMTokenList-coverage-for-attributes.html55
-rw-r--r--testing/web-platform/tests/dom/lists/DOMTokenList-iteration.html71
-rw-r--r--testing/web-platform/tests/dom/lists/DOMTokenList-stringifier.html25
-rw-r--r--testing/web-platform/tests/dom/lists/DOMTokenList-value.html24
-rw-r--r--testing/web-platform/tests/dom/lists/README.md1
-rw-r--r--testing/web-platform/tests/dom/nodes/CharacterData-appendChild.html34
-rw-r--r--testing/web-platform/tests/dom/nodes/CharacterData-appendData.html70
-rw-r--r--testing/web-platform/tests/dom/nodes/CharacterData-data.html82
-rw-r--r--testing/web-platform/tests/dom/nodes/CharacterData-deleteData.html95
-rw-r--r--testing/web-platform/tests/dom/nodes/CharacterData-insertData.html90
-rw-r--r--testing/web-platform/tests/dom/nodes/CharacterData-remove.html24
-rw-r--r--testing/web-platform/tests/dom/nodes/CharacterData-replaceData.html163
-rw-r--r--testing/web-platform/tests/dom/nodes/CharacterData-substringData.html137
-rw-r--r--testing/web-platform/tests/dom/nodes/CharacterData-surrogates.html74
-rw-r--r--testing/web-platform/tests/dom/nodes/ChildNode-after.html166
-rw-r--r--testing/web-platform/tests/dom/nodes/ChildNode-before.html166
-rw-r--r--testing/web-platform/tests/dom/nodes/ChildNode-remove.js30
-rw-r--r--testing/web-platform/tests/dom/nodes/ChildNode-replaceWith.html110
-rw-r--r--testing/web-platform/tests/dom/nodes/Comment-Text-constructor.js77
-rw-r--r--testing/web-platform/tests/dom/nodes/Comment-constructor.html11
-rw-r--r--testing/web-platform/tests/dom/nodes/Comment-in-doctype.xhtml15
-rw-r--r--testing/web-platform/tests/dom/nodes/DOMImplementation-createDocument-with-null-browsing-context-crash.html13
-rw-r--r--testing/web-platform/tests/dom/nodes/DOMImplementation-createDocument.html170
-rw-r--r--testing/web-platform/tests/dom/nodes/DOMImplementation-createDocumentType.html123
-rw-r--r--testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument-with-null-browsing-context-crash.html13
-rw-r--r--testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument-with-saved-implementation.html16
-rw-r--r--testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument.html96
-rw-r--r--testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument.js25
-rw-r--r--testing/web-platform/tests/dom/nodes/DOMImplementation-hasFeature.html155
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-Element-getElementsByTagName.js208
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-Element-getElementsByTagNameNS.js143
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-URL.html18
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-adoptNode.html50
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-characterSet-normalization-1.html157
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-characterSet-normalization-2.html179
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-constructor-svg.svg46
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-constructor-xml.xml49
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-constructor.html56
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_bmp.html15
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_css.html15
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_datauri_02.html15
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_gif.html15
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_html.html15
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_javascripturi.html16
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_jpg.html15
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_mimeheader_01.html15
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_mimeheader_02.html15
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_png.html15
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_txt.html15
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_xml.html15
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/createDocument.html11
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/createHTMLDocument.html11
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/contentType/xhr_responseType_document.html18
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/resources/blob.htm1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/resources/blob.txt1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/resources/blob.xml2
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/resources/lib.js1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/resources/style.css12
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.bmpbin0 -> 30054 bytes
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.gifbin0 -> 148 bytes
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.jpgbin0 -> 361 bytes
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.pngbin0 -> 228 bytes
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-contentType/support/contenttype_setter.py20
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createAttribute.html55
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createCDATASection-xhtml.xhtml22
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createCDATASection.html16
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createComment-createTextNode.js22
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createComment.html21
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.html1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.svg1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.xhtml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.xml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.html1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.svg1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.xhtml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.xml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.html1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.svg1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.xhtml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.xml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.html0
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.svg0
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.xhtml0
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.xml0
-rwxr-xr-xtesting/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/generate.py78
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.html1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.svg1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.xhtml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.xml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.html1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.svg1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.xhtml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.xml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.html1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.svg1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.xhtml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.xml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.html1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.svg1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.xhtml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.xml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.html7
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.svg7
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.xhtml7
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.xml7
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.html7
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.svg7
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.xhtml7
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.xml7
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement-namespace.html117
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElement.html158
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElementNS.html224
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createElementNS.js189
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createEvent-touchevent.window.js12
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createEvent.https.html170
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createEvent.js22
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createProcessingInstruction-xhtml.xhtml15
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createProcessingInstruction.html11
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createProcessingInstruction.js39
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createTextNode.html21
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-createTreeWalker.html42
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-doctype.html21
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-getElementById.html350
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-getElementsByClassName.html26
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-getElementsByTagName-xhtml.xhtml104
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-getElementsByTagName.html11
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-getElementsByTagNameNS.html11
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-implementation.html20
-rw-r--r--testing/web-platform/tests/dom/nodes/Document-importNode.html67
-rw-r--r--testing/web-platform/tests/dom/nodes/DocumentFragment-constructor.html22
-rw-r--r--testing/web-platform/tests/dom/nodes/DocumentFragment-getElementById.html62
-rw-r--r--testing/web-platform/tests/dom/nodes/DocumentFragment-querySelectorAll-after-modification.html24
-rw-r--r--testing/web-platform/tests/dom/nodes/DocumentType-literal-xhtml.xhtml23
-rw-r--r--testing/web-platform/tests/dom/nodes/DocumentType-literal.html17
-rw-r--r--testing/web-platform/tests/dom/nodes/DocumentType-remove.html16
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElement-null-svg.svg20
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElement-null-xhtml.xhtml20
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElement-null.html15
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-add-svg.svg22
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-add-xhtml.xhtml22
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-add.html17
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-remove-svg.svg22
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-remove-xhtml.xhtml22
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-remove.html17
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElementCount-nochild-svg.svg19
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElementCount-nochild-xhtml.xhtml19
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElementCount-nochild.html14
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElementCount-svg.svg25
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElementCount-xhtml.xhtml25
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-childElementCount.html20
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-children.html58
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-classlist.html478
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-closest.html73
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-firstElementChild-entity-xhtml.xhtml27
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-firstElementChild-entity.svg26
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-firstElementChild-namespace-svg.svg26
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-firstElementChild-namespace-xhtml.xhtml28
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-firstElementChild-namespace.html21
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-firstElementChild-svg.svg23
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-firstElementChild-xhtml.xhtml23
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-firstElementChild.html18
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-getElementsByClassName.html43
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-getElementsByTagName-change-document-HTMLNess-iframe.xml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-getElementsByTagName-change-document-HTMLNess.html51
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-getElementsByTagName.html30
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-getElementsByTagNameNS.html37
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-hasAttribute.html32
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-hasAttributes.html40
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-insertAdjacentElement.html91
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-insertAdjacentText.html76
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-lastElementChild-svg.svg22
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-lastElementChild-xhtml.xhtml22
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-lastElementChild.html17
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-matches-init.js65
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-matches-namespaced-elements.html24
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-matches.html22
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-matches.js135
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-nextElementSibling-svg.svg23
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-nextElementSibling-xhtml.xhtml23
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-nextElementSibling.html18
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-previousElementSibling-svg.svg28
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-previousElementSibling-xhtml.xhtml28
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-previousElementSibling.html23
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-remove.html16
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-removeAttribute.html58
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-removeAttributeNS.html18
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-setAttribute-crbug-1138487.html22
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-setAttribute.html38
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-siblingElement-null-svg.svg20
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-siblingElement-null-xhtml.xhtml20
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-siblingElement-null.html16
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-tagName.html57
-rw-r--r--testing/web-platform/tests/dom/nodes/Element-webkitMatchesSelector.html22
-rw-r--r--testing/web-platform/tests/dom/nodes/MutationObserver-attributes.html406
-rw-r--r--testing/web-platform/tests/dom/nodes/MutationObserver-callback-arguments.html31
-rw-r--r--testing/web-platform/tests/dom/nodes/MutationObserver-characterData.html215
-rw-r--r--testing/web-platform/tests/dom/nodes/MutationObserver-childList.html434
-rw-r--r--testing/web-platform/tests/dom/nodes/MutationObserver-cross-realm-callback-report-exception.html32
-rw-r--r--testing/web-platform/tests/dom/nodes/MutationObserver-disconnect.html48
-rw-r--r--testing/web-platform/tests/dom/nodes/MutationObserver-document.html167
-rw-r--r--testing/web-platform/tests/dom/nodes/MutationObserver-inner-outer.html65
-rw-r--r--testing/web-platform/tests/dom/nodes/MutationObserver-sanity.html95
-rw-r--r--testing/web-platform/tests/dom/nodes/MutationObserver-takeRecords.html53
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-appendChild-cereactions-vs-script.window.js27
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-appendChild.html59
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-baseURI.html62
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-childNodes.html117
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-cloneNode-XMLDocument.html28
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-cloneNode-document-with-doctype.html51
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-cloneNode-external-stylesheet-no-bc.sub.html23
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-cloneNode-on-inactive-document-crash.html6
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-cloneNode-svg.html63
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-cloneNode.html346
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-compareDocumentPosition.html87
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-constants.html39
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-contains-xml.xml83
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-contains.html36
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-insertBefore.html297
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-isConnected-shadow-dom.html29
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-isConnected.html95
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-isEqualNode-iframe1.xml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-isEqualNode-iframe2.xml1
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-isEqualNode-xhtml.xhtml84
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-isEqualNode.html161
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-isSameNode.html111
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-lookupNamespaceURI.html139
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-lookupPrefix.xhtml31
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-mutation-adoptNode.html23
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-nodeName-xhtml.xhtml42
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-nodeName.html32
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-nodeValue.html71
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-normalize.html83
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-parentElement.html82
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-parentNode-iframe.html1
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-parentNode.html33
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-properties.html688
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-removeChild.html58
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-replaceChild.html349
-rw-r--r--testing/web-platform/tests/dom/nodes/Node-textContent.html265
-rw-r--r--testing/web-platform/tests/dom/nodes/NodeList-Iterable.html61
-rw-r--r--testing/web-platform/tests/dom/nodes/NodeList-live-mutations.window.js79
-rw-r--r--testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-1.html22
-rw-r--r--testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-2.html22
-rw-r--r--testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-3.html22
-rw-r--r--testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-indexOf-1.html22
-rw-r--r--testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-indexOf-2.html22
-rw-r--r--testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-indexOf-3.html22
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-append.html67
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-children.html27
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-prepend.html67
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All-content.html377
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All-content.xht372
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All-xht.xht124
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All.html120
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All.js261
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-querySelector-case-insensitive.html21
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-querySelector-escapes.html123
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-querySelector-scope.html33
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-querySelectorAll-removed-elements.html30
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-querySelectors-exclusive.html39
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-querySelectors-namespaces.html21
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-querySelectors-space-and-dash-attribute-value.html21
-rw-r--r--testing/web-platform/tests/dom/nodes/ParentNode-replaceChildren.html205
-rw-r--r--testing/web-platform/tests/dom/nodes/ProcessingInstruction-escapes-1.xhtml33
-rw-r--r--testing/web-platform/tests/dom/nodes/ProcessingInstruction-in-doctype.xhtml15
-rw-r--r--testing/web-platform/tests/dom/nodes/ProcessingInstruction-literal-1.xhtml16
-rw-r--r--testing/web-platform/tests/dom/nodes/ProcessingInstruction-literal-2.xhtml21
-rw-r--r--testing/web-platform/tests/dom/nodes/Text-constructor.html11
-rw-r--r--testing/web-platform/tests/dom/nodes/Text-splitText.html53
-rw-r--r--testing/web-platform/tests/dom/nodes/Text-wholeText.html46
-rw-r--r--testing/web-platform/tests/dom/nodes/adoption.window.js58
-rw-r--r--testing/web-platform/tests/dom/nodes/append-on-Document.html53
-rw-r--r--testing/web-platform/tests/dom/nodes/attributes-namednodemap.html120
-rw-r--r--testing/web-platform/tests/dom/nodes/attributes.html858
-rw-r--r--testing/web-platform/tests/dom/nodes/attributes.js18
-rw-r--r--testing/web-platform/tests/dom/nodes/case.html18
-rw-r--r--testing/web-platform/tests/dom/nodes/case.js186
-rw-r--r--testing/web-platform/tests/dom/nodes/characterset-helper.js62
-rw-r--r--testing/web-platform/tests/dom/nodes/creators.js5
-rw-r--r--testing/web-platform/tests/dom/nodes/encoding.py7
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-01.htm13
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-02.htm14
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-03.htm18
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-04.htm18
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-05.htm18
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-06.htm20
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-07.htm15
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-08.htm15
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-09.htm15
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-10.xml19
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-11.xml24
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-12.htm15
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-13.htm19
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-14.htm26
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-15.htm18
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-16.htm16
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-17.htm15
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-18.htm17
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-19.htm54
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-20.htm61
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-21.htm52
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-22.htm58
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-23.htm52
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-24.htm30
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-25.htm57
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-26.htm30
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-27.htm32
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-28.htm31
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-29.htm51
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-30.htm190
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-31.htm22
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-32.html68
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-empty-set.html29
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassName-whitespace-class-names.html50
-rw-r--r--testing/web-platform/tests/dom/nodes/getElementsByClassNameFrame.htm6
-rw-r--r--testing/web-platform/tests/dom/nodes/insert-adjacent.html79
-rw-r--r--testing/web-platform/tests/dom/nodes/mutationobservers.js76
-rw-r--r--testing/web-platform/tests/dom/nodes/node-appendchild-crash.html18
-rw-r--r--testing/web-platform/tests/dom/nodes/pre-insertion-validation-hierarchy.js86
-rw-r--r--testing/web-platform/tests/dom/nodes/pre-insertion-validation-notfound.js108
-rw-r--r--testing/web-platform/tests/dom/nodes/prepend-on-Document.html53
-rw-r--r--testing/web-platform/tests/dom/nodes/productions.js3
-rw-r--r--testing/web-platform/tests/dom/nodes/query-target-in-load-event.html13
-rw-r--r--testing/web-platform/tests/dom/nodes/query-target-in-load-event.part.html10
-rw-r--r--testing/web-platform/tests/dom/nodes/remove-and-adopt-thcrash.html18
-rw-r--r--testing/web-platform/tests/dom/nodes/remove-from-shadow-host-and-adopt-into-iframe-ref.html4
-rw-r--r--testing/web-platform/tests/dom/nodes/remove-from-shadow-host-and-adopt-into-iframe.html29
-rw-r--r--testing/web-platform/tests/dom/nodes/remove-unscopable.html32
-rw-r--r--testing/web-platform/tests/dom/nodes/rootNode.html96
-rw-r--r--testing/web-platform/tests/dom/nodes/selectors.js755
-rw-r--r--testing/web-platform/tests/dom/nodes/support/NodeList-static-length-tampered.js46
-rw-r--r--testing/web-platform/tests/dom/nodes/svg-template-querySelector.html29
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-adopt-test.html52
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-attributes.html23
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-cloneContents.html461
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-cloneRange.html112
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-collapse.html67
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-commonAncestorContainer-2.html33
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-commonAncestorContainer.html40
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-compareBoundaryPoints.html182
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-comparePoint-2.html23
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-comparePoint.html92
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-constructor.html20
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-deleteContents.html337
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-detach.html14
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-extractContents.html252
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-insertNode.html286
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-intersectsNode-2.html36
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-intersectsNode-binding.html25
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-intersectsNode-shadow.html19
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-intersectsNode.html70
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-isPointInRange.html83
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-mutations-appendChild.html14
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-mutations-appendData.html14
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-mutations-dataChange.html14
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-mutations-deleteData.html14
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-mutations-insertBefore.html14
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-mutations-insertData.html14
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-mutations-removeChild.html14
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-mutations-replaceChild.html14
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-mutations-replaceData.html14
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-mutations-splitText.html14
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-mutations.js921
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-selectNode.html99
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-set.html221
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-stringifier.html44
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-surroundContents.html324
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-test-iframe.html56
-rw-r--r--testing/web-platform/tests/dom/ranges/StaticRange-constructor.html200
-rw-r--r--testing/web-platform/tests/dom/slot-recalc-ref.html5
-rw-r--r--testing/web-platform/tests/dom/slot-recalc.html23
-rw-r--r--testing/web-platform/tests/dom/svg-insert-crash.html18
-rw-r--r--testing/web-platform/tests/dom/traversal/NodeFilter-constants.html34
-rw-r--r--testing/web-platform/tests/dom/traversal/NodeIterator-removal.html100
-rw-r--r--testing/web-platform/tests/dom/traversal/NodeIterator.html215
-rw-r--r--testing/web-platform/tests/dom/traversal/TreeWalker-acceptNode-filter-cross-realm-null-browsing-context.html30
-rw-r--r--testing/web-platform/tests/dom/traversal/TreeWalker-acceptNode-filter-cross-realm.html60
-rw-r--r--testing/web-platform/tests/dom/traversal/TreeWalker-acceptNode-filter.html195
-rw-r--r--testing/web-platform/tests/dom/traversal/TreeWalker-basic.html154
-rw-r--r--testing/web-platform/tests/dom/traversal/TreeWalker-currentNode.html73
-rw-r--r--testing/web-platform/tests/dom/traversal/TreeWalker-previousNodeLastChildReject.html87
-rw-r--r--testing/web-platform/tests/dom/traversal/TreeWalker-previousSiblingLastChildSkip.html91
-rw-r--r--testing/web-platform/tests/dom/traversal/TreeWalker-traversal-reject.html109
-rw-r--r--testing/web-platform/tests/dom/traversal/TreeWalker-traversal-skip-most.html66
-rw-r--r--testing/web-platform/tests/dom/traversal/TreeWalker-traversal-skip.html111
-rw-r--r--testing/web-platform/tests/dom/traversal/TreeWalker-walking-outside-a-tree.html40
-rw-r--r--testing/web-platform/tests/dom/traversal/TreeWalker.html324
-rw-r--r--testing/web-platform/tests/dom/traversal/support/TreeWalker-acceptNode-filter-cross-realm-null-browsing-context-subframe.html13
-rw-r--r--testing/web-platform/tests/dom/traversal/support/assert-node.js10
-rw-r--r--testing/web-platform/tests/dom/traversal/support/empty-document.html3
-rw-r--r--testing/web-platform/tests/dom/traversal/unfinished/001.xml53
-rw-r--r--testing/web-platform/tests/dom/traversal/unfinished/002.xml54
-rw-r--r--testing/web-platform/tests/dom/traversal/unfinished/003.xml58
-rw-r--r--testing/web-platform/tests/dom/traversal/unfinished/004.xml49
-rw-r--r--testing/web-platform/tests/dom/traversal/unfinished/005.xml57
-rw-r--r--testing/web-platform/tests/dom/traversal/unfinished/006.xml47
-rw-r--r--testing/web-platform/tests/dom/traversal/unfinished/007.xml54
-rw-r--r--testing/web-platform/tests/dom/traversal/unfinished/008.xml48
-rw-r--r--testing/web-platform/tests/dom/traversal/unfinished/009.xml55
-rw-r--r--testing/web-platform/tests/dom/traversal/unfinished/010.xml64
-rw-r--r--testing/web-platform/tests/dom/traversal/unfinished/TODO1
-rw-r--r--testing/web-platform/tests/dom/window-extends-event-target.html55
-rw-r--r--testing/web-platform/tests/dom/xslt/README.md11
-rw-r--r--testing/web-platform/tests/dom/xslt/externalScript.js1
-rw-r--r--testing/web-platform/tests/dom/xslt/functions.tentative.window.js252
-rw-r--r--testing/web-platform/tests/dom/xslt/invalid-output-encoding-crash.html26
-rw-r--r--testing/web-platform/tests/dom/xslt/sort-ref.html10
-rw-r--r--testing/web-platform/tests/dom/xslt/sort.html48
-rw-r--r--testing/web-platform/tests/dom/xslt/strip-space-crash.xml33
-rw-r--r--testing/web-platform/tests/dom/xslt/transformToFragment-on-node-from-inactive-document-crash.html11
-rw-r--r--testing/web-platform/tests/dom/xslt/transformToFragment.tentative.window.js39
593 files changed, 38153 insertions, 0 deletions
diff --git a/testing/web-platform/tests/dom/META.yml b/testing/web-platform/tests/dom/META.yml
new file mode 100644
index 0000000000..6fd5b12664
--- /dev/null
+++ b/testing/web-platform/tests/dom/META.yml
@@ -0,0 +1,5 @@
+spec: https://dom.spec.whatwg.org/
+suggested_reviewers:
+ - jdm
+ - zqzhang
+ - annevk
diff --git a/testing/web-platform/tests/dom/abort/AbortSignal.any.js b/testing/web-platform/tests/dom/abort/AbortSignal.any.js
new file mode 100644
index 0000000000..3bbdc11a92
--- /dev/null
+++ b/testing/web-platform/tests/dom/abort/AbortSignal.any.js
@@ -0,0 +1,40 @@
+test(t => {
+ const signal = AbortSignal.abort();
+ assert_true(signal instanceof AbortSignal, "returned object is an AbortSignal");
+ assert_true(signal.aborted, "returned signal is already aborted");
+}, "the AbortSignal.abort() static returns an already aborted signal");
+
+async_test(t => {
+ const s = AbortSignal.abort();
+ s.addEventListener("abort", t.unreached_func("abort event listener called"));
+ s.onabort = t.unreached_func("abort event handler called");
+ t.step_timeout(() => { t.done(); }, 2000);
+}, "signal returned by AbortSignal.abort() should not fire abort event");
+
+test(t => {
+ const signal = AbortSignal.timeout(0);
+ assert_true(signal instanceof AbortSignal, "returned object is an AbortSignal");
+ assert_false(signal.aborted, "returned signal is not already aborted");
+}, "AbortSignal.timeout() returns a non-aborted signal");
+
+async_test(t => {
+ const signal = AbortSignal.timeout(5);
+ signal.onabort = t.step_func_done(() => {
+ assert_true(signal.aborted, "signal is aborted");
+ assert_true(signal.reason instanceof DOMException, "signal.reason is a DOMException");
+ assert_equals(signal.reason.name, "TimeoutError", "signal.reason is a TimeoutError");
+ });
+}, "Signal returned by AbortSignal.timeout() times out");
+
+async_test(t => {
+ let result = "";
+ for (const value of ["1", "2", "3"]) {
+ const signal = AbortSignal.timeout(5);
+ signal.onabort = t.step_func(() => { result += value; });
+ }
+
+ const signal = AbortSignal.timeout(5);
+ signal.onabort = t.step_func_done(() => {
+ assert_equals(result, "123", "Timeout order should be 123");
+ });
+}, "AbortSignal timeouts fire in order");
diff --git a/testing/web-platform/tests/dom/abort/abort-signal-any.any.js b/testing/web-platform/tests/dom/abort/abort-signal-any.any.js
new file mode 100644
index 0000000000..b4abb14c1a
--- /dev/null
+++ b/testing/web-platform/tests/dom/abort/abort-signal-any.any.js
@@ -0,0 +1,4 @@
+// META: script=./resources/abort-signal-any-tests.js
+
+abortSignalAnySignalOnlyTests(AbortSignal);
+abortSignalAnyTests(AbortSignal, AbortController);
diff --git a/testing/web-platform/tests/dom/abort/abort-signal-timeout.html b/testing/web-platform/tests/dom/abort/abort-signal-timeout.html
new file mode 100644
index 0000000000..2a9c13d614
--- /dev/null
+++ b/testing/web-platform/tests/dom/abort/abort-signal-timeout.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>AbortSignal.timeout frame detach</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="iframe"></iframe>
+<script>
+ async_test(t => {
+ const signal = iframe.contentWindow.AbortSignal.timeout(5);
+ signal.onabort = t.unreached_func("abort must not fire");
+
+ iframe.remove();
+
+ t.step_timeout(() => {
+ assert_false(signal.aborted);
+ t.done();
+ }, 10);
+ }, "Signal returned by AbortSignal.timeout() is not aborted after frame detach");
+</script>
diff --git a/testing/web-platform/tests/dom/abort/crashtests/timeout-close.html b/testing/web-platform/tests/dom/abort/crashtests/timeout-close.html
new file mode 100644
index 0000000000..ee8544a7f5
--- /dev/null
+++ b/testing/web-platform/tests/dom/abort/crashtests/timeout-close.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="test-wait">
+<meta charset="utf-8">
+<iframe id="iframe"></iframe>
+<script>
+ const srcdoc = `
+ <!DOCTYPE html>
+ <meta charset="utf-8">
+ <script>
+ const xhr = new XMLHttpRequest()
+ setTimeout(() => {
+ xhr.open('GET', '/', false)
+ xhr.send()
+ AbortSignal.timeout(41.62684667994843)
+ }, 1)
+ setTimeout(() => {
+ location.href = "about:blank"
+ parent.document.documentElement.classList.remove("test-wait")
+ }, 0)
+ </` + "script>";
+ iframe.srcdoc = srcdoc;
+</script>
diff --git a/testing/web-platform/tests/dom/abort/event.any.js b/testing/web-platform/tests/dom/abort/event.any.js
new file mode 100644
index 0000000000..bbbe28b233
--- /dev/null
+++ b/testing/web-platform/tests/dom/abort/event.any.js
@@ -0,0 +1,190 @@
+test(t => {
+ const c = new AbortController(),
+ s = c.signal;
+ let state = "begin";
+
+ assert_false(s.aborted);
+ assert_true("reason" in s, "signal has reason property");
+ assert_equals(s.reason, undefined, "signal.reason is initially undefined");
+
+ s.addEventListener("abort",
+ t.step_func(e => {
+ assert_equals(state, "begin");
+ state = "aborted";
+ })
+ );
+ c.abort();
+
+ assert_equals(state, "aborted");
+ assert_true(s.aborted);
+ assert_true(s.reason instanceof DOMException, "signal.reason is DOMException");
+ assert_equals(s.reason.name, "AbortError", "signal.reason is AbortError");
+
+ c.abort();
+}, "AbortController abort() should fire event synchronously");
+
+test(t => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+ assert_equals(controller.signal, signal,
+ "value of controller.signal should not have changed");
+ controller.abort();
+ assert_equals(controller.signal, signal,
+ "value of controller.signal should still not have changed");
+}, "controller.signal should always return the same object");
+
+test(t => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+ let eventCount = 0;
+ signal.onabort = () => {
+ ++eventCount;
+ };
+ controller.abort();
+ assert_true(signal.aborted);
+ assert_equals(eventCount, 1, "event handler should have been called once");
+ controller.abort();
+ assert_true(signal.aborted);
+ assert_equals(eventCount, 1,
+ "event handler should not have been called again");
+}, "controller.abort() should do nothing the second time it is called");
+
+test(t => {
+ const controller = new AbortController();
+ controller.abort();
+ controller.signal.onabort =
+ t.unreached_func("event handler should not be called");
+}, "event handler should not be called if added after controller.abort()");
+
+test(t => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+ signal.onabort = t.step_func(e => {
+ assert_equals(e.type, "abort", "event type should be abort");
+ assert_equals(e.target, signal, "event target should be signal");
+ assert_false(e.bubbles, "event should not bubble");
+ assert_true(e.isTrusted, "event should be trusted");
+ });
+ controller.abort();
+}, "the abort event should have the right properties");
+
+test(t => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+
+ assert_true("reason" in signal, "signal has reason property");
+ assert_equals(signal.reason, undefined, "signal.reason is initially undefined");
+
+ const reason = Error("hello");
+ controller.abort(reason);
+
+ assert_true(signal.aborted, "signal.aborted");
+ assert_equals(signal.reason, reason, "signal.reason");
+}, "AbortController abort(reason) should set signal.reason");
+
+test(t => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+
+ assert_true("reason" in signal, "signal has reason property");
+ assert_equals(signal.reason, undefined, "signal.reason is initially undefined");
+
+ controller.abort();
+
+ assert_true(signal.aborted, "signal.aborted");
+ assert_true(signal.reason instanceof DOMException, "signal.reason is DOMException");
+ assert_equals(signal.reason.name, "AbortError", "signal.reason is AbortError");
+}, "aborting AbortController without reason creates an \"AbortError\" DOMException");
+
+test(t => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+
+ assert_true("reason" in signal, "signal has reason property");
+ assert_equals(signal.reason, undefined, "signal.reason is initially undefined");
+
+ controller.abort(undefined);
+
+ assert_true(signal.aborted, "signal.aborted");
+ assert_true(signal.reason instanceof DOMException, "signal.reason is DOMException");
+ assert_equals(signal.reason.name, "AbortError", "signal.reason is AbortError");
+}, "AbortController abort(undefined) creates an \"AbortError\" DOMException");
+
+test(t => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+
+ assert_true("reason" in signal, "signal has reason property");
+ assert_equals(signal.reason, undefined, "signal.reason is initially undefined");
+
+ controller.abort(null);
+
+ assert_true(signal.aborted, "signal.aborted");
+ assert_equals(signal.reason, null, "signal.reason");
+}, "AbortController abort(null) should set signal.reason");
+
+test(t => {
+ const signal = AbortSignal.abort();
+
+ assert_true(signal.aborted, "signal.aborted");
+ assert_true(signal.reason instanceof DOMException, "signal.reason is DOMException");
+ assert_equals(signal.reason.name, "AbortError", "signal.reason is AbortError");
+}, "static aborting signal should have right properties");
+
+test(t => {
+ const reason = Error("hello");
+ const signal = AbortSignal.abort(reason);
+
+ assert_true(signal.aborted, "signal.aborted");
+ assert_equals(signal.reason, reason, "signal.reason");
+}, "static aborting signal with reason should set signal.reason");
+
+test(t => {
+ const signal = AbortSignal.abort();
+
+ assert_true(
+ signal.reason instanceof DOMException,
+ "signal.reason is a DOMException"
+ );
+ assert_equals(
+ signal.reason,
+ signal.reason,
+ "signal.reason returns the same DOMException"
+ );
+}, "AbortSignal.reason returns the same DOMException");
+
+test(t => {
+ const controller = new AbortController();
+ controller.abort();
+
+ assert_true(
+ controller.signal.reason instanceof DOMException,
+ "signal.reason is a DOMException"
+ );
+ assert_equals(
+ controller.signal.reason,
+ controller.signal.reason,
+ "signal.reason returns the same DOMException"
+ );
+}, "AbortController.signal.reason returns the same DOMException");
+
+test(t => {
+ const reason = new Error('boom');
+ const signal = AbortSignal.abort(reason);
+ assert_true(signal.aborted);
+ assert_throws_exactly(reason, () => signal.throwIfAborted());
+}, "throwIfAborted() should throw abort.reason if signal aborted");
+
+test(t => {
+ const signal = AbortSignal.abort('hello');
+ assert_true(signal.aborted);
+ assert_throws_exactly('hello', () => signal.throwIfAborted());
+}, "throwIfAborted() should throw primitive abort.reason if signal aborted");
+
+test(t => {
+ const controller = new AbortController();
+ assert_false(controller.signal.aborted);
+ controller.signal.throwIfAborted();
+}, "throwIfAborted() should not throw if signal not aborted");
+
+done();
diff --git a/testing/web-platform/tests/dom/abort/reason-constructor.html b/testing/web-platform/tests/dom/abort/reason-constructor.html
new file mode 100644
index 0000000000..0515165a0f
--- /dev/null
+++ b/testing/web-platform/tests/dom/abort/reason-constructor.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>AbortSignal.reason constructor</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="iframe"></iframe>
+<script>
+ test(() => {
+ const aborted = iframe.contentWindow.AbortSignal.abort();
+ assert_equals(aborted.reason.constructor, iframe.contentWindow.DOMException, "DOMException is using the correct global");
+ }, "AbortSignal.reason.constructor should be from iframe");
+</script>
diff --git a/testing/web-platform/tests/dom/abort/resources/abort-signal-any-tests.js b/testing/web-platform/tests/dom/abort/resources/abort-signal-any-tests.js
new file mode 100644
index 0000000000..66e4141eac
--- /dev/null
+++ b/testing/web-platform/tests/dom/abort/resources/abort-signal-any-tests.js
@@ -0,0 +1,185 @@
+// Tests for AbortSignal.any() and subclasses that don't use a controller.
+function abortSignalAnySignalOnlyTests(signalInterface) {
+ const desc = `${signalInterface.name}.any()`
+
+ test(t => {
+ const signal = signalInterface.any([]);
+ assert_false(signal.aborted);
+ }, `${desc} works with an empty array of signals`);
+}
+
+// Tests for AbortSignal.any() and subclasses that use a controller.
+function abortSignalAnyTests(signalInterface, controllerInterface) {
+ const suffix = `(using ${controllerInterface.name})`;
+ const desc = `${signalInterface.name}.any()`;
+
+ test(t => {
+ const controller = new controllerInterface();
+ const signal = controller.signal;
+ const cloneSignal = signalInterface.any([signal]);
+ assert_false(cloneSignal.aborted);
+ assert_true("reason" in cloneSignal, "cloneSignal has reason property");
+ assert_equals(cloneSignal.reason, undefined,
+ "cloneSignal.reason is initially undefined");
+ assert_not_equals(signal, cloneSignal,
+ `${desc} returns a new signal.`);
+
+ let eventFired = false;
+ cloneSignal.onabort = t.step_func((e) => {
+ assert_equals(e.target, cloneSignal,
+ `The event target is the signal returned by ${desc}`);
+ eventFired = true;
+ });
+
+ controller.abort("reason string");
+ assert_true(signal.aborted);
+ assert_true(cloneSignal.aborted);
+ assert_true(eventFired);
+ assert_equals(cloneSignal.reason, "reason string",
+ `${desc} propagates the abort reason`);
+ }, `${desc} follows a single signal ${suffix}`);
+
+ test(t => {
+ for (let i = 0; i < 3; ++i) {
+ const controllers = [];
+ for (let j = 0; j < 3; ++j) {
+ controllers.push(new controllerInterface());
+ }
+ const combinedSignal = signalInterface.any(controllers.map(c => c.signal));
+
+ let eventFired = false;
+ combinedSignal.onabort = t.step_func((e) => {
+ assert_equals(e.target, combinedSignal,
+ `The event target is the signal returned by ${desc}`);
+ eventFired = true;
+ });
+
+ controllers[i].abort();
+ assert_true(eventFired);
+ assert_true(combinedSignal.aborted);
+ assert_true(combinedSignal.reason instanceof DOMException,
+ "signal.reason is a DOMException");
+ assert_equals(combinedSignal.reason.name, "AbortError",
+ "signal.reason is a AbortError");
+ }
+ }, `${desc} follows multiple signals ${suffix}`);
+
+ test(t => {
+ const controllers = [];
+ for (let i = 0; i < 3; ++i) {
+ controllers.push(new controllerInterface());
+ }
+ controllers[1].abort("reason 1");
+ controllers[2].abort("reason 2");
+
+ const signal = signalInterface.any(controllers.map(c => c.signal));
+ assert_true(signal.aborted);
+ assert_equals(signal.reason, "reason 1",
+ "The signal should be aborted with the first reason");
+ }, `${desc} returns an aborted signal if passed an aborted signal ${suffix}`);
+
+ test(t => {
+ const controller = new controllerInterface();
+ const signal = signalInterface.any([controller.signal, controller.signal]);
+ assert_false(signal.aborted);
+ controller.abort("reason");
+ assert_true(signal.aborted);
+ assert_equals(signal.reason, "reason");
+ }, `${desc} can be passed the same signal more than once ${suffix}`);
+
+ test(t => {
+ const controller1 = new controllerInterface();
+ controller1.abort("reason 1");
+ const controller2 = new controllerInterface();
+ controller2.abort("reason 2");
+
+ const signal = signalInterface.any([controller1.signal, controller2.signal, controller1.signal]);
+ assert_true(signal.aborted);
+ assert_equals(signal.reason, "reason 1");
+ }, `${desc} uses the first instance of a duplicate signal ${suffix}`);
+
+ test(t => {
+ for (let i = 0; i < 3; ++i) {
+ const controllers = [];
+ for (let j = 0; j < 3; ++j) {
+ controllers.push(new controllerInterface());
+ }
+ const combinedSignal1 =
+ signalInterface.any([controllers[0].signal, controllers[1].signal]);
+ const combinedSignal2 =
+ signalInterface.any([combinedSignal1, controllers[2].signal]);
+
+ let eventFired = false;
+ combinedSignal2.onabort = t.step_func((e) => {
+ eventFired = true;
+ });
+
+ controllers[i].abort();
+ assert_true(eventFired);
+ assert_true(combinedSignal2.aborted);
+ assert_true(combinedSignal2.reason instanceof DOMException,
+ "signal.reason is a DOMException");
+ assert_equals(combinedSignal2.reason.name, "AbortError",
+ "signal.reason is a AbortError");
+ }
+ }, `${desc} signals are composable ${suffix}`);
+
+ async_test(t => {
+ const controller = new controllerInterface();
+ const timeoutSignal = AbortSignal.timeout(5);
+
+ const combinedSignal = signalInterface.any([controller.signal, timeoutSignal]);
+
+ combinedSignal.onabort = t.step_func_done(() => {
+ assert_true(combinedSignal.aborted);
+ assert_true(combinedSignal.reason instanceof DOMException,
+ "combinedSignal.reason is a DOMException");
+ assert_equals(combinedSignal.reason.name, "TimeoutError",
+ "combinedSignal.reason is a TimeoutError");
+ });
+ }, `${desc} works with signals returned by AbortSignal.timeout() ${suffix}`);
+
+ test(t => {
+ const controller = new controllerInterface();
+ let combined = signalInterface.any([controller.signal]);
+ combined = signalInterface.any([combined]);
+ combined = signalInterface.any([combined]);
+ combined = signalInterface.any([combined]);
+
+ let eventFired = false;
+ combined.onabort = () => {
+ eventFired = true;
+ }
+
+ assert_false(eventFired);
+ assert_false(combined.aborted);
+
+ controller.abort("the reason");
+
+ assert_true(eventFired);
+ assert_true(combined.aborted);
+ assert_equals(combined.reason, "the reason");
+ }, `${desc} works with intermediate signals ${suffix}`);
+
+ test(t => {
+ const controller = new controllerInterface();
+ const signals = [];
+ // The first event should be dispatched on the originating signal.
+ signals.push(controller.signal);
+ // All dependents are linked to `controller.signal` (never to another
+ // composite signal), so this is the order events should fire.
+ signals.push(signalInterface.any([controller.signal]));
+ signals.push(signalInterface.any([controller.signal]));
+ signals.push(signalInterface.any([signals[0]]));
+ signals.push(signalInterface.any([signals[1]]));
+
+ let result = "";
+ for (let i = 0; i < signals.length; i++) {
+ signals[i].addEventListener('abort', () => {
+ result += i;
+ });
+ }
+ controller.abort();
+ assert_equals(result, "01234");
+ }, `Abort events for ${desc} signals fire in the right order ${suffix}`);
+}
diff --git a/testing/web-platform/tests/dom/attributes-are-nodes.html b/testing/web-platform/tests/dom/attributes-are-nodes.html
new file mode 100644
index 0000000000..54ff4ccaec
--- /dev/null
+++ b/testing/web-platform/tests/dom/attributes-are-nodes.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Attributes are Nodes but should not be accepted outside of the `attributes` NamedNodeMap</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-core-changes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+
+ const attribute = document.createAttribute("newattribute");
+
+ assert_true(attribute instanceof Node, "attribute instances are instances of Node");
+ assert_true(Attr.prototype instanceof Node, "attribute instances are instances of Node");
+
+}, "Attrs are subclasses of Nodes");
+
+test(() => {
+
+ const parent = document.createElement("p");
+
+ const attribute = document.createAttribute("newattribute");
+ assert_throws_dom("HierarchyRequestError", () => {
+ parent.appendChild(attribute);
+ });
+
+}, "appendChild with an attribute as the child should fail");
+
+test(() => {
+
+ const parent = document.createElement("p");
+ parent.appendChild(document.createElement("span"));
+
+ const attribute = document.createAttribute("newattribute");
+ assert_throws_dom("HierarchyRequestError", () => {
+ parent.replaceChild(attribute, parent.firstChild);
+ });
+
+}, "replaceChild with an attribute as the child should fail");
+
+test(() => {
+
+ const parent = document.createElement("p");
+ parent.appendChild(document.createElement("span"));
+
+ const attribute = document.createAttribute("newattribute");
+ assert_throws_dom("HierarchyRequestError", () => {
+ parent.insertBefore(attribute, parent.firstChild);
+ });
+
+}, "insertBefore with an attribute as the child should fail");
+
+</script>
diff --git a/testing/web-platform/tests/dom/collections/HTMLCollection-as-prototype.html b/testing/web-platform/tests/dom/collections/HTMLCollection-as-prototype.html
new file mode 100644
index 0000000000..d572d35c04
--- /dev/null
+++ b/testing/web-platform/tests/dom/collections/HTMLCollection-as-prototype.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Objects whose prototype is an HTMLCollection</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ var obj = Object.create(document.getElementsByTagName("script"));
+ assert_throws_js(TypeError, function() {
+ obj.length;
+ });
+}, "HTMLCollection as a prototype should not allow getting .length on the base object")
+
+test(function() {
+ var element = document.createElement("p");
+ element.id = "named";
+ document.body.appendChild(element);
+ this.add_cleanup(function() { element.remove() });
+
+ var collection = document.getElementsByTagName("p");
+ assert_equals(collection.named, element);
+ var object = Object.create(collection);
+ assert_equals(object.named, element);
+ object.named = "foo";
+ assert_equals(object.named, "foo");
+ assert_equals(collection.named, element);
+}, "HTMLCollection as a prototype and setting own properties")
+</script>
diff --git a/testing/web-platform/tests/dom/collections/HTMLCollection-delete.html b/testing/web-platform/tests/dom/collections/HTMLCollection-delete.html
new file mode 100644
index 0000000000..99420d4319
--- /dev/null
+++ b/testing/web-platform/tests/dom/collections/HTMLCollection-delete.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Deleting properties from HTMLCollection</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<i id=foo></i>
+<script>
+let c, expected;
+setup(() => {
+ // These might be cached anyway, so explicitly use a single object.
+ c = document.getElementsByTagName("i");
+ expected = document.getElementById("foo");
+});
+
+test(() => {
+ assert_equals(c[0], expected, "before");
+ delete c[0];
+ assert_equals(c[0], expected, "after");
+}, "Loose id");
+
+test(() => {
+ assert_equals(c[0], expected, "before");
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ delete c[0];
+ });
+ assert_equals(c[0], expected, "after");
+}, "Strict id");
+
+test(() => {
+ assert_equals(c.foo, expected, "before");
+ delete c.foo;
+ assert_equals(c.foo, expected, "after");
+}, "Loose name");
+
+test(() => {
+ assert_equals(c.foo, expected, "before");
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ delete c.foo;
+ });
+ assert_equals(c.foo, expected, "after");
+}, "Strict name");
+</script>
diff --git a/testing/web-platform/tests/dom/collections/HTMLCollection-empty-name.html b/testing/web-platform/tests/dom/collections/HTMLCollection-empty-name.html
new file mode 100644
index 0000000000..4fc34db7f5
--- /dev/null
+++ b/testing/web-platform/tests/dom/collections/HTMLCollection-empty-name.html
@@ -0,0 +1,65 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLCollection and empty names</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<div id=test>
+<div class=a id></div>
+<div class=a name></div>
+<a class=a name></a>
+</div>
+<script>
+test(function() {
+ var c = document.getElementsByTagName("*");
+ assert_false("" in c, "Empty string should not be in the collection.");
+ assert_equals(c[""], undefined, "Named getter should return undefined for empty string.");
+ assert_equals(c.namedItem(""), null, "namedItem should return null for empty string.");
+}, "Empty string as a name for Document.getElementsByTagName");
+
+test(function() {
+ var div = document.getElementById("test");
+ var c = div.getElementsByTagName("*");
+ assert_false("" in c, "Empty string should not be in the collection.");
+ assert_equals(c[""], undefined, "Named getter should return undefined for empty string.");
+ assert_equals(c.namedItem(""), null, "namedItem should return null for empty string.");
+}, "Empty string as a name for Element.getElementsByTagName");
+
+test(function() {
+ var c = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "a");
+ assert_false("" in c, "Empty string should not be in the collection.");
+ assert_equals(c[""], undefined, "Named getter should return undefined for empty string.");
+ assert_equals(c.namedItem(""), null, "namedItem should return null for empty string.");
+}, "Empty string as a name for Document.getElementsByTagNameNS");
+
+test(function() {
+ var div = document.getElementById("test");
+ var c = div.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "a");
+ assert_false("" in c, "Empty string should not be in the collection.");
+ assert_equals(c[""], undefined, "Named getter should return undefined for empty string.");
+ assert_equals(c.namedItem(""), null, "namedItem should return null for empty string.");
+}, "Empty string as a name for Element.getElementsByTagNameNS");
+
+test(function() {
+ var c = document.getElementsByClassName("a");
+ assert_false("" in c, "Empty string should not be in the collection.");
+ assert_equals(c[""], undefined, "Named getter should return undefined for empty string.");
+ assert_equals(c.namedItem(""), null, "namedItem should return null for empty string.");
+}, "Empty string as a name for Document.getElementsByClassName");
+
+test(function() {
+ var div = document.getElementById("test");
+ var c = div.getElementsByClassName("a");
+ assert_false("" in c, "Empty string should not be in the collection.");
+ assert_equals(c[""], undefined, "Named getter should return undefined for empty string.");
+ assert_equals(c.namedItem(""), null, "namedItem should return null for empty string.");
+}, "Empty string as a name for Element.getElementsByClassName");
+
+test(function() {
+ var div = document.getElementById("test");
+ var c = div.children;
+ assert_false("" in c, "Empty string should not be in the collection.");
+ assert_equals(c[""], undefined, "Named getter should return undefined for empty string.");
+ assert_equals(c.namedItem(""), null, "namedItem should return null for empty string.");
+}, "Empty string as a name for Element.children");
+</script>
diff --git a/testing/web-platform/tests/dom/collections/HTMLCollection-iterator.html b/testing/web-platform/tests/dom/collections/HTMLCollection-iterator.html
new file mode 100644
index 0000000000..6296fd1b2d
--- /dev/null
+++ b/testing/web-platform/tests/dom/collections/HTMLCollection-iterator.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<meta charset="utf-8">
+<link rel="help" href="https://dom.spec.whatwg.org/#interface-htmlcollection">
+<link rel="help" href="https://webidl.spec.whatwg.org/#es-iterator">
+<title>HTMLCollection @@iterator Test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<p id="1"></p>
+<p id="2"></p>
+<p id="3"></p>
+<p id="4"></p>
+<p id="5"></p>
+<script>
+"use strict";
+
+const paragraphs = document.getElementsByTagName("p");
+
+test(() => {
+ assert_true("length" in paragraphs);
+}, "HTMLCollection has length method.");
+
+test(() => {
+ assert_false("values" in paragraphs);
+}, "HTMLCollection does not have iterable's values method.");
+
+test(() => {
+ assert_false("entries" in paragraphs);
+}, "HTMLCollection does not have iterable's entries method.");
+
+test(() => {
+ assert_false("forEach" in paragraphs);
+}, "HTMLCollection does not have iterable's forEach method.");
+
+test(() => {
+ assert_true(Symbol.iterator in paragraphs);
+}, "HTMLCollection has Symbol.iterator.");
+
+test(() => {
+ const ids = "12345";
+ let idx = 0;
+ for (const element of paragraphs) {
+ assert_equals(element.getAttribute("id"), ids[idx++]);
+ }
+}, "HTMLCollection is iterable via for-of loop.");
+</script>
diff --git a/testing/web-platform/tests/dom/collections/HTMLCollection-live-mutations.window.js b/testing/web-platform/tests/dom/collections/HTMLCollection-live-mutations.window.js
new file mode 100644
index 0000000000..7dbfc6ccf6
--- /dev/null
+++ b/testing/web-platform/tests/dom/collections/HTMLCollection-live-mutations.window.js
@@ -0,0 +1,93 @@
+function testHTMLCollection(name, hooks) {
+ test(() => {
+ const nodes = {
+ root: document.createElement("div"),
+ div1: document.createElement("div"),
+ div2: document.createElement("div"),
+ p: document.createElement("p")
+ };
+
+ nodes.div1.id = "div1";
+ nodes.div2.id = "div2";
+
+ const list = nodes.root.getElementsByTagName("div");
+
+ hooks.initial(list, nodes);
+
+ nodes.root.appendChild(nodes.div1);
+ nodes.root.appendChild(nodes.p);
+ nodes.root.appendChild(nodes.div2);
+
+ hooks.afterInsertion(list, nodes);
+
+ nodes.root.removeChild(nodes.div1);
+
+ hooks.afterRemoval(list, nodes);
+ }, `HTMLCollection live mutations: ${name}`);
+}
+
+testHTMLCollection("HTMLCollection.length", {
+ initial(list) {
+ assert_equals(list.length, 0);
+ },
+ afterInsertion(list) {
+ assert_equals(list.length, 2);
+ },
+ afterRemoval(list) {
+ assert_equals(list.length, 1);
+ }
+});
+
+testHTMLCollection("HTMLCollection.item(index)", {
+ initial(list) {
+ assert_equals(list.item(0), null);
+ },
+ afterInsertion(list, nodes) {
+ assert_equals(list.item(0), nodes.div1);
+ assert_equals(list.item(1), nodes.div2);
+ },
+ afterRemoval(list, nodes) {
+ assert_equals(list.item(0), nodes.div2);
+ }
+});
+
+testHTMLCollection("HTMLCollection[index]", {
+ initial(list) {
+ assert_equals(list[0], undefined);
+ },
+ afterInsertion(list, nodes) {
+ assert_equals(list[0], nodes.div1);
+ assert_equals(list[1], nodes.div2);
+ },
+ afterRemoval(list, nodes) {
+ assert_equals(list[0], nodes.div2);
+ }
+});
+
+testHTMLCollection("HTMLCollection.namedItem(index)", {
+ initial(list) {
+ assert_equals(list.namedItem("div1"), null);
+ assert_equals(list.namedItem("div2"), null);
+ },
+ afterInsertion(list, nodes) {
+ assert_equals(list.namedItem("div1"), nodes.div1);
+ assert_equals(list.namedItem("div2"), nodes.div2);
+ },
+ afterRemoval(list, nodes) {
+ assert_equals(list.namedItem("div1"), null);
+ assert_equals(list.namedItem("div2"), nodes.div2);
+ }
+});
+
+testHTMLCollection("HTMLCollection ownPropertyNames", {
+ initial(list) {
+ assert_object_equals(Object.getOwnPropertyNames(list), []);
+ },
+ afterInsertion(list) {
+ assert_object_equals(Object.getOwnPropertyNames(list), ["0", "1", "div1", "div2"]);
+ },
+ afterRemoval(list) {
+ assert_object_equals(Object.getOwnPropertyNames(list), ["0", "div2"]);
+ }
+});
+
diff --git a/testing/web-platform/tests/dom/collections/HTMLCollection-own-props.html b/testing/web-platform/tests/dom/collections/HTMLCollection-own-props.html
new file mode 100644
index 0000000000..99dc425dbe
--- /dev/null
+++ b/testing/web-platform/tests/dom/collections/HTMLCollection-own-props.html
@@ -0,0 +1,109 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLCollection getters and own properties</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+function append(t, tag, name) {
+ var element = document.createElement(tag);
+ if (name) {
+ element.id = name;
+ }
+ document.body.appendChild(element);
+ t.add_cleanup(function() { element.remove(); });
+ return element;
+}
+
+test(function() {
+ var name = "named", tag = "a";
+ var c = document.getElementsByTagName(tag);
+ var element = append(this, tag, name);
+ assert_equals(c[name], element);
+ c[name] = "foo";
+ assert_equals(c[name], element);
+}, "Setting non-array index while named property exists (loose)");
+
+test(function() {
+ "use strict";
+ var name = "named", tag = "b";
+ var c = document.getElementsByTagName(tag);
+ var element = append(this, tag, name);
+ assert_equals(c[name], element);
+ assert_throws_js(TypeError, function() {
+ c[name] = "foo";
+ });
+ assert_equals(c[name], element);
+}, "Setting non-array index while named property exists (strict)");
+
+test(function() {
+ var name = "named", tag = "i";
+ var c = document.getElementsByTagName(tag);
+ assert_equals(c[name], undefined);
+ c[name] = "foo";
+ assert_equals(c[name], "foo");
+
+ var element = append(this, tag, name);
+ assert_equals(c[name], "foo");
+ assert_equals(c.namedItem(name), element);
+}, "Setting non-array index while named property doesn't exist (loose)");
+
+test(function() {
+ "use strict";
+ var name = "named", tag = "p";
+ var c = document.getElementsByTagName(tag);
+ assert_equals(c[name], undefined);
+ c[name] = "foo";
+ assert_equals(c[name], "foo");
+
+ var element = append(this, tag, name);
+ assert_equals(c[name], "foo");
+ assert_equals(c.namedItem(name), element);
+}, "Setting non-array index while named property doesn't exist (strict)");
+
+test(function() {
+ var tag = "q";
+ var c = document.getElementsByTagName(tag);
+ var element = append(this, tag);
+ assert_equals(c[0], element);
+ c[0] = "foo";
+ assert_equals(c[0], element);
+}, "Setting array index while indexed property exists (loose)");
+
+test(function() {
+ "use strict";
+ var tag = "s";
+ var c = document.getElementsByTagName(tag);
+ var element = append(this, tag);
+ assert_equals(c[0], element);
+ assert_throws_js(TypeError, function() {
+ c[0] = "foo";
+ });
+ assert_equals(c[0], element);
+}, "Setting array index while indexed property exists (strict)");
+
+test(function() {
+ var tag = "u";
+ var c = document.getElementsByTagName(tag);
+ assert_equals(c[0], undefined);
+ c[0] = "foo";
+ assert_equals(c[0], undefined);
+
+ var element = append(this, tag);
+ assert_equals(c[0], element);
+}, "Setting array index while indexed property doesn't exist (loose)");
+
+test(function() {
+ "use strict";
+ var tag = "u";
+ var c = document.getElementsByTagName(tag);
+ assert_equals(c[0], undefined);
+ assert_throws_js(TypeError, function() {
+ c[0] = "foo";
+ });
+ assert_equals(c[0], undefined);
+
+ var element = append(this, tag);
+ assert_equals(c[0], element);
+}, "Setting array index while indexed property doesn't exist (strict)");
+</script>
diff --git a/testing/web-platform/tests/dom/collections/HTMLCollection-supported-property-indices.html b/testing/web-platform/tests/dom/collections/HTMLCollection-supported-property-indices.html
new file mode 100644
index 0000000000..5339ec31ea
--- /dev/null
+++ b/testing/web-platform/tests/dom/collections/HTMLCollection-supported-property-indices.html
@@ -0,0 +1,179 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<!-- We want to use a tag name that will not interact with our test harness,
+ so just make one up. "foo" is a good one -->
+
+<!-- Ids that look like negative indices. These should come first, so we can
+ assert that lookups for nonnegative indices find these by index -->
+<foo id="-2"></foo>
+<foo id="-1"></foo>
+
+<!-- Ids that look like nonnegative indices -->
+<foo id="0"></foo>
+<foo id="1"></foo>
+
+<!-- Ids that look like nonnegative indices near 2^31 = 2147483648 -->
+<foo id="2147483645"></foo> <!-- 2^31 - 3 -->
+<foo id="2147483646"></foo> <!-- 2^31 - 2 -->
+<foo id="2147483647"></foo> <!-- 2^31 - 1 -->
+<foo id="2147483648"></foo> <!-- 2^31 -->
+<foo id="2147483649"></foo> <!-- 2^31 + 1 -->
+
+<!-- Ids that look like nonnegative indices near 2^32 = 4294967296 -->
+<foo id="4294967293"></foo> <!-- 2^32 - 3 -->
+<foo id="4294967294"></foo> <!-- 2^32 - 2 -->
+<foo id="4294967295"></foo> <!-- 2^32 - 1 -->
+<foo id="4294967296"></foo> <!-- 2^32 -->
+<foo id="4294967297"></foo> <!-- 2^32 + 1 -->
+
+<script>
+test(function() {
+ var collection = document.getElementsByTagName("foo");
+ assert_equals(collection.item(-2), null);
+ assert_equals(collection.item(-1), null);
+ assert_equals(collection.namedItem(-2), document.getElementById("-2"));
+ assert_equals(collection.namedItem(-1), document.getElementById("-1"));
+ assert_equals(collection[-2], document.getElementById("-2"));
+ assert_equals(collection[-1], document.getElementById("-1"));
+}, "Handling of property names that look like negative integers");
+
+test(function() {
+ var collection = document.getElementsByTagName("foo");
+ assert_equals(collection.item(0), document.getElementById("-2"));
+ assert_equals(collection.item(1), document.getElementById("-1"));
+ assert_equals(collection.namedItem(0), document.getElementById("0"));
+ assert_equals(collection.namedItem(1), document.getElementById("1"));
+ assert_equals(collection[0], document.getElementById("-2"));
+ assert_equals(collection[1], document.getElementById("-1"));
+}, "Handling of property names that look like small nonnegative integers");
+
+test(function() {
+ var collection = document.getElementsByTagName("foo");
+ assert_equals(collection.item(2147483645), null);
+ assert_equals(collection.item(2147483646), null);
+ assert_equals(collection.item(2147483647), null);
+ assert_equals(collection.item(2147483648), null);
+ assert_equals(collection.item(2147483649), null);
+ assert_equals(collection.namedItem(2147483645),
+ document.getElementById("2147483645"));
+ assert_equals(collection.namedItem(2147483646),
+ document.getElementById("2147483646"));
+ assert_equals(collection.namedItem(2147483647),
+ document.getElementById("2147483647"));
+ assert_equals(collection.namedItem(2147483648),
+ document.getElementById("2147483648"));
+ assert_equals(collection.namedItem(2147483649),
+ document.getElementById("2147483649"));
+ assert_equals(collection[2147483645], undefined);
+ assert_equals(collection[2147483646], undefined);
+ assert_equals(collection[2147483647], undefined);
+ assert_equals(collection[2147483648], undefined);
+ assert_equals(collection[2147483649], undefined);
+}, "Handling of property names that look like integers around 2^31");
+
+test(function() {
+ var collection = document.getElementsByTagName("foo");
+ assert_equals(collection.item(4294967293), null);
+ assert_equals(collection.item(4294967294), null);
+ assert_equals(collection.item(4294967295), null);
+ assert_equals(collection.item(4294967296), document.getElementById("-2"));
+ assert_equals(collection.item(4294967297), document.getElementById("-1"));
+ assert_equals(collection.namedItem(4294967293),
+ document.getElementById("4294967293"));
+ assert_equals(collection.namedItem(4294967294),
+ document.getElementById("4294967294"));
+ assert_equals(collection.namedItem(4294967295),
+ document.getElementById("4294967295"));
+ assert_equals(collection.namedItem(4294967296),
+ document.getElementById("4294967296"));
+ assert_equals(collection.namedItem(4294967297),
+ document.getElementById("4294967297"));
+ assert_equals(collection[4294967293], undefined);
+ assert_equals(collection[4294967294], undefined);
+ assert_equals(collection[4294967295], document.getElementById("4294967295"));
+ assert_equals(collection[4294967296], document.getElementById("4294967296"));
+ assert_equals(collection[4294967297], document.getElementById("4294967297"));
+}, "Handling of property names that look like integers around 2^32");
+
+test(function() {
+ var elements = document.getElementsByTagName("foo");
+ var old_item = elements[0];
+ var old_desc = Object.getOwnPropertyDescriptor(elements, 0);
+ assert_equals(old_desc.value, old_item);
+ assert_true(old_desc.enumerable);
+ assert_true(old_desc.configurable);
+ assert_false(old_desc.writable);
+
+ elements[0] = 5;
+ assert_equals(elements[0], old_item);
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ elements[0] = 5;
+ });
+ assert_throws_js(TypeError, function() {
+ Object.defineProperty(elements, 0, { value: 5 });
+ });
+
+ delete elements[0];
+ assert_equals(elements[0], old_item);
+
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ delete elements[0];
+ });
+ assert_equals(elements[0], old_item);
+}, 'Trying to set an expando that would shadow an already-existing indexed property');
+
+test(function() {
+ var elements = document.getElementsByTagName("foo");
+ var idx = elements.length;
+ var old_item = elements[idx];
+ var old_desc = Object.getOwnPropertyDescriptor(elements, idx);
+ assert_equals(old_item, undefined);
+ assert_equals(old_desc, undefined);
+
+ // [[DefineOwnProperty]] will disallow defining an indexed expando.
+ elements[idx] = 5;
+ assert_equals(elements[idx], undefined);
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ elements[idx] = 5;
+ });
+ assert_throws_js(TypeError, function() {
+ Object.defineProperty(elements, idx, { value: 5 });
+ });
+
+ // Check that deletions out of range do not throw
+ delete elements[idx];
+ (function() {
+ "use strict";
+ delete elements[idx];
+ })();
+}, 'Trying to set an expando with an indexed property name past the end of the list');
+
+test(function(){
+ var elements = document.getElementsByTagName("foo");
+ var old_item = elements[0];
+ var old_desc = Object.getOwnPropertyDescriptor(elements, 0);
+ assert_equals(old_desc.value, old_item);
+ assert_true(old_desc.enumerable);
+ assert_true(old_desc.configurable);
+ assert_false(old_desc.writable);
+
+ Object.prototype[0] = 5;
+ this.add_cleanup(function () { delete Object.prototype[0]; });
+ assert_equals(elements[0], old_item);
+
+ delete elements[0];
+ assert_equals(elements[0], old_item);
+
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ delete elements[0];
+ });
+ assert_equals(elements[0], old_item);
+}, 'Trying to delete an indexed property name should never work');
+</script>
diff --git a/testing/web-platform/tests/dom/collections/HTMLCollection-supported-property-names.html b/testing/web-platform/tests/dom/collections/HTMLCollection-supported-property-names.html
new file mode 100644
index 0000000000..3d21e16692
--- /dev/null
+++ b/testing/web-platform/tests/dom/collections/HTMLCollection-supported-property-names.html
@@ -0,0 +1,135 @@
+<!doctype html>
+<meta charset=utf-8>
+<link rel=help href=https://dom.spec.whatwg.org/#interface-htmlcollection>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<div id=log></div>
+
+<!-- with no attribute -->
+<span></span>
+
+<!-- with `id` attribute -->
+<span id=''></span>
+<span id='some-id'></span>
+<span id='some-id'></span><!-- to ensure no duplicates -->
+
+<!-- with `name` attribute -->
+<span name=''></span>
+<span name='some-name'></span>
+<span name='some-name'></span><!-- to ensure no duplicates -->
+
+<!-- with `name` and `id` attribute -->
+<span id='another-id' name='another-name'></span>
+
+<script>
+test(function () {
+ var elements = document.getElementsByTagName("span");
+ assert_array_equals(
+ Object.getOwnPropertyNames(elements),
+ ['0', '1', '2', '3', '4', '5', '6', '7', 'some-id', 'some-name', 'another-id', 'another-name']
+ );
+}, 'Object.getOwnPropertyNames on HTMLCollection');
+
+test(function () {
+ var elem = document.createElementNS('some-random-namespace', 'foo');
+ this.add_cleanup(function () {elem.remove();});
+ elem.setAttribute("name", "some-name");
+ document.body.appendChild(elem);
+
+ var elements = document.getElementsByTagName("foo");
+ assert_array_equals(Object.getOwnPropertyNames(elements), ['0']);
+}, 'Object.getOwnPropertyNames on HTMLCollection with non-HTML namespace');
+
+test(function () {
+ var elem = document.createElement('foo');
+ this.add_cleanup(function () {elem.remove();});
+ document.body.appendChild(elem);
+
+ var elements = document.getElementsByTagName("foo");
+ elements.someProperty = "some value";
+
+ assert_array_equals(Object.getOwnPropertyNames(elements), ['0', 'someProperty']);
+}, 'Object.getOwnPropertyNames on HTMLCollection with expando object');
+
+test(function() {
+ var elements = document.getElementsByTagName("span");
+ var old_item = elements["some-id"];
+ var old_desc = Object.getOwnPropertyDescriptor(elements, "some-id");
+ assert_equals(old_desc.value, old_item);
+ assert_false(old_desc.enumerable);
+ assert_true(old_desc.configurable);
+ assert_false(old_desc.writable);
+
+ elements["some-id"] = 5;
+ assert_equals(elements["some-id"], old_item);
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ elements["some-id"] = 5;
+ });
+ assert_throws_js(TypeError, function() {
+ Object.defineProperty(elements, "some-id", { value: 5 });
+ });
+
+ delete elements["some-id"];
+ assert_equals(elements["some-id"], old_item);
+
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ delete elements["some-id"];
+ });
+ assert_equals(elements["some-id"], old_item);
+
+}, 'Trying to set an expando that would shadow an already-existing named property');
+
+test(function() {
+ var elements = document.getElementsByTagName("span");
+ var old_item = elements["new-id"];
+ var old_desc = Object.getOwnPropertyDescriptor(elements, "new-id");
+ assert_equals(old_item, undefined);
+ assert_equals(old_desc, undefined);
+
+ elements["new-id"] = 5;
+ assert_equals(elements["new-id"], 5);
+
+ var span = document.createElement("span");
+ this.add_cleanup(function () {span.remove();});
+ span.id = "new-id";
+ document.body.appendChild(span);
+
+ assert_equals(elements.namedItem("new-id"), span);
+ assert_equals(elements["new-id"], 5);
+
+ delete elements["new-id"];
+ assert_equals(elements["new-id"], span);
+}, 'Trying to set an expando that shadows a named property that gets added later');
+
+test(function() {
+ var elements = document.getElementsByTagName("span");
+ var old_item = elements["new-id2"];
+ var old_desc = Object.getOwnPropertyDescriptor(elements, "new-id2");
+ assert_equals(old_item, undefined);
+ assert_equals(old_desc, undefined);
+
+ Object.defineProperty(elements, "new-id2", { configurable: false, writable:
+ false, value: 5 });
+ assert_equals(elements["new-id2"], 5);
+
+ var span = document.createElement("span");
+ this.add_cleanup(function () {span.remove();});
+ span.id = "new-id2";
+ document.body.appendChild(span);
+
+ assert_equals(elements.namedItem("new-id2"), span);
+ assert_equals(elements["new-id2"], 5);
+
+ delete elements["new-id2"];
+ assert_equals(elements["new-id2"], 5);
+
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ delete elements["new-id2"];
+ });
+ assert_equals(elements["new-id2"], 5);
+}, 'Trying to set a non-configurable expando that shadows a named property that gets added later');
+</script>
diff --git a/testing/web-platform/tests/dom/collections/domstringmap-supported-property-names.html b/testing/web-platform/tests/dom/collections/domstringmap-supported-property-names.html
new file mode 100644
index 0000000000..430aa44c3a
--- /dev/null
+++ b/testing/web-platform/tests/dom/collections/domstringmap-supported-property-names.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>DOMStringMap Test: Supported property names</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+
+<div id="edge1" data-="012">Simple</div>
+
+<div id="edge2" data-id-="012">Simple</div>
+
+<div id="user" data-id="1234567890" data-user="johndoe" data-date-of-birth>
+ John Doe
+</div>
+
+<div id="user2" data-unique-id="1234567890"> Jane Doe </div>
+
+<div id="user3" data-unique-id="4324324241"> Jim Doe </div>
+
+<script>
+
+test(function() {
+ var element = document.querySelector('#edge1');
+ assert_array_equals(Object.getOwnPropertyNames(element.dataset),
+ [""]);
+}, "Object.getOwnPropertyNames on DOMStringMap, empty data attribute");
+
+test(function() {
+ var element = document.querySelector('#edge2');
+ assert_array_equals(Object.getOwnPropertyNames(element.dataset),
+ ["id-"]);
+}, "Object.getOwnPropertyNames on DOMStringMap, data attribute trailing hyphen");
+
+test(function() {
+ var element = document.querySelector('#user');
+ assert_array_equals(Object.getOwnPropertyNames(element.dataset),
+ ['id', 'user', 'dateOfBirth']);
+}, "Object.getOwnPropertyNames on DOMStringMap, multiple data attributes");
+
+test(function() {
+ var element = document.querySelector('#user2');
+ element.dataset.middleName = "mark";
+ assert_array_equals(Object.getOwnPropertyNames(element.dataset),
+ ['uniqueId', 'middleName']);
+}, "Object.getOwnPropertyNames on DOMStringMap, attribute set on dataset in JS");
+
+test(function() {
+ var element = document.querySelector('#user3');
+ element.setAttribute("data-age", 30);
+ assert_array_equals(Object.getOwnPropertyNames(element.dataset),
+ ['uniqueId', 'age']);
+}, "Object.getOwnPropertyNames on DOMStringMap, attribute set on element in JS");
+
+</script>
diff --git a/testing/web-platform/tests/dom/collections/namednodemap-supported-property-names.html b/testing/web-platform/tests/dom/collections/namednodemap-supported-property-names.html
new file mode 100644
index 0000000000..2c5dee4efd
--- /dev/null
+++ b/testing/web-platform/tests/dom/collections/namednodemap-supported-property-names.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>NamedNodeMap Test: Supported property names</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="simple" class="fancy">Simple</div>
+<input id="result" type="text" value="" width="200px">
+<script>
+
+test(function() {
+ var elt = document.querySelector('#simple');
+ assert_array_equals(Object.getOwnPropertyNames(elt.attributes),
+ ['0','1','id','class']);
+}, "Object.getOwnPropertyNames on NamedNodeMap");
+
+test(function() {
+ var result = document.getElementById("result");
+ assert_array_equals(Object.getOwnPropertyNames(result.attributes),
+ ['0','1','2','3','id','type','value','width']);
+}, "Object.getOwnPropertyNames on NamedNodeMap of input");
+
+test(function() {
+ var result = document.getElementById("result");
+ result.removeAttribute("width");
+ assert_array_equals(Object.getOwnPropertyNames(result.attributes),
+ ['0','1','2','id','type','value']);
+}, "Object.getOwnPropertyNames on NamedNodeMap after attribute removal");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/common.js b/testing/web-platform/tests/dom/common.js
new file mode 100644
index 0000000000..70367b2eff
--- /dev/null
+++ b/testing/web-platform/tests/dom/common.js
@@ -0,0 +1,1083 @@
+"use strict";
+// Written by Aryeh Gregor <ayg@aryeh.name>
+
+// TODO: iframes, contenteditable/designMode
+
+// Everything is done in functions in this test harness, so we have to declare
+// all the variables before use to make sure they can be reused.
+var testDiv, paras, detachedDiv, detachedPara1, detachedPara2,
+ foreignDoc, foreignPara1, foreignPara2, xmlDoc, xmlElement,
+ detachedXmlElement, detachedTextNode, foreignTextNode,
+ detachedForeignTextNode, xmlTextNode, detachedXmlTextNode,
+ processingInstruction, detachedProcessingInstruction, comment,
+ detachedComment, foreignComment, detachedForeignComment, xmlComment,
+ detachedXmlComment, docfrag, foreignDocfrag, xmlDocfrag, doctype,
+ foreignDoctype, xmlDoctype;
+var testRangesShort, testRanges, testPoints, testNodesShort, testNodes;
+
+function setupRangeTests() {
+ testDiv = document.querySelector("#test");
+ if (testDiv) {
+ testDiv.parentNode.removeChild(testDiv);
+ }
+ testDiv = document.createElement("div");
+ testDiv.id = "test";
+ document.body.insertBefore(testDiv, document.body.firstChild);
+
+ paras = [];
+ paras.push(document.createElement("p"));
+ paras[0].setAttribute("id", "a");
+ // Test some diacritics, to make sure browsers are using code units here
+ // and not something like grapheme clusters.
+ paras[0].textContent = "A\u0308b\u0308c\u0308d\u0308e\u0308f\u0308g\u0308h\u0308\n";
+ testDiv.appendChild(paras[0]);
+
+ paras.push(document.createElement("p"));
+ paras[1].setAttribute("id", "b");
+ paras[1].setAttribute("style", "display:none");
+ paras[1].textContent = "Ijklmnop\n";
+ testDiv.appendChild(paras[1]);
+
+ paras.push(document.createElement("p"));
+ paras[2].setAttribute("id", "c");
+ paras[2].textContent = "Qrstuvwx";
+ testDiv.appendChild(paras[2]);
+
+ paras.push(document.createElement("p"));
+ paras[3].setAttribute("id", "d");
+ paras[3].setAttribute("style", "display:none");
+ paras[3].textContent = "Yzabcdef";
+ testDiv.appendChild(paras[3]);
+
+ paras.push(document.createElement("p"));
+ paras[4].setAttribute("id", "e");
+ paras[4].setAttribute("style", "display:none");
+ paras[4].textContent = "Ghijklmn";
+ testDiv.appendChild(paras[4]);
+
+ detachedDiv = document.createElement("div");
+ detachedPara1 = document.createElement("p");
+ detachedPara1.appendChild(document.createTextNode("Opqrstuv"));
+ detachedPara2 = document.createElement("p");
+ detachedPara2.appendChild(document.createTextNode("Wxyzabcd"));
+ detachedDiv.appendChild(detachedPara1);
+ detachedDiv.appendChild(detachedPara2);
+
+ // Opera doesn't automatically create a doctype for a new HTML document,
+ // contrary to spec. It also doesn't let you add doctypes to documents
+ // after the fact through any means I've tried. So foreignDoc in Opera
+ // will have no doctype, foreignDoctype will be null, and Opera will fail
+ // some tests somewhat mysteriously as a result.
+ foreignDoc = document.implementation.createHTMLDocument("");
+ foreignPara1 = foreignDoc.createElement("p");
+ foreignPara1.appendChild(foreignDoc.createTextNode("Efghijkl"));
+ foreignPara2 = foreignDoc.createElement("p");
+ foreignPara2.appendChild(foreignDoc.createTextNode("Mnopqrst"));
+ foreignDoc.body.appendChild(foreignPara1);
+ foreignDoc.body.appendChild(foreignPara2);
+
+ // Now we get to do really silly stuff, which nobody in the universe is
+ // ever going to actually do, but the spec defines behavior, so too bad.
+ // Testing is fun!
+ xmlDoctype = document.implementation.createDocumentType("qorflesnorf", "abcde", "x\"'y");
+ xmlDoc = document.implementation.createDocument(null, null, xmlDoctype);
+ detachedXmlElement = xmlDoc.createElement("everyone-hates-hyphenated-element-names");
+ detachedTextNode = document.createTextNode("Uvwxyzab");
+ detachedForeignTextNode = foreignDoc.createTextNode("Cdefghij");
+ detachedXmlTextNode = xmlDoc.createTextNode("Klmnopqr");
+ // PIs only exist in XML documents, so don't bother with document or
+ // foreignDoc.
+ detachedProcessingInstruction = xmlDoc.createProcessingInstruction("whippoorwill", "chirp chirp chirp");
+ detachedComment = document.createComment("Stuvwxyz");
+ // Hurrah, we finally got to "z" at the end!
+ detachedForeignComment = foreignDoc.createComment("אריה יהודה");
+ detachedXmlComment = xmlDoc.createComment("בן חיים אליעזר");
+
+ // We should also test with document fragments that actually contain stuff
+ // . . . but, maybe later.
+ docfrag = document.createDocumentFragment();
+ foreignDocfrag = foreignDoc.createDocumentFragment();
+ xmlDocfrag = xmlDoc.createDocumentFragment();
+
+ xmlElement = xmlDoc.createElement("igiveuponcreativenames");
+ xmlTextNode = xmlDoc.createTextNode("do re mi fa so la ti");
+ xmlElement.appendChild(xmlTextNode);
+ processingInstruction = xmlDoc.createProcessingInstruction("somePI", 'Did you know that ":syn sync fromstart" is very useful when using vim to edit large amounts of JavaScript embedded in HTML?');
+ xmlDoc.appendChild(xmlElement);
+ xmlDoc.appendChild(processingInstruction);
+ xmlComment = xmlDoc.createComment("I maliciously created a comment that will break incautious XML serializers, but Firefox threw an exception, so all I got was this lousy T-shirt");
+ xmlDoc.appendChild(xmlComment);
+
+ comment = document.createComment("Alphabet soup?");
+ testDiv.appendChild(comment);
+
+ foreignComment = foreignDoc.createComment('"Commenter" and "commentator" mean different things. I\'ve seen non-native speakers trip up on this.');
+ foreignDoc.appendChild(foreignComment);
+ foreignTextNode = foreignDoc.createTextNode("I admit that I harbor doubts about whether we really need so many things to test, but it's too late to stop now.");
+ foreignDoc.body.appendChild(foreignTextNode);
+
+ doctype = document.doctype;
+ foreignDoctype = foreignDoc.doctype;
+
+ testRangesShort = [
+ // Various ranges within the text node children of different
+ // paragraphs. All should be valid.
+ "[paras[0].firstChild, 0, paras[0].firstChild, 0]",
+ "[paras[0].firstChild, 0, paras[0].firstChild, 1]",
+ "[paras[0].firstChild, 2, paras[0].firstChild, 8]",
+ "[paras[0].firstChild, 2, paras[0].firstChild, 9]",
+ "[paras[1].firstChild, 0, paras[1].firstChild, 0]",
+ "[paras[1].firstChild, 2, paras[1].firstChild, 9]",
+ "[detachedPara1.firstChild, 0, detachedPara1.firstChild, 0]",
+ "[detachedPara1.firstChild, 2, detachedPara1.firstChild, 8]",
+ "[foreignPara1.firstChild, 0, foreignPara1.firstChild, 0]",
+ "[foreignPara1.firstChild, 2, foreignPara1.firstChild, 8]",
+ // Now try testing some elements, not just text nodes.
+ "[document.documentElement, 0, document.documentElement, 1]",
+ "[document.documentElement, 0, document.documentElement, 2]",
+ "[document.documentElement, 1, document.documentElement, 2]",
+ "[document.head, 1, document.head, 1]",
+ "[document.body, 4, document.body, 5]",
+ "[foreignDoc.documentElement, 0, foreignDoc.documentElement, 1]",
+ "[paras[0], 0, paras[0], 1]",
+ "[detachedPara1, 0, detachedPara1, 1]",
+ // Now try some ranges that span elements.
+ "[paras[0].firstChild, 0, paras[1].firstChild, 0]",
+ "[paras[0].firstChild, 0, paras[1].firstChild, 8]",
+ "[paras[0].firstChild, 3, paras[3], 1]",
+ // How about something that spans a node and its descendant?
+ "[paras[0], 0, paras[0].firstChild, 7]",
+ "[testDiv, 2, paras[4], 1]",
+ // Then a few more interesting things just for good measure.
+ "[document, 0, document, 1]",
+ "[document, 0, document, 2]",
+ "[comment, 2, comment, 3]",
+ "[testDiv, 0, comment, 5]",
+ "[foreignDoc, 1, foreignComment, 2]",
+ "[foreignDoc.body, 0, foreignTextNode, 36]",
+ "[xmlDoc, 1, xmlComment, 0]",
+ "[detachedTextNode, 0, detachedTextNode, 8]",
+ "[detachedForeignTextNode, 0, detachedForeignTextNode, 8]",
+ "[detachedXmlTextNode, 0, detachedXmlTextNode, 8]",
+ "[detachedComment, 3, detachedComment, 4]",
+ "[detachedForeignComment, 0, detachedForeignComment, 1]",
+ "[detachedXmlComment, 2, detachedXmlComment, 6]",
+ "[docfrag, 0, docfrag, 0]",
+ "[processingInstruction, 0, processingInstruction, 4]",
+ ];
+
+ testRanges = testRangesShort.concat([
+ "[paras[1].firstChild, 0, paras[1].firstChild, 1]",
+ "[paras[1].firstChild, 2, paras[1].firstChild, 8]",
+ "[detachedPara1.firstChild, 0, detachedPara1.firstChild, 1]",
+ "[foreignPara1.firstChild, 0, foreignPara1.firstChild, 1]",
+ "[foreignDoc.head, 1, foreignDoc.head, 1]",
+ "[foreignDoc.body, 0, foreignDoc.body, 0]",
+ "[paras[0], 0, paras[0], 0]",
+ "[detachedPara1, 0, detachedPara1, 0]",
+ "[testDiv, 1, paras[2].firstChild, 5]",
+ "[document.documentElement, 1, document.body, 0]",
+ "[foreignDoc.documentElement, 1, foreignDoc.body, 0]",
+ "[document, 1, document, 2]",
+ "[paras[2].firstChild, 4, comment, 2]",
+ "[paras[3], 1, comment, 8]",
+ "[foreignDoc, 0, foreignDoc, 0]",
+ "[xmlDoc, 0, xmlDoc, 0]",
+ "[detachedForeignTextNode, 7, detachedForeignTextNode, 7]",
+ "[detachedXmlTextNode, 7, detachedXmlTextNode, 7]",
+ "[detachedComment, 5, detachedComment, 5]",
+ "[detachedForeignComment, 4, detachedForeignComment, 4]",
+ "[foreignDocfrag, 0, foreignDocfrag, 0]",
+ "[xmlDocfrag, 0, xmlDocfrag, 0]",
+ ]);
+
+ testPoints = [
+ // Various positions within the page, some invalid. Remember that
+ // paras[0] is visible, and paras[1] is display: none.
+ "[paras[0].firstChild, -1]",
+ "[paras[0].firstChild, 0]",
+ "[paras[0].firstChild, 1]",
+ "[paras[0].firstChild, 2]",
+ "[paras[0].firstChild, 8]",
+ "[paras[0].firstChild, 9]",
+ "[paras[0].firstChild, 10]",
+ "[paras[0].firstChild, 65535]",
+ "[paras[1].firstChild, -1]",
+ "[paras[1].firstChild, 0]",
+ "[paras[1].firstChild, 1]",
+ "[paras[1].firstChild, 2]",
+ "[paras[1].firstChild, 8]",
+ "[paras[1].firstChild, 9]",
+ "[paras[1].firstChild, 10]",
+ "[paras[1].firstChild, 65535]",
+ "[detachedPara1.firstChild, 0]",
+ "[detachedPara1.firstChild, 1]",
+ "[detachedPara1.firstChild, 8]",
+ "[detachedPara1.firstChild, 9]",
+ "[foreignPara1.firstChild, 0]",
+ "[foreignPara1.firstChild, 1]",
+ "[foreignPara1.firstChild, 8]",
+ "[foreignPara1.firstChild, 9]",
+ // Now try testing some elements, not just text nodes.
+ "[document.documentElement, -1]",
+ "[document.documentElement, 0]",
+ "[document.documentElement, 1]",
+ "[document.documentElement, 2]",
+ "[document.documentElement, 7]",
+ "[document.head, 1]",
+ "[document.body, 3]",
+ "[foreignDoc.documentElement, 0]",
+ "[foreignDoc.documentElement, 1]",
+ "[foreignDoc.head, 0]",
+ "[foreignDoc.body, 1]",
+ "[paras[0], 0]",
+ "[paras[0], 1]",
+ "[paras[0], 2]",
+ "[paras[1], 0]",
+ "[paras[1], 1]",
+ "[paras[1], 2]",
+ "[detachedPara1, 0]",
+ "[detachedPara1, 1]",
+ "[testDiv, 0]",
+ "[testDiv, 3]",
+ // Then a few more interesting things just for good measure.
+ "[document, -1]",
+ "[document, 0]",
+ "[document, 1]",
+ "[document, 2]",
+ "[document, 3]",
+ "[comment, -1]",
+ "[comment, 0]",
+ "[comment, 4]",
+ "[comment, 96]",
+ "[foreignDoc, 0]",
+ "[foreignDoc, 1]",
+ "[foreignComment, 2]",
+ "[foreignTextNode, 0]",
+ "[foreignTextNode, 36]",
+ "[xmlDoc, -1]",
+ "[xmlDoc, 0]",
+ "[xmlDoc, 1]",
+ "[xmlDoc, 5]",
+ "[xmlComment, 0]",
+ "[xmlComment, 4]",
+ "[processingInstruction, 0]",
+ "[processingInstruction, 5]",
+ "[processingInstruction, 9]",
+ "[detachedTextNode, 0]",
+ "[detachedTextNode, 8]",
+ "[detachedForeignTextNode, 0]",
+ "[detachedForeignTextNode, 8]",
+ "[detachedXmlTextNode, 0]",
+ "[detachedXmlTextNode, 8]",
+ "[detachedProcessingInstruction, 12]",
+ "[detachedComment, 3]",
+ "[detachedComment, 5]",
+ "[detachedForeignComment, 0]",
+ "[detachedForeignComment, 4]",
+ "[detachedXmlComment, 2]",
+ "[docfrag, 0]",
+ "[foreignDocfrag, 0]",
+ "[xmlDocfrag, 0]",
+ "[doctype, 0]",
+ "[doctype, -17]",
+ "[doctype, 1]",
+ "[foreignDoctype, 0]",
+ "[xmlDoctype, 0]",
+ ];
+
+ testNodesShort = [
+ "paras[0]",
+ "paras[0].firstChild",
+ "paras[1].firstChild",
+ "foreignPara1",
+ "foreignPara1.firstChild",
+ "detachedPara1",
+ "detachedPara1.firstChild",
+ "document",
+ "detachedDiv",
+ "foreignDoc",
+ "foreignPara2",
+ "xmlDoc",
+ "xmlElement",
+ "detachedTextNode",
+ "foreignTextNode",
+ "processingInstruction",
+ "detachedProcessingInstruction",
+ "comment",
+ "detachedComment",
+ "docfrag",
+ "doctype",
+ "foreignDoctype",
+ ];
+
+ testNodes = testNodesShort.concat([
+ "paras[1]",
+ "detachedPara2",
+ "detachedPara2.firstChild",
+ "testDiv",
+ "detachedXmlElement",
+ "detachedForeignTextNode",
+ "xmlTextNode",
+ "detachedXmlTextNode",
+ "xmlComment",
+ "foreignComment",
+ "detachedForeignComment",
+ "detachedXmlComment",
+ "foreignDocfrag",
+ "xmlDocfrag",
+ "xmlDoctype",
+ ]);
+}
+if ("setup" in window) {
+ setup(setupRangeTests);
+} else {
+ // Presumably we're running from within an iframe or something
+ setupRangeTests();
+}
+
+/**
+ * The "length" of a node as defined by the Ranges section of DOM4.
+ */
+function nodeLength(node) {
+ // "The length of a node node depends on node:
+ //
+ // "DocumentType
+ // "0."
+ if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
+ return 0;
+ }
+ // "Text
+ // "ProcessingInstruction
+ // "Comment
+ // "Its length attribute value."
+ // Browsers don't historically support the length attribute on
+ // ProcessingInstruction, so to avoid spurious failures, do
+ // node.data.length instead of node.length.
+ if (node.nodeType == Node.TEXT_NODE || node.nodeType == Node.PROCESSING_INSTRUCTION_NODE || node.nodeType == Node.COMMENT_NODE) {
+ return node.data.length;
+ }
+ // "Any other node
+ // "Its number of children."
+ return node.childNodes.length;
+}
+
+/**
+ * Returns the furthest ancestor of a Node as defined by the spec.
+ */
+function furthestAncestor(node) {
+ var root = node;
+ while (root.parentNode != null) {
+ root = root.parentNode;
+ }
+ return root;
+}
+
+/**
+ * "The ancestor containers of a Node are the Node itself and all its
+ * ancestors."
+ *
+ * Is node1 an ancestor container of node2?
+ */
+function isAncestorContainer(node1, node2) {
+ return node1 == node2 ||
+ (node2.compareDocumentPosition(node1) & Node.DOCUMENT_POSITION_CONTAINS);
+}
+
+/**
+ * Returns the first Node that's after node in tree order, or null if node is
+ * the last Node.
+ */
+function nextNode(node) {
+ if (node.hasChildNodes()) {
+ return node.firstChild;
+ }
+ return nextNodeDescendants(node);
+}
+
+/**
+ * Returns the last Node that's before node in tree order, or null if node is
+ * the first Node.
+ */
+function previousNode(node) {
+ if (node.previousSibling) {
+ node = node.previousSibling;
+ while (node.hasChildNodes()) {
+ node = node.lastChild;
+ }
+ return node;
+ }
+ return node.parentNode;
+}
+
+/**
+ * Returns the next Node that's after node and all its descendants in tree
+ * order, or null if node is the last Node or an ancestor of it.
+ */
+function nextNodeDescendants(node) {
+ while (node && !node.nextSibling) {
+ node = node.parentNode;
+ }
+ if (!node) {
+ return null;
+ }
+ return node.nextSibling;
+}
+
+/**
+ * Returns the ownerDocument of the Node, or the Node itself if it's a
+ * Document.
+ */
+function ownerDocument(node) {
+ return node.nodeType == Node.DOCUMENT_NODE
+ ? node
+ : node.ownerDocument;
+}
+
+/**
+ * Returns true if ancestor is an ancestor of descendant, false otherwise.
+ */
+function isAncestor(ancestor, descendant) {
+ if (!ancestor || !descendant) {
+ return false;
+ }
+ while (descendant && descendant != ancestor) {
+ descendant = descendant.parentNode;
+ }
+ return descendant == ancestor;
+}
+
+/**
+ * Returns true if ancestor is an inclusive ancestor of descendant, false
+ * otherwise.
+ */
+function isInclusiveAncestor(ancestor, descendant) {
+ return ancestor === descendant || isAncestor(ancestor, descendant);
+}
+
+/**
+ * Returns true if descendant is a descendant of ancestor, false otherwise.
+ */
+function isDescendant(descendant, ancestor) {
+ return isAncestor(ancestor, descendant);
+}
+
+/**
+ * Returns true if descendant is an inclusive descendant of ancestor, false
+ * otherwise.
+ */
+function isInclusiveDescendant(descendant, ancestor) {
+ return descendant === ancestor || isDescendant(descendant, ancestor);
+}
+
+/**
+ * The position of two boundary points relative to one another, as defined by
+ * the spec.
+ */
+function getPosition(nodeA, offsetA, nodeB, offsetB) {
+ // "If node A is the same as node B, return equal if offset A equals offset
+ // B, before if offset A is less than offset B, and after if offset A is
+ // greater than offset B."
+ if (nodeA == nodeB) {
+ if (offsetA == offsetB) {
+ return "equal";
+ }
+ if (offsetA < offsetB) {
+ return "before";
+ }
+ if (offsetA > offsetB) {
+ return "after";
+ }
+ }
+
+ // "If node A is after node B in tree order, compute the position of (node
+ // B, offset B) relative to (node A, offset A). If it is before, return
+ // after. If it is after, return before."
+ if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_FOLLOWING) {
+ var pos = getPosition(nodeB, offsetB, nodeA, offsetA);
+ if (pos == "before") {
+ return "after";
+ }
+ if (pos == "after") {
+ return "before";
+ }
+ }
+
+ // "If node A is an ancestor of node B:"
+ if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_CONTAINS) {
+ // "Let child equal node B."
+ var child = nodeB;
+
+ // "While child is not a child of node A, set child to its parent."
+ while (child.parentNode != nodeA) {
+ child = child.parentNode;
+ }
+
+ // "If the index of child is less than offset A, return after."
+ if (indexOf(child) < offsetA) {
+ return "after";
+ }
+ }
+
+ // "Return before."
+ return "before";
+}
+
+/**
+ * "contained" as defined by DOM Range: "A Node node is contained in a range
+ * range if node's furthest ancestor is the same as range's root, and (node, 0)
+ * is after range's start, and (node, length of node) is before range's end."
+ */
+function isContained(node, range) {
+ var pos1 = getPosition(node, 0, range.startContainer, range.startOffset);
+ var pos2 = getPosition(node, nodeLength(node), range.endContainer, range.endOffset);
+
+ return furthestAncestor(node) == furthestAncestor(range.startContainer)
+ && pos1 == "after"
+ && pos2 == "before";
+}
+
+/**
+ * "partially contained" as defined by DOM Range: "A Node is partially
+ * contained in a range if it is an ancestor container of the range's start but
+ * not its end, or vice versa."
+ */
+function isPartiallyContained(node, range) {
+ var cond1 = isAncestorContainer(node, range.startContainer);
+ var cond2 = isAncestorContainer(node, range.endContainer);
+ return (cond1 && !cond2) || (cond2 && !cond1);
+}
+
+/**
+ * Index of a node as defined by the spec.
+ */
+function indexOf(node) {
+ if (!node.parentNode) {
+ // No preceding sibling nodes, right?
+ return 0;
+ }
+ var i = 0;
+ while (node != node.parentNode.childNodes[i]) {
+ i++;
+ }
+ return i;
+}
+
+/**
+ * extractContents() implementation, following the spec. If an exception is
+ * supposed to be thrown, will return a string with the name (e.g.,
+ * "HIERARCHY_REQUEST_ERR") instead of a document fragment. It might also
+ * return an arbitrary human-readable string if a condition is hit that implies
+ * a spec bug.
+ */
+function myExtractContents(range) {
+ // "Let frag be a new DocumentFragment whose ownerDocument is the same as
+ // the ownerDocument of the context object's start node."
+ var ownerDoc = range.startContainer.nodeType == Node.DOCUMENT_NODE
+ ? range.startContainer
+ : range.startContainer.ownerDocument;
+ var frag = ownerDoc.createDocumentFragment();
+
+ // "If the context object's start and end are the same, abort this method,
+ // returning frag."
+ if (range.startContainer == range.endContainer
+ && range.startOffset == range.endOffset) {
+ return frag;
+ }
+
+ // "Let original start node, original start offset, original end node, and
+ // original end offset be the context object's start and end nodes and
+ // offsets, respectively."
+ var originalStartNode = range.startContainer;
+ var originalStartOffset = range.startOffset;
+ var originalEndNode = range.endContainer;
+ var originalEndOffset = range.endOffset;
+
+ // "If original start node is original end node, and they are a Text,
+ // ProcessingInstruction, or Comment node:"
+ if (range.startContainer == range.endContainer
+ && (range.startContainer.nodeType == Node.TEXT_NODE
+ || range.startContainer.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+ || range.startContainer.nodeType == Node.COMMENT_NODE)) {
+ // "Let clone be the result of calling cloneNode(false) on original
+ // start node."
+ var clone = originalStartNode.cloneNode(false);
+
+ // "Set the data of clone to the result of calling
+ // substringData(original start offset, original end offset − original
+ // start offset) on original start node."
+ clone.data = originalStartNode.substringData(originalStartOffset,
+ originalEndOffset - originalStartOffset);
+
+ // "Append clone as the last child of frag."
+ frag.appendChild(clone);
+
+ // "Call deleteData(original start offset, original end offset −
+ // original start offset) on original start node."
+ originalStartNode.deleteData(originalStartOffset,
+ originalEndOffset - originalStartOffset);
+
+ // "Abort this method, returning frag."
+ return frag;
+ }
+
+ // "Let common ancestor equal original start node."
+ var commonAncestor = originalStartNode;
+
+ // "While common ancestor is not an ancestor container of original end
+ // node, set common ancestor to its own parent."
+ while (!isAncestorContainer(commonAncestor, originalEndNode)) {
+ commonAncestor = commonAncestor.parentNode;
+ }
+
+ // "If original start node is an ancestor container of original end node,
+ // let first partially contained child be null."
+ var firstPartiallyContainedChild;
+ if (isAncestorContainer(originalStartNode, originalEndNode)) {
+ firstPartiallyContainedChild = null;
+ // "Otherwise, let first partially contained child be the first child of
+ // common ancestor that is partially contained in the context object."
+ } else {
+ for (var i = 0; i < commonAncestor.childNodes.length; i++) {
+ if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
+ firstPartiallyContainedChild = commonAncestor.childNodes[i];
+ break;
+ }
+ }
+ if (!firstPartiallyContainedChild) {
+ throw "Spec bug: no first partially contained child!";
+ }
+ }
+
+ // "If original end node is an ancestor container of original start node,
+ // let last partially contained child be null."
+ var lastPartiallyContainedChild;
+ if (isAncestorContainer(originalEndNode, originalStartNode)) {
+ lastPartiallyContainedChild = null;
+ // "Otherwise, let last partially contained child be the last child of
+ // common ancestor that is partially contained in the context object."
+ } else {
+ for (var i = commonAncestor.childNodes.length - 1; i >= 0; i--) {
+ if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
+ lastPartiallyContainedChild = commonAncestor.childNodes[i];
+ break;
+ }
+ }
+ if (!lastPartiallyContainedChild) {
+ throw "Spec bug: no last partially contained child!";
+ }
+ }
+
+ // "Let contained children be a list of all children of common ancestor
+ // that are contained in the context object, in tree order."
+ //
+ // "If any member of contained children is a DocumentType, raise a
+ // HIERARCHY_REQUEST_ERR exception and abort these steps."
+ var containedChildren = [];
+ for (var i = 0; i < commonAncestor.childNodes.length; i++) {
+ if (isContained(commonAncestor.childNodes[i], range)) {
+ if (commonAncestor.childNodes[i].nodeType
+ == Node.DOCUMENT_TYPE_NODE) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+ containedChildren.push(commonAncestor.childNodes[i]);
+ }
+ }
+
+ // "If original start node is an ancestor container of original end node,
+ // set new node to original start node and new offset to original start
+ // offset."
+ var newNode, newOffset;
+ if (isAncestorContainer(originalStartNode, originalEndNode)) {
+ newNode = originalStartNode;
+ newOffset = originalStartOffset;
+ // "Otherwise:"
+ } else {
+ // "Let reference node equal original start node."
+ var referenceNode = originalStartNode;
+
+ // "While reference node's parent is not null and is not an ancestor
+ // container of original end node, set reference node to its parent."
+ while (referenceNode.parentNode
+ && !isAncestorContainer(referenceNode.parentNode, originalEndNode)) {
+ referenceNode = referenceNode.parentNode;
+ }
+
+ // "Set new node to the parent of reference node, and new offset to one
+ // plus the index of reference node."
+ newNode = referenceNode.parentNode;
+ newOffset = 1 + indexOf(referenceNode);
+ }
+
+ // "If first partially contained child is a Text, ProcessingInstruction, or
+ // Comment node:"
+ if (firstPartiallyContainedChild
+ && (firstPartiallyContainedChild.nodeType == Node.TEXT_NODE
+ || firstPartiallyContainedChild.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+ || firstPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
+ // "Let clone be the result of calling cloneNode(false) on original
+ // start node."
+ var clone = originalStartNode.cloneNode(false);
+
+ // "Set the data of clone to the result of calling substringData() on
+ // original start node, with original start offset as the first
+ // argument and (length of original start node − original start offset)
+ // as the second."
+ clone.data = originalStartNode.substringData(originalStartOffset,
+ nodeLength(originalStartNode) - originalStartOffset);
+
+ // "Append clone as the last child of frag."
+ frag.appendChild(clone);
+
+ // "Call deleteData() on original start node, with original start
+ // offset as the first argument and (length of original start node −
+ // original start offset) as the second."
+ originalStartNode.deleteData(originalStartOffset,
+ nodeLength(originalStartNode) - originalStartOffset);
+ // "Otherwise, if first partially contained child is not null:"
+ } else if (firstPartiallyContainedChild) {
+ // "Let clone be the result of calling cloneNode(false) on first
+ // partially contained child."
+ var clone = firstPartiallyContainedChild.cloneNode(false);
+
+ // "Append clone as the last child of frag."
+ frag.appendChild(clone);
+
+ // "Let subrange be a new Range whose start is (original start node,
+ // original start offset) and whose end is (first partially contained
+ // child, length of first partially contained child)."
+ var subrange = ownerDoc.createRange();
+ subrange.setStart(originalStartNode, originalStartOffset);
+ subrange.setEnd(firstPartiallyContainedChild,
+ nodeLength(firstPartiallyContainedChild));
+
+ // "Let subfrag be the result of calling extractContents() on
+ // subrange."
+ var subfrag = myExtractContents(subrange);
+
+ // "For each child of subfrag, in order, append that child to clone as
+ // its last child."
+ for (var i = 0; i < subfrag.childNodes.length; i++) {
+ clone.appendChild(subfrag.childNodes[i]);
+ }
+ }
+
+ // "For each contained child in contained children, append contained child
+ // as the last child of frag."
+ for (var i = 0; i < containedChildren.length; i++) {
+ frag.appendChild(containedChildren[i]);
+ }
+
+ // "If last partially contained child is a Text, ProcessingInstruction, or
+ // Comment node:"
+ if (lastPartiallyContainedChild
+ && (lastPartiallyContainedChild.nodeType == Node.TEXT_NODE
+ || lastPartiallyContainedChild.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+ || lastPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
+ // "Let clone be the result of calling cloneNode(false) on original
+ // end node."
+ var clone = originalEndNode.cloneNode(false);
+
+ // "Set the data of clone to the result of calling substringData(0,
+ // original end offset) on original end node."
+ clone.data = originalEndNode.substringData(0, originalEndOffset);
+
+ // "Append clone as the last child of frag."
+ frag.appendChild(clone);
+
+ // "Call deleteData(0, original end offset) on original end node."
+ originalEndNode.deleteData(0, originalEndOffset);
+ // "Otherwise, if last partially contained child is not null:"
+ } else if (lastPartiallyContainedChild) {
+ // "Let clone be the result of calling cloneNode(false) on last
+ // partially contained child."
+ var clone = lastPartiallyContainedChild.cloneNode(false);
+
+ // "Append clone as the last child of frag."
+ frag.appendChild(clone);
+
+ // "Let subrange be a new Range whose start is (last partially
+ // contained child, 0) and whose end is (original end node, original
+ // end offset)."
+ var subrange = ownerDoc.createRange();
+ subrange.setStart(lastPartiallyContainedChild, 0);
+ subrange.setEnd(originalEndNode, originalEndOffset);
+
+ // "Let subfrag be the result of calling extractContents() on
+ // subrange."
+ var subfrag = myExtractContents(subrange);
+
+ // "For each child of subfrag, in order, append that child to clone as
+ // its last child."
+ for (var i = 0; i < subfrag.childNodes.length; i++) {
+ clone.appendChild(subfrag.childNodes[i]);
+ }
+ }
+
+ // "Set the context object's start and end to (new node, new offset)."
+ range.setStart(newNode, newOffset);
+ range.setEnd(newNode, newOffset);
+
+ // "Return frag."
+ return frag;
+}
+
+/**
+ * insertNode() implementation, following the spec. If an exception is meant
+ * to be thrown, will return a string with the expected exception name, for
+ * instance "HIERARCHY_REQUEST_ERR".
+ */
+function myInsertNode(range, node) {
+ // "If range's start node is a ProcessingInstruction or Comment node, or is
+ // a Text node whose parent is null, or is node, throw an
+ // "HierarchyRequestError" exception and terminate these steps."
+ if (range.startContainer.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+ || range.startContainer.nodeType == Node.COMMENT_NODE
+ || (range.startContainer.nodeType == Node.TEXT_NODE
+ && !range.startContainer.parentNode)
+ || range.startContainer == node) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+
+ // "Let referenceNode be null."
+ var referenceNode = null;
+
+ // "If range's start node is a Text node, set referenceNode to that Text node."
+ if (range.startContainer.nodeType == Node.TEXT_NODE) {
+ referenceNode = range.startContainer;
+
+ // "Otherwise, set referenceNode to the child of start node whose index is
+ // start offset, and null if there is no such child."
+ } else {
+ if (range.startOffset < range.startContainer.childNodes.length) {
+ referenceNode = range.startContainer.childNodes[range.startOffset];
+ } else {
+ referenceNode = null;
+ }
+ }
+
+ // "Let parent be range's start node if referenceNode is null, and
+ // referenceNode's parent otherwise."
+ var parent_ = referenceNode === null ? range.startContainer :
+ referenceNode.parentNode;
+
+ // "Ensure pre-insertion validity of node into parent before
+ // referenceNode."
+ var error = ensurePreInsertionValidity(node, parent_, referenceNode);
+ if (error) {
+ return error;
+ }
+
+ // "If range's start node is a Text node, set referenceNode to the result
+ // of splitting it with offset range's start offset."
+ if (range.startContainer.nodeType == Node.TEXT_NODE) {
+ referenceNode = range.startContainer.splitText(range.startOffset);
+ }
+
+ // "If node is referenceNode, set referenceNode to its next sibling."
+ if (node == referenceNode) {
+ referenceNode = referenceNode.nextSibling;
+ }
+
+ // "If node's parent is not null, remove node from its parent."
+ if (node.parentNode) {
+ node.parentNode.removeChild(node);
+ }
+
+ // "Let newOffset be parent's length if referenceNode is null, and
+ // referenceNode's index otherwise."
+ var newOffset = referenceNode === null ? nodeLength(parent_) :
+ indexOf(referenceNode);
+
+ // "Increase newOffset by node's length if node is a DocumentFragment node,
+ // and one otherwise."
+ newOffset += node.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
+ nodeLength(node) : 1;
+
+ // "Pre-insert node into parent before referenceNode."
+ parent_.insertBefore(node, referenceNode);
+
+ // "If range's start and end are the same, set range's end to (parent,
+ // newOffset)."
+ if (range.startContainer == range.endContainer
+ && range.startOffset == range.endOffset) {
+ range.setEnd(parent_, newOffset);
+ }
+}
+
+// To make filter() calls more readable
+function isElement(node) {
+ return node.nodeType == Node.ELEMENT_NODE;
+}
+
+function isText(node) {
+ return node.nodeType == Node.TEXT_NODE;
+}
+
+function isDoctype(node) {
+ return node.nodeType == Node.DOCUMENT_TYPE_NODE;
+}
+
+function ensurePreInsertionValidity(node, parent_, child) {
+ // "If parent is not a Document, DocumentFragment, or Element node, throw a
+ // HierarchyRequestError."
+ if (parent_.nodeType != Node.DOCUMENT_NODE
+ && parent_.nodeType != Node.DOCUMENT_FRAGMENT_NODE
+ && parent_.nodeType != Node.ELEMENT_NODE) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+
+ // "If node is a host-including inclusive ancestor of parent, throw a
+ // HierarchyRequestError."
+ //
+ // XXX Does not account for host
+ if (isInclusiveAncestor(node, parent_)) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+
+ // "If child is not null and its parent is not parent, throw a NotFoundError
+ // exception."
+ if (child && child.parentNode != parent_) {
+ return "NOT_FOUND_ERR";
+ }
+
+ // "If node is not a DocumentFragment, DocumentType, Element, Text,
+ // ProcessingInstruction, or Comment node, throw a HierarchyRequestError."
+ if (node.nodeType != Node.DOCUMENT_FRAGMENT_NODE
+ && node.nodeType != Node.DOCUMENT_TYPE_NODE
+ && node.nodeType != Node.ELEMENT_NODE
+ && node.nodeType != Node.TEXT_NODE
+ && node.nodeType != Node.PROCESSING_INSTRUCTION_NODE
+ && node.nodeType != Node.COMMENT_NODE) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+
+ // "If either node is a Text node and parent is a document, or node is a
+ // doctype and parent is not a document, throw a HierarchyRequestError."
+ if ((node.nodeType == Node.TEXT_NODE
+ && parent_.nodeType == Node.DOCUMENT_NODE)
+ || (node.nodeType == Node.DOCUMENT_TYPE_NODE
+ && parent_.nodeType != Node.DOCUMENT_NODE)) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+
+ // "If parent is a document, and any of the statements below, switched on
+ // node, are true, throw a HierarchyRequestError."
+ if (parent_.nodeType == Node.DOCUMENT_NODE) {
+ switch (node.nodeType) {
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ // "If node has more than one element child or has a Text node
+ // child. Otherwise, if node has one element child and either
+ // parent has an element child, child is a doctype, or child is not
+ // null and a doctype is following child."
+ if ([].filter.call(node.childNodes, isElement).length > 1) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+
+ if ([].some.call(node.childNodes, isText)) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+
+ if ([].filter.call(node.childNodes, isElement).length == 1) {
+ if ([].some.call(parent_.childNodes, isElement)) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+
+ if (child && child.nodeType == Node.DOCUMENT_TYPE_NODE) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+
+ if (child && [].slice.call(parent_.childNodes, indexOf(child) + 1)
+ .filter(isDoctype)) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+ }
+ break;
+
+ case Node.ELEMENT_NODE:
+ // "parent has an element child, child is a doctype, or child is
+ // not null and a doctype is following child."
+ if ([].some.call(parent_.childNodes, isElement)) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+
+ if (child.nodeType == Node.DOCUMENT_TYPE_NODE) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+
+ if (child && [].slice.call(parent_.childNodes, indexOf(child) + 1)
+ .filter(isDoctype)) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+ break;
+
+ case Node.DOCUMENT_TYPE_NODE:
+ // "parent has a doctype child, an element is preceding child, or
+ // child is null and parent has an element child."
+ if ([].some.call(parent_.childNodes, isDoctype)) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+
+ if (child && [].slice.call(parent_.childNodes, 0, indexOf(child))
+ .some(isElement)) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+
+ if (!child && [].some.call(parent_.childNodes, isElement)) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+ break;
+ }
+ }
+}
+
+/**
+ * Asserts that two nodes are equal, in the sense of isEqualNode(). If they
+ * aren't, tries to print a relatively informative reason why not. TODO: Move
+ * this to testharness.js?
+ */
+function assertNodesEqual(actual, expected, msg) {
+ if (!actual.isEqualNode(expected)) {
+ msg = "Actual and expected mismatch for " + msg + ". ";
+
+ while (actual && expected) {
+ assert_true(actual.nodeType === expected.nodeType
+ && actual.nodeName === expected.nodeName
+ && actual.nodeValue === expected.nodeValue,
+ "First differing node: expected " + format_value(expected)
+ + ", got " + format_value(actual) + " [" + msg + "]");
+ actual = nextNode(actual);
+ expected = nextNode(expected);
+ }
+
+ assert_unreached("DOMs were not equal but we couldn't figure out why");
+ }
+}
+
+/**
+ * Given a DOMException, return the name (e.g., "HIERARCHY_REQUEST_ERR").
+ */
+function getDomExceptionName(e) {
+ var ret = null;
+ for (var prop in e) {
+ if (/^[A-Z_]+_ERR$/.test(prop) && e[prop] == e.code) {
+ return prop;
+ }
+ }
+
+ throw "Exception seems to not be a DOMException? " + e;
+}
+
+/**
+ * Given an array of endpoint data [start container, start offset, end
+ * container, end offset], returns a Range with those endpoints.
+ */
+function rangeFromEndpoints(endpoints) {
+ // If we just use document instead of the ownerDocument of endpoints[0],
+ // WebKit will throw on setStart/setEnd. This is a WebKit bug, but it's in
+ // range, not selection, so we don't want to fail anything for it.
+ var range = ownerDocument(endpoints[0]).createRange();
+ range.setStart(endpoints[0], endpoints[1]);
+ range.setEnd(endpoints[2], endpoints[3]);
+ return range;
+}
diff --git a/testing/web-platform/tests/dom/constants.js b/testing/web-platform/tests/dom/constants.js
new file mode 100644
index 0000000000..397df96fbc
--- /dev/null
+++ b/testing/web-platform/tests/dom/constants.js
@@ -0,0 +1,11 @@
+function testConstants(objects, constants, msg) {
+ objects.forEach(function(arr) {
+ var o = arr[0], desc = arr[1];
+ test(function() {
+ constants.forEach(function(d) {
+ assert_true(d[0] in o, "Object " + o + " doesn't have " + d[0])
+ assert_equals(o[d[0]], d[1], "Object " + o + " value for " + d[0] + " is wrong")
+ })
+ }, "Constants for " + msg + " on " + desc + ".")
+ })
+}
diff --git a/testing/web-platform/tests/dom/eventPathRemoved.html b/testing/web-platform/tests/dom/eventPathRemoved.html
new file mode 100644
index 0000000000..1ed9f88753
--- /dev/null
+++ b/testing/web-platform/tests/dom/eventPathRemoved.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Event.path must be removed</title>
+<!-- Note that this duplicates a test in historical.html for the purposes of
+ using in the Interop-2022 metric -->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(() => {
+ const name = "path";
+ assert_false(name in Event.prototype)
+ assert_equals(Event.prototype[name], undefined)
+ assert_false(name in new Event("test"))
+ assert_equals((new Event("test"))[name], undefined)
+ }, "Event.prototype should not have property named 'path'")
+</script>
diff --git a/testing/web-platform/tests/dom/events/AddEventListenerOptions-once.any.js b/testing/web-platform/tests/dom/events/AddEventListenerOptions-once.any.js
new file mode 100644
index 0000000000..b4edd4345c
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/AddEventListenerOptions-once.any.js
@@ -0,0 +1,96 @@
+// META: title=AddEventListenerOptions.once
+
+"use strict";
+
+test(function() {
+ var invoked_once = false;
+ var invoked_normal = false;
+ function handler_once() {
+ invoked_once = true;
+ }
+ function handler_normal() {
+ invoked_normal = true;
+ }
+
+ const et = new EventTarget();
+ et.addEventListener('test', handler_once, {once: true});
+ et.addEventListener('test', handler_normal);
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_once, true, "Once handler should be invoked");
+ assert_equals(invoked_normal, true, "Normal handler should be invoked");
+
+ invoked_once = false;
+ invoked_normal = false;
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_once, false, "Once handler shouldn't be invoked again");
+ assert_equals(invoked_normal, true, "Normal handler should be invoked again");
+ et.removeEventListener('test', handler_normal);
+}, "Once listener should be invoked only once");
+
+test(function() {
+ const et = new EventTarget();
+ var invoked_count = 0;
+ function handler() {
+ invoked_count++;
+ if (invoked_count == 1)
+ et.dispatchEvent(new Event('test'));
+ }
+ et.addEventListener('test', handler, {once: true});
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_count, 1, "Once handler should only be invoked once");
+
+ invoked_count = 0;
+ function handler2() {
+ invoked_count++;
+ if (invoked_count == 1)
+ et.addEventListener('test', handler2, {once: true});
+ if (invoked_count <= 2)
+ et.dispatchEvent(new Event('test'));
+ }
+ et.addEventListener('test', handler2, {once: true});
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_count, 2, "Once handler should only be invoked once after each adding");
+}, "Once listener should be invoked only once even if the event is nested");
+
+test(function() {
+ var invoked_count = 0;
+ function handler() {
+ invoked_count++;
+ }
+
+ const et = new EventTarget();
+
+ et.addEventListener('test', handler, {once: true});
+ et.addEventListener('test', handler);
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_count, 1, "The handler should only be added once");
+
+ invoked_count = 0;
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_count, 0, "The handler was added as a once listener");
+
+ invoked_count = 0;
+ et.addEventListener('test', handler, {once: true});
+ et.removeEventListener('test', handler);
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_count, 0, "The handler should have been removed");
+}, "Once listener should be added / removed like normal listeners");
+
+test(function() {
+ const et = new EventTarget();
+
+ var invoked_count = 0;
+
+ for (let n = 4; n > 0; n--) {
+ et.addEventListener('test', (e) => {
+ invoked_count++;
+ e.stopImmediatePropagation();
+ }, {once: true});
+ }
+
+ for (let n = 4; n > 0; n--) {
+ et.dispatchEvent(new Event('test'));
+ }
+
+ assert_equals(invoked_count, 4, "The listeners should be invoked");
+}, "Multiple once listeners should be invoked even if the stopImmediatePropagation is set");
diff --git a/testing/web-platform/tests/dom/events/AddEventListenerOptions-passive.any.js b/testing/web-platform/tests/dom/events/AddEventListenerOptions-passive.any.js
new file mode 100644
index 0000000000..8e59cf5b37
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/AddEventListenerOptions-passive.any.js
@@ -0,0 +1,134 @@
+// META: title=AddEventListenerOptions.passive
+
+test(function() {
+ var supportsPassive = false;
+ var query_options = {
+ get passive() {
+ supportsPassive = true;
+ return false;
+ },
+ get dummy() {
+ assert_unreached("dummy value getter invoked");
+ return false;
+ }
+ };
+
+ const et = new EventTarget();
+ et.addEventListener('test_event', null, query_options);
+ assert_true(supportsPassive, "addEventListener doesn't support the passive option");
+
+ supportsPassive = false;
+ et.removeEventListener('test_event', null, query_options);
+ assert_false(supportsPassive, "removeEventListener supports the passive option when it should not");
+}, "Supports passive option on addEventListener only");
+
+function testPassiveValue(optionsValue, expectedDefaultPrevented, existingEventTarget) {
+ var defaultPrevented = undefined;
+ var handler = function handler(e) {
+ assert_false(e.defaultPrevented, "Event prematurely marked defaultPrevented");
+ e.preventDefault();
+ defaultPrevented = e.defaultPrevented;
+ }
+ const et = existingEventTarget || new EventTarget();
+ et.addEventListener('test', handler, optionsValue);
+ var uncanceled = et.dispatchEvent(new Event('test', {bubbles: true, cancelable: true}));
+
+ assert_equals(defaultPrevented, expectedDefaultPrevented, "Incorrect defaultPrevented for options: " + JSON.stringify(optionsValue));
+ assert_equals(uncanceled, !expectedDefaultPrevented, "Incorrect return value from dispatchEvent");
+
+ et.removeEventListener('test', handler, optionsValue);
+}
+
+test(function() {
+ testPassiveValue(undefined, true);
+ testPassiveValue({}, true);
+ testPassiveValue({passive: false}, true);
+ testPassiveValue({passive: true}, false);
+ testPassiveValue({passive: 0}, true);
+ testPassiveValue({passive: 1}, false);
+}, "preventDefault should be ignored if-and-only-if the passive option is true");
+
+function testPassiveValueOnReturnValue(test, optionsValue, expectedDefaultPrevented) {
+ var defaultPrevented = undefined;
+ var handler = test.step_func(e => {
+ assert_false(e.defaultPrevented, "Event prematurely marked defaultPrevented");
+ e.returnValue = false;
+ defaultPrevented = e.defaultPrevented;
+ });
+ const et = new EventTarget();
+ et.addEventListener('test', handler, optionsValue);
+ var uncanceled = et.dispatchEvent(new Event('test', {bubbles: true, cancelable: true}));
+
+ assert_equals(defaultPrevented, expectedDefaultPrevented, "Incorrect defaultPrevented for options: " + JSON.stringify(optionsValue));
+ assert_equals(uncanceled, !expectedDefaultPrevented, "Incorrect return value from dispatchEvent");
+
+ et.removeEventListener('test', handler, optionsValue);
+}
+
+async_test(t => {
+ testPassiveValueOnReturnValue(t, undefined, true);
+ testPassiveValueOnReturnValue(t, {}, true);
+ testPassiveValueOnReturnValue(t, {passive: false}, true);
+ testPassiveValueOnReturnValue(t, {passive: true}, false);
+ testPassiveValueOnReturnValue(t, {passive: 0}, true);
+ testPassiveValueOnReturnValue(t, {passive: 1}, false);
+ t.done();
+}, "returnValue should be ignored if-and-only-if the passive option is true");
+
+function testPassiveWithOtherHandlers(optionsValue, expectedDefaultPrevented) {
+ var handlerInvoked1 = false;
+ var dummyHandler1 = function() {
+ handlerInvoked1 = true;
+ };
+ var handlerInvoked2 = false;
+ var dummyHandler2 = function() {
+ handlerInvoked2 = true;
+ };
+
+ const et = new EventTarget();
+ et.addEventListener('test', dummyHandler1, {passive:true});
+ et.addEventListener('test', dummyHandler2);
+
+ testPassiveValue(optionsValue, expectedDefaultPrevented, et);
+
+ assert_true(handlerInvoked1, "Extra passive handler not invoked");
+ assert_true(handlerInvoked2, "Extra non-passive handler not invoked");
+
+ et.removeEventListener('test', dummyHandler1);
+ et.removeEventListener('test', dummyHandler2);
+}
+
+test(function() {
+ testPassiveWithOtherHandlers({}, true);
+ testPassiveWithOtherHandlers({passive: false}, true);
+ testPassiveWithOtherHandlers({passive: true}, false);
+}, "passive behavior of one listener should be unaffected by the presence of other listeners");
+
+function testOptionEquivalence(optionValue1, optionValue2, expectedEquality) {
+ var invocationCount = 0;
+ var handler = function handler(e) {
+ invocationCount++;
+ }
+ const et = new EventTarget();
+ et.addEventListener('test', handler, optionValue1);
+ et.addEventListener('test', handler, optionValue2);
+ et.dispatchEvent(new Event('test', {bubbles: true}));
+ assert_equals(invocationCount, expectedEquality ? 1 : 2, "equivalence of options " +
+ JSON.stringify(optionValue1) + " and " + JSON.stringify(optionValue2));
+ et.removeEventListener('test', handler, optionValue1);
+ et.removeEventListener('test', handler, optionValue2);
+}
+
+test(function() {
+ // Sanity check options that should be treated as distinct handlers
+ testOptionEquivalence({capture:true}, {capture:false, passive:false}, false);
+ testOptionEquivalence({capture:true}, {passive:true}, false);
+
+ // Option values that should be treated as equivalent
+ testOptionEquivalence({}, {passive:false}, true);
+ testOptionEquivalence({passive:true}, {passive:false}, true);
+ testOptionEquivalence(undefined, {passive:true}, true);
+ testOptionEquivalence({capture: true, passive: false}, {capture: true, passive: true}, true);
+
+}, "Equivalence of option values");
+
diff --git a/testing/web-platform/tests/dom/events/AddEventListenerOptions-signal.any.js b/testing/web-platform/tests/dom/events/AddEventListenerOptions-signal.any.js
new file mode 100644
index 0000000000..e6a3426159
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/AddEventListenerOptions-signal.any.js
@@ -0,0 +1,143 @@
+'use strict';
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', handler, { signal: controller.signal });
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 1, "Adding a signal still adds a listener");
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 2, "The listener was not added with the once flag");
+ controller.abort();
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 2, "Aborting on the controller removes the listener");
+ et.addEventListener('test', handler, { signal: controller.signal });
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 2, "Passing an aborted signal never adds the handler");
+}, "Passing an AbortSignal to addEventListener options should allow removing a listener");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', handler, { signal: controller.signal });
+ et.removeEventListener('test', handler);
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Passing an AbortSignal to addEventListener does not prevent removeEventListener");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', handler, { signal: controller.signal, once: true });
+ controller.abort();
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Passing an AbortSignal to addEventListener works with the once flag");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', handler, { signal: controller.signal, once: true });
+ et.removeEventListener('test', handler);
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Removing a once listener works with a passed signal");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('first', handler, { signal: controller.signal, once: true });
+ et.addEventListener('second', handler, { signal: controller.signal, once: true });
+ controller.abort();
+ et.dispatchEvent(new Event('first'));
+ et.dispatchEvent(new Event('second'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Passing an AbortSignal to multiple listeners");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', handler, { signal: controller.signal, capture: true });
+ controller.abort();
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Passing an AbortSignal to addEventListener works with the capture flag");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', () => {
+ controller.abort();
+ }, { signal: controller.signal });
+ et.addEventListener('test', handler, { signal: controller.signal });
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Aborting from a listener does not call future listeners");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', () => {
+ et.addEventListener('test', handler, { signal: controller.signal });
+ controller.abort();
+ }, { signal: controller.signal });
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Adding then aborting a listener in another listener does not call it");
+
+test(function() {
+ const et = new EventTarget();
+ const ac = new AbortController();
+ let count = 0;
+ et.addEventListener('foo', () => {
+ et.addEventListener('foo', () => {
+ count++;
+ if (count > 5) ac.abort();
+ et.dispatchEvent(new Event('foo'));
+ }, { signal: ac.signal });
+ et.dispatchEvent(new Event('foo'));
+ }, { once: true });
+ et.dispatchEvent(new Event('foo'));
+}, "Aborting from a nested listener should remove it");
+
+test(function() {
+ const et = new EventTarget();
+ assert_throws_js(TypeError, () => { et.addEventListener("foo", () => {}, { signal: null }); });
+}, "Passing null as the signal should throw");
+
+test(function() {
+ const et = new EventTarget();
+ assert_throws_js(TypeError, () => { et.addEventListener("foo", null, { signal: null }); });
+}, "Passing null as the signal should throw (listener is also null)");
diff --git a/testing/web-platform/tests/dom/events/Body-FrameSet-Event-Handlers.html b/testing/web-platform/tests/dom/events/Body-FrameSet-Event-Handlers.html
new file mode 100644
index 0000000000..3a891158d5
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Body-FrameSet-Event-Handlers.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<html>
+<title>HTMLBodyElement and HTMLFrameSetElement Event Handler Tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+function getObject(interface) {
+ switch(interface) {
+ case "Element":
+ var e = document.createElementNS("http://example.com/", "example");
+ assert_true(e instanceof Element);
+ assert_false(e instanceof HTMLElement);
+ assert_false(e instanceof SVGElement);
+ return e;
+ case "HTMLElement":
+ var e = document.createElement("html");
+ assert_true(e instanceof HTMLElement);
+ return e;
+ case "HTMLBodyElement":
+ var e = document.createElement("body");
+ assert_true(e instanceof HTMLBodyElement);
+ return e;
+ case "HTMLFormElement":
+ var e = document.createElement("form");
+ assert_true(e instanceof HTMLFormElement);
+ return e;
+ case "HTMLFrameSetElement":
+ var e = document.createElement("frameset");
+ assert_true(e instanceof HTMLFrameSetElement);
+ return e;
+ case "SVGElement":
+ var e = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+ assert_true(e instanceof SVGElement);
+ return e;
+ case "Document":
+ assert_true(document instanceof Document);
+ return document;
+ case "Window":
+ assert_true(window instanceof Window);
+ return window;
+ }
+ assert_unreached();
+}
+
+function testSet(interface, attribute) {
+ test(function() {
+ var object = getObject(interface);
+ function nop() {}
+ assert_equals(object[attribute], null, "Initially null");
+ object[attribute] = nop;
+ assert_equals(object[attribute], nop, "Return same function");
+ object[attribute] = "";
+ assert_equals(object[attribute], null, "Return null after setting string");
+ object[attribute] = null;
+ assert_equals(object[attribute], null, "Finally null");
+ }, "Set " + interface + "." + attribute);
+}
+
+function testReflect(interface, attribute) {
+ test(function() {
+ var element = getObject(interface);
+ assert_false(element.hasAttribute(attribute), "Initially missing");
+ element.setAttribute(attribute, "return");
+ assert_equals(element.getAttribute(attribute), "return", "Return same string");
+ assert_equals(typeof element[attribute], "function", "Convert to function");
+ element.removeAttribute(attribute);
+ }, "Reflect " + interface + "." + attribute);
+}
+
+function testForwardToWindow(interface, attribute) {
+ test(function() {
+ var element = getObject(interface);
+ window[attribute] = null;
+ element.setAttribute(attribute, "return");
+ assert_equals(typeof window[attribute], "function", "Convert to function");
+ assert_equals(window[attribute], element[attribute], "Forward content attribute");
+ function nop() {}
+ element[attribute] = nop;
+ assert_equals(window[attribute], nop, "Forward IDL attribute");
+ window[attribute] = null;
+ }, "Forward " + interface + "." + attribute + " to Window");
+}
+
+// Object.propertyIsEnumerable cannot be used because it doesn't
+// work with properties inherited through the prototype chain.
+function getEnumerable(interface) {
+ var enumerable = {};
+ for (var attribute in getObject(interface)) {
+ enumerable[attribute] = true;
+ }
+ return enumerable;
+}
+
+var enumerableCache = {};
+function testEnumerate(interface, attribute) {
+ if (!(interface in enumerableCache)) {
+ enumerableCache[interface] = getEnumerable(interface);
+ }
+ test(function() {
+ assert_true(enumerableCache[interface][attribute]);
+ }, "Enumerate " + interface + "." + attribute);
+}
+
+[
+ "onblur",
+ "onerror",
+ "onfocus",
+ "onload",
+ "onscroll",
+ "onresize"
+].forEach(function(attribute) {
+ testSet("HTMLBodyElement", attribute);
+ testEnumerate("HTMLBodyElement", attribute);
+ testReflect("HTMLBodyElement", attribute);
+ testForwardToWindow("HTMLBodyElement", attribute);
+ testSet("HTMLFrameSetElement", attribute);
+ testEnumerate("HTMLFrameSetElement", attribute);
+ testReflect("HTMLFrameSetElement", attribute);
+ testForwardToWindow("HTMLFrameSetElement", attribute);
+});
+</script>
+</html>
diff --git a/testing/web-platform/tests/dom/events/CustomEvent.html b/testing/web-platform/tests/dom/events/CustomEvent.html
new file mode 100644
index 0000000000..87050943f9
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/CustomEvent.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<title>CustomEvent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var type = "foo";
+
+ var target = document.createElement("div");
+ target.addEventListener(type, this.step_func(function(evt) {
+ assert_equals(evt.type, type);
+ }), true);
+
+ var fooEvent = document.createEvent("CustomEvent");
+ fooEvent.initEvent(type, true, true);
+ target.dispatchEvent(fooEvent);
+}, "CustomEvent dispatching.");
+
+test(function() {
+ var e = document.createEvent("CustomEvent");
+ assert_throws_js(TypeError, function() {
+ e.initCustomEvent();
+ });
+}, "First parameter to initCustomEvent should be mandatory.");
+
+test(function() {
+ var e = document.createEvent("CustomEvent");
+ e.initCustomEvent("foo");
+ assert_equals(e.type, "foo", "type");
+ assert_false(e.bubbles, "bubbles");
+ assert_false(e.cancelable, "cancelable");
+ assert_equals(e.detail, null, "detail");
+}, "initCustomEvent's default parameter values.");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-cancelBubble.html b/testing/web-platform/tests/dom/events/Event-cancelBubble.html
new file mode 100644
index 0000000000..d8d2d7239d
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-cancelBubble.html
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Event.cancelBubble</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://dom.spec.whatwg.org/#dom-event-cancelbubble">
+ <meta name="flags" content="dom">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id="outer">
+ <div id="middle">
+ <div id="inner"></div>
+ </div>
+ </div>
+ <script>
+test(function () {
+ // See https://dom.spec.whatwg.org/#stop-propagation-flag
+ var e = document.createEvent('Event');
+ assert_false(e.cancelBubble, "cancelBubble must be false after event creation.");
+}, "cancelBubble must be false when an event is initially created.");
+
+test(function () {
+ // See https://dom.spec.whatwg.org/#concept-event-initialize
+
+ // Event which bubbles.
+ var one = document.createEvent('Event');
+ one.cancelBubble = true;
+ one.initEvent('foo', true/*bubbles*/, false/*cancelable*/);
+ assert_false(one.cancelBubble, "initEvent() must set cancelBubble to false. [bubbles=true]");
+ // Re-initialization.
+ one.cancelBubble = true;
+ one.initEvent('foo', true/*bubbles*/, false/*cancelable*/);
+ assert_false(one.cancelBubble, "2nd initEvent() call must set cancelBubble to false. [bubbles=true]");
+
+ // Event which doesn't bubble.
+ var two = document.createEvent('Event');
+ two.cancelBubble = true;
+ two.initEvent('foo', false/*bubbles*/, false/*cancelable*/);
+ assert_false(two.cancelBubble, "initEvent() must set cancelBubble to false. [bubbles=false]");
+ // Re-initialization.
+ two.cancelBubble = true;
+ two.initEvent('foo', false/*bubbles*/, false/*cancelable*/);
+ assert_false(two.cancelBubble, "2nd initEvent() call must set cancelBubble to false. [bubbles=false]");
+}, "Initializing an event must set cancelBubble to false.");
+
+test(function () {
+ // See https://dom.spec.whatwg.org/#dom-event-stoppropagation
+ var e = document.createEvent('Event');
+ e.stopPropagation();
+ assert_true(e.cancelBubble, "stopPropagation() must set cancelBubble to true.");
+}, "stopPropagation() must set cancelBubble to true.");
+
+test(function () {
+ // See https://dom.spec.whatwg.org/#dom-event-stopimmediatepropagation
+ var e = document.createEvent('Event');
+ e.stopImmediatePropagation();
+ assert_true(e.cancelBubble, "stopImmediatePropagation() must set cancelBubble to true.");
+}, "stopImmediatePropagation() must set cancelBubble to true.");
+
+test(function () {
+ var one = document.createEvent('Event');
+ one.stopPropagation();
+ one.cancelBubble = false;
+ assert_true(one.cancelBubble, "cancelBubble must still be true after attempting to set it to false.");
+}, "Event.cancelBubble=false must have no effect.");
+
+test(function (t) {
+ var outer = document.getElementById('outer');
+ var middle = document.getElementById('middle');
+ var inner = document.getElementById('inner');
+
+ outer.addEventListener('barbaz', t.step_func(function () {
+ assert_unreached("Setting Event.cancelBubble=false after setting Event.cancelBubble=true should have no effect.");
+ }), false/*useCapture*/);
+
+ middle.addEventListener('barbaz', function (e) {
+ e.cancelBubble = true;// Stop propagation.
+ e.cancelBubble = false;// Should be a no-op.
+ }, false/*useCapture*/);
+
+ var barbazEvent = document.createEvent('Event');
+ barbazEvent.initEvent('barbaz', true/*bubbles*/, false/*cancelable*/);
+ inner.dispatchEvent(barbazEvent);
+}, "Event.cancelBubble=false must have no effect during event propagation.");
+
+test(function () {
+ // See https://dom.spec.whatwg.org/#concept-event-dispatch
+ // "14. Unset event’s [...] stop propagation flag,"
+ var e = document.createEvent('Event');
+ e.initEvent('foobar', true/*bubbles*/, true/*cancelable*/);
+ document.body.addEventListener('foobar', function listener(e) {
+ e.stopPropagation();
+ });
+ document.body.dispatchEvent(e);
+ assert_false(e.cancelBubble, "cancelBubble must be false after an event has been dispatched.");
+}, "cancelBubble must be false after an event has been dispatched.");
+
+test(function (t) {
+ var outer = document.getElementById('outer');
+ var middle = document.getElementById('middle');
+ var inner = document.getElementById('inner');
+
+ var propagationStopper = function (e) {
+ e.cancelBubble = true;
+ };
+
+ // Bubble phase
+ middle.addEventListener('bar', propagationStopper, false/*useCapture*/);
+ outer.addEventListener('bar', t.step_func(function listenerOne() {
+ assert_unreached("Setting cancelBubble=true should stop the event from bubbling further.");
+ }), false/*useCapture*/);
+
+ var barEvent = document.createEvent('Event');
+ barEvent.initEvent('bar', true/*bubbles*/, false/*cancelable*/);
+ inner.dispatchEvent(barEvent);
+
+ // Capture phase
+ outer.addEventListener('qux', propagationStopper, true/*useCapture*/);
+ middle.addEventListener('qux', t.step_func(function listenerTwo() {
+ assert_unreached("Setting cancelBubble=true should stop the event from propagating further, including during the Capture Phase.");
+ }), true/*useCapture*/);
+
+ var quxEvent = document.createEvent('Event');
+ quxEvent.initEvent('qux', false/*bubbles*/, false/*cancelable*/);
+ inner.dispatchEvent(quxEvent);
+}, "Event.cancelBubble=true must set the stop propagation flag.");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/events/Event-constants.html b/testing/web-platform/tests/dom/events/Event-constants.html
new file mode 100644
index 0000000000..635e9894d9
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-constants.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<title>Event constants</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../constants.js"></script>
+<div id="log"></div>
+<script>
+var objects;
+setup(function() {
+ objects = [
+ [Event, "Event interface object"],
+ [Event.prototype, "Event prototype object"],
+ [document.createEvent("Event"), "Event object"],
+ [document.createEvent("CustomEvent"), "CustomEvent object"]
+ ]
+})
+testConstants(objects, [
+ ["NONE", 0],
+ ["CAPTURING_PHASE", 1],
+ ["AT_TARGET", 2],
+ ["BUBBLING_PHASE", 3]
+], "eventPhase")
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-constructors.any.js b/testing/web-platform/tests/dom/events/Event-constructors.any.js
new file mode 100644
index 0000000000..faa623ea92
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-constructors.any.js
@@ -0,0 +1,120 @@
+// META: title=Event constructors
+
+test(function() {
+ assert_throws_js(
+ TypeError,
+ () => Event(""),
+ "Calling Event constructor without 'new' must throw")
+})
+test(function() {
+ assert_throws_js(TypeError, function() {
+ new Event()
+ })
+})
+test(function() {
+ var test_error = { name: "test" }
+ assert_throws_exactly(test_error, function() {
+ new Event({ toString: function() { throw test_error; } })
+ })
+})
+test(function() {
+ var ev = new Event("")
+ assert_equals(ev.type, "")
+ assert_equals(ev.target, null)
+ assert_equals(ev.srcElement, null)
+ assert_equals(ev.currentTarget, null)
+ assert_equals(ev.eventPhase, Event.NONE)
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, false)
+ assert_equals(ev.defaultPrevented, false)
+ assert_equals(ev.returnValue, true)
+ assert_equals(ev.isTrusted, false)
+ assert_true(ev.timeStamp > 0)
+ assert_true("initEvent" in ev)
+})
+test(function() {
+ var ev = new Event("test")
+ assert_equals(ev.type, "test")
+ assert_equals(ev.target, null)
+ assert_equals(ev.srcElement, null)
+ assert_equals(ev.currentTarget, null)
+ assert_equals(ev.eventPhase, Event.NONE)
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, false)
+ assert_equals(ev.defaultPrevented, false)
+ assert_equals(ev.returnValue, true)
+ assert_equals(ev.isTrusted, false)
+ assert_true(ev.timeStamp > 0)
+ assert_true("initEvent" in ev)
+})
+test(function() {
+ assert_throws_js(TypeError, function() { Event("test") },
+ 'Calling Event constructor without "new" must throw');
+})
+test(function() {
+ var ev = new Event("I am an event", { bubbles: true, cancelable: false})
+ assert_equals(ev.type, "I am an event")
+ assert_equals(ev.bubbles, true)
+ assert_equals(ev.cancelable, false)
+})
+test(function() {
+ var ev = new Event("@", { bubblesIGNORED: true, cancelable: true})
+ assert_equals(ev.type, "@")
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, true)
+})
+test(function() {
+ var ev = new Event("@", { "bubbles\0IGNORED": true, cancelable: true})
+ assert_equals(ev.type, "@")
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, true)
+})
+test(function() {
+ var ev = new Event("Xx", { cancelable: true})
+ assert_equals(ev.type, "Xx")
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, true)
+})
+test(function() {
+ var ev = new Event("Xx", {})
+ assert_equals(ev.type, "Xx")
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, false)
+})
+test(function() {
+ var ev = new Event("Xx", {bubbles: true, cancelable: false, sweet: "x"})
+ assert_equals(ev.type, "Xx")
+ assert_equals(ev.bubbles, true)
+ assert_equals(ev.cancelable, false)
+ assert_equals(ev.sweet, undefined)
+})
+test(function() {
+ var called = []
+ var ev = new Event("Xx", {
+ get cancelable() {
+ called.push("cancelable")
+ return false
+ },
+ get bubbles() {
+ called.push("bubbles")
+ return true;
+ },
+ get sweet() {
+ called.push("sweet")
+ return "x"
+ }
+ })
+ assert_array_equals(called, ["bubbles", "cancelable"])
+ assert_equals(ev.type, "Xx")
+ assert_equals(ev.bubbles, true)
+ assert_equals(ev.cancelable, false)
+ assert_equals(ev.sweet, undefined)
+})
+test(function() {
+ var ev = new CustomEvent("$", {detail: 54, sweet: "x", sweet2: "x", cancelable:true})
+ assert_equals(ev.type, "$")
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, true)
+ assert_equals(ev.sweet, undefined)
+ assert_equals(ev.detail, 54)
+})
diff --git a/testing/web-platform/tests/dom/events/Event-defaultPrevented-after-dispatch.html b/testing/web-platform/tests/dom/events/Event-defaultPrevented-after-dispatch.html
new file mode 100644
index 0000000000..8fef005eb5
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-defaultPrevented-after-dispatch.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Event.defaultPrevented is not reset after dispatchEvent()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id=log></div>
+<input id="target" type="hidden" value=""/>
+<script>
+test(function() {
+ var EVENT = "foo";
+ var TARGET = document.getElementById("target");
+ var evt = document.createEvent("Event");
+ evt.initEvent(EVENT, true, true);
+
+ TARGET.addEventListener(EVENT, this.step_func(function(e) {
+ e.preventDefault();
+ assert_true(e.defaultPrevented, "during dispatch");
+ }), true);
+ TARGET.dispatchEvent(evt);
+
+ assert_true(evt.defaultPrevented, "after dispatch");
+ assert_equals(evt.target, TARGET);
+ assert_equals(evt.srcElement, TARGET);
+}, "Default prevention via preventDefault");
+
+test(function() {
+ var EVENT = "foo";
+ var TARGET = document.getElementById("target");
+ var evt = document.createEvent("Event");
+ evt.initEvent(EVENT, true, true);
+
+ TARGET.addEventListener(EVENT, this.step_func(function(e) {
+ e.returnValue = false;
+ assert_true(e.defaultPrevented, "during dispatch");
+ }), true);
+ TARGET.dispatchEvent(evt);
+
+ assert_true(evt.defaultPrevented, "after dispatch");
+ assert_equals(evt.target, TARGET);
+ assert_equals(evt.srcElement, TARGET);
+}, "Default prevention via returnValue");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-defaultPrevented.html b/testing/web-platform/tests/dom/events/Event-defaultPrevented.html
new file mode 100644
index 0000000000..2548fa3e06
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-defaultPrevented.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<title>Event.defaultPrevented</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var ev;
+test(function() {
+ ev = document.createEvent("Event");
+ assert_equals(ev.defaultPrevented, false, "defaultPrevented");
+}, "When an event is created, defaultPrevented should be initialized to false.");
+test(function() {
+ ev.initEvent("foo", true, false);
+ assert_equals(ev.bubbles, true, "bubbles");
+ assert_equals(ev.cancelable, false, "cancelable");
+ assert_equals(ev.defaultPrevented, false, "defaultPrevented");
+}, "initEvent should work correctly (not cancelable).");
+test(function() {
+ assert_equals(ev.cancelable, false, "cancelable (before)");
+ ev.preventDefault();
+ assert_equals(ev.cancelable, false, "cancelable (after)");
+ assert_equals(ev.defaultPrevented, false, "defaultPrevented");
+}, "preventDefault() should not change defaultPrevented if cancelable is false.");
+test(function() {
+ assert_equals(ev.cancelable, false, "cancelable (before)");
+ ev.returnValue = false;
+ assert_equals(ev.cancelable, false, "cancelable (after)");
+ assert_equals(ev.defaultPrevented, false, "defaultPrevented");
+}, "returnValue should not change defaultPrevented if cancelable is false.");
+test(function() {
+ ev.initEvent("foo", true, true);
+ assert_equals(ev.bubbles, true, "bubbles");
+ assert_equals(ev.cancelable, true, "cancelable");
+ assert_equals(ev.defaultPrevented, false, "defaultPrevented");
+}, "initEvent should work correctly (cancelable).");
+test(function() {
+ assert_equals(ev.cancelable, true, "cancelable (before)");
+ ev.preventDefault();
+ assert_equals(ev.cancelable, true, "cancelable (after)");
+ assert_equals(ev.defaultPrevented, true, "defaultPrevented");
+}, "preventDefault() should change defaultPrevented if cancelable is true.");
+test(function() {
+ ev.initEvent("foo", true, true);
+ assert_equals(ev.cancelable, true, "cancelable (before)");
+ ev.returnValue = false;
+ assert_equals(ev.cancelable, true, "cancelable (after)");
+ assert_equals(ev.defaultPrevented, true, "defaultPrevented");
+}, "returnValue should change defaultPrevented if cancelable is true.");
+test(function() {
+ ev.initEvent("foo", true, true);
+ assert_equals(ev.bubbles, true, "bubbles");
+ assert_equals(ev.cancelable, true, "cancelable");
+ assert_equals(ev.defaultPrevented, false, "defaultPrevented");
+}, "initEvent should unset defaultPrevented.");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-bubble-canceled.html b/testing/web-platform/tests/dom/events/Event-dispatch-bubble-canceled.html
new file mode 100644
index 0000000000..20f398f66f
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-bubble-canceled.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Setting cancelBubble=true prior to dispatchEvent()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+
+<table id="table" border="1" style="display: none">
+ <tbody id="table-body">
+ <tr id="table-row">
+ <td id="table-cell">Shady Grove</td>
+ <td>Aeolian</td>
+ </tr>
+ <tr id="parent">
+ <td id="target">Over the river, Charlie</td>
+ <td>Dorian</td>
+ </tr>
+ </tbody>
+</table>
+
+<script>
+test(function() {
+ var event = "foo";
+ var target = document.getElementById("target");
+ var parent = document.getElementById("parent");
+ var tbody = document.getElementById("table-body");
+ var table = document.getElementById("table");
+ var body = document.body;
+ var html = document.documentElement;
+ var current_targets = [window, document, html, body, table, tbody, parent, target];
+ var expected_targets = [];
+ var actual_targets = [];
+ var expected_phases = [];
+ var actual_phases = [];
+
+ var test_event = function(evt) {
+ actual_targets.push(evt.currentTarget);
+ actual_phases.push(evt.eventPhase);
+ };
+
+ for (var i = 0; i < current_targets.length; ++i) {
+ current_targets[i].addEventListener(event, test_event, true);
+ current_targets[i].addEventListener(event, test_event, false);
+ }
+
+ var evt = document.createEvent("Event");
+ evt.initEvent(event, true, true);
+ evt.cancelBubble = true;
+ target.dispatchEvent(evt);
+
+ assert_array_equals(actual_targets, expected_targets, "actual_targets");
+ assert_array_equals(actual_phases, expected_phases, "actual_phases");
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-bubbles-false.html b/testing/web-platform/tests/dom/events/Event-dispatch-bubbles-false.html
new file mode 100644
index 0000000000..0f43cb0275
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-bubbles-false.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title> Event.bubbles attribute is set to false </title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-event-initevent">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-dispatch">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<table id="table" border="1" style="display: none">
+ <tbody id="table-body">
+ <tr id="table-row">
+ <td id="table-cell">Shady Grove</td>
+ <td>Aeolian</td>
+ </tr>
+ <tr id="parent">
+ <td id="target">Over the river, Charlie</td>
+ <td>Dorian</td>
+ </tr>
+ </tbody>
+</table>
+<script>
+function targetsForDocumentChain(document) {
+ return [
+ document,
+ document.documentElement,
+ document.getElementsByTagName("body")[0],
+ document.getElementById("table"),
+ document.getElementById("table-body"),
+ document.getElementById("parent")
+ ];
+}
+
+function testChain(document, targetParents, phases, event_type) {
+ var target = document.getElementById("target");
+ var targets = targetParents.concat(target);
+ var expected_targets = targets.concat(target);
+
+ var actual_targets = [], actual_phases = [];
+ var test_event = function(evt) {
+ actual_targets.push(evt.currentTarget);
+ actual_phases.push(evt.eventPhase);
+ }
+
+ for (var i = 0; i < targets.length; i++) {
+ targets[i].addEventListener(event_type, test_event, true);
+ targets[i].addEventListener(event_type, test_event, false);
+ }
+
+ var evt = document.createEvent("Event");
+ evt.initEvent(event_type, false, true);
+
+ target.dispatchEvent(evt);
+
+ assert_array_equals(actual_targets, expected_targets, "targets");
+ assert_array_equals(actual_phases, phases, "phases");
+}
+
+var phasesForDocumentChain = [
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.AT_TARGET,
+ Event.AT_TARGET,
+];
+
+test(function () {
+ var chainWithWindow = [window].concat(targetsForDocumentChain(document));
+ testChain(
+ document, chainWithWindow, [Event.CAPTURING_PHASE].concat(phasesForDocumentChain), "click");
+}, "In window.document with click event");
+
+test(function () {
+ testChain(document, targetsForDocumentChain(document), phasesForDocumentChain, "load");
+}, "In window.document with load event")
+
+test(function () {
+ var documentClone = document.cloneNode(true);
+ testChain(
+ documentClone, targetsForDocumentChain(documentClone), phasesForDocumentChain, "click");
+}, "In window.document.cloneNode(true)");
+
+test(function () {
+ var newDocument = new Document();
+ newDocument.appendChild(document.documentElement.cloneNode(true));
+ testChain(
+ newDocument, targetsForDocumentChain(newDocument), phasesForDocumentChain, "click");
+}, "In new Document()");
+
+test(function () {
+ var HTMLDocument = document.implementation.createHTMLDocument();
+ HTMLDocument.body.appendChild(document.getElementById("table").cloneNode(true));
+ testChain(
+ HTMLDocument, targetsForDocumentChain(HTMLDocument), phasesForDocumentChain, "click");
+}, "In DOMImplementation.createHTMLDocument()");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-bubbles-true.html b/testing/web-platform/tests/dom/events/Event-dispatch-bubbles-true.html
new file mode 100644
index 0000000000..b23605a1eb
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-bubbles-true.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title> Event.bubbles attribute is set to false </title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-event-initevent">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-dispatch">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<table id="table" border="1" style="display: none">
+ <tbody id="table-body">
+ <tr id="table-row">
+ <td id="table-cell">Shady Grove</td>
+ <td>Aeolian</td>
+ </tr>
+ <tr id="parent">
+ <td id="target">Over the river, Charlie</td>
+ <td>Dorian</td>
+ </tr>
+ </tbody>
+</table>
+<script>
+function concatReverse(a) {
+ return a.concat(a.map(function(x) { return x }).reverse());
+}
+
+function targetsForDocumentChain(document) {
+ return [
+ document,
+ document.documentElement,
+ document.getElementsByTagName("body")[0],
+ document.getElementById("table"),
+ document.getElementById("table-body"),
+ document.getElementById("parent")
+ ];
+}
+
+function testChain(document, targetParents, phases, event_type) {
+ var target = document.getElementById("target");
+ var targets = targetParents.concat(target);
+ var expected_targets = concatReverse(targets);
+
+ var actual_targets = [], actual_phases = [];
+ var test_event = function(evt) {
+ actual_targets.push(evt.currentTarget);
+ actual_phases.push(evt.eventPhase);
+ }
+
+ for (var i = 0; i < targets.length; i++) {
+ targets[i].addEventListener(event_type, test_event, true);
+ targets[i].addEventListener(event_type, test_event, false);
+ }
+
+ var evt = document.createEvent("Event");
+ evt.initEvent(event_type, true, true);
+
+ target.dispatchEvent(evt);
+
+ assert_array_equals(actual_targets, expected_targets, "targets");
+ assert_array_equals(actual_phases, phases, "phases");
+}
+
+var phasesForDocumentChain = [
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.AT_TARGET,
+ Event.AT_TARGET,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+];
+
+test(function () {
+ var chainWithWindow = [window].concat(targetsForDocumentChain(document));
+ var phases = [Event.CAPTURING_PHASE].concat(phasesForDocumentChain, Event.BUBBLING_PHASE);
+ testChain(document, chainWithWindow, phases, "click");
+}, "In window.document with click event");
+
+test(function () {
+ testChain(document, targetsForDocumentChain(document), phasesForDocumentChain, "load");
+}, "In window.document with load event")
+
+test(function () {
+ var documentClone = document.cloneNode(true);
+ testChain(
+ documentClone, targetsForDocumentChain(documentClone), phasesForDocumentChain, "click");
+}, "In window.document.cloneNode(true)");
+
+test(function () {
+ var newDocument = new Document();
+ newDocument.appendChild(document.documentElement.cloneNode(true));
+ testChain(
+ newDocument, targetsForDocumentChain(newDocument), phasesForDocumentChain, "click");
+}, "In new Document()");
+
+test(function () {
+ var HTMLDocument = document.implementation.createHTMLDocument();
+ HTMLDocument.body.appendChild(document.getElementById("table").cloneNode(true));
+ testChain(
+ HTMLDocument, targetsForDocumentChain(HTMLDocument), phasesForDocumentChain, "click");
+}, "In DOMImplementation.createHTMLDocument()");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-click.html b/testing/web-platform/tests/dom/events/Event-dispatch-click.html
new file mode 100644
index 0000000000..ab4a24a5ad
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-click.html
@@ -0,0 +1,425 @@
+<!doctype html>
+<title>Synthetic click event "magic"</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<div id=dump style=display:none></div>
+<script>
+var dump = document.getElementById("dump")
+
+async_test(function(t) {
+ var input = document.createElement("input")
+ input.type = "checkbox"
+ dump.appendChild(input)
+ input.onclick = t.step_func_done(function() {
+ assert_true(input.checked)
+ })
+ input.click()
+}, "basic with click()")
+
+async_test(function(t) {
+ var input = document.createElement("input")
+ input.type = "checkbox"
+ dump.appendChild(input)
+ input.onclick = t.step_func_done(function() {
+ assert_true(input.checked)
+ })
+ input.dispatchEvent(new MouseEvent("click", {bubbles:true})) // equivalent to the above
+}, "basic with dispatchEvent()")
+
+async_test(function(t) {
+ var input = document.createElement("input")
+ input.type = "checkbox"
+ dump.appendChild(input)
+ input.onclick = t.step_func_done(function() {
+ assert_false(input.checked)
+ })
+ input.dispatchEvent(new Event("click", {bubbles:true})) // no MouseEvent
+}, "basic with wrong event class")
+
+async_test(function(t) {
+ var input = document.createElement("input")
+ input.type = "checkbox"
+ dump.appendChild(input)
+ var child = input.appendChild(new Text("does not matter"))
+ child.dispatchEvent(new MouseEvent("click")) // does not bubble
+ assert_false(input.checked)
+ t.done()
+}, "look at parents only when event bubbles")
+
+async_test(function(t) {
+ var input = document.createElement("input")
+ input.type = "checkbox"
+ dump.appendChild(input)
+ input.onclick = t.step_func_done(function() {
+ assert_true(input.checked)
+ })
+ var child = input.appendChild(new Text("does not matter"))
+ child.dispatchEvent(new MouseEvent("click", {bubbles:true}))
+}, "look at parents when event bubbles")
+
+async_test(function(t) {
+ var input = document.createElement("input")
+ input.type = "checkbox"
+ dump.appendChild(input)
+ input.onclick = t.step_func(function() {
+ assert_false(input.checked, "input pre-click must not be triggered")
+ })
+ var child = input.appendChild(document.createElement("input"))
+ child.type = "checkbox"
+ child.onclick = t.step_func(function() {
+ assert_true(child.checked, "child pre-click must be triggered")
+ })
+ child.dispatchEvent(new MouseEvent("click", {bubbles:true}))
+ t.done()
+}, "pick the first with activation behavior <input type=checkbox>")
+
+async_test(function(t) { // as above with <a>
+ window.hrefComplete = t.step_func(function(a) {
+ assert_equals(a, 'child');
+ t.done();
+ });
+ var link = document.createElement("a")
+ link.href = "javascript:hrefComplete('link')" // must not be triggered
+ dump.appendChild(link)
+ var child = link.appendChild(document.createElement("a"))
+ child.href = "javascript:hrefComplete('child')"
+ child.dispatchEvent(new MouseEvent("click", {bubbles:true}))
+}, "pick the first with activation behavior <a href>")
+
+async_test(function(t) {
+ var input = document.createElement("input")
+ input.type = "radio"
+ dump.appendChild(input)
+ input.onclick = t.step_func(function() {
+ assert_false(input.checked, "input pre-click must not be triggered")
+ })
+ var child = input.appendChild(document.createElement("input"))
+ child.type = "radio"
+ child.onclick = t.step_func(function() {
+ assert_true(child.checked, "child pre-click must be triggered")
+ })
+ child.dispatchEvent(new MouseEvent("click", {bubbles:true}))
+ t.done()
+}, "pick the first with activation behavior <input type=radio>")
+
+async_test(function(t) {
+ var input = document.createElement("input")
+ input.type = "checkbox"
+ dump.appendChild(input)
+ var clickEvent = new MouseEvent("click")
+ input.onchange = t.step_func_done(function() {
+ assert_false(clickEvent.defaultPrevented)
+ assert_true(clickEvent.returnValue)
+ assert_equals(clickEvent.eventPhase, 0)
+ assert_equals(clickEvent.currentTarget, null)
+ assert_equals(clickEvent.target, input)
+ assert_equals(clickEvent.srcElement, input)
+ assert_equals(clickEvent.composedPath().length, 0)
+ })
+ input.dispatchEvent(clickEvent)
+}, "event state during post-click handling")
+
+async_test(function(t) {
+ var input = document.createElement("input")
+ input.type = "checkbox"
+ dump.appendChild(input)
+ var clickEvent = new MouseEvent("click")
+ var finalTarget = document.createElement("doesnotmatter")
+ finalTarget.onclick = t.step_func_done(function() {
+ assert_equals(clickEvent.target, finalTarget)
+ assert_equals(clickEvent.srcElement, finalTarget)
+ })
+ input.onchange = t.step_func(function() {
+ finalTarget.dispatchEvent(clickEvent)
+ })
+ input.dispatchEvent(clickEvent)
+}, "redispatch during post-click handling")
+
+async_test(function(t) {
+ var input = document.createElement("input")
+ input.type = "checkbox"
+ dump.appendChild(input)
+ var child = input.appendChild(document.createElement("input"))
+ child.type = "checkbox"
+ child.disabled = true
+ child.click()
+ assert_false(input.checked)
+ assert_false(child.checked)
+ t.done()
+}, "disabled checkbox still has activation behavior")
+
+async_test(function(t) {
+ var state = "start"
+
+ var form = document.createElement("form")
+ form.onsubmit = t.step_func(() => {
+ if(state == "start" || state == "checkbox") {
+ state = "failure"
+ } else if(state == "form") {
+ state = "done"
+ }
+ return false
+ })
+ dump.appendChild(form)
+ var button = form.appendChild(document.createElement("button"))
+ button.type = "submit"
+ var checkbox = button.appendChild(document.createElement("input"))
+ checkbox.type = "checkbox"
+ checkbox.onclick = t.step_func(() => {
+ if(state == "start") {
+ assert_unreached()
+ } else if(state == "checkbox") {
+ assert_true(checkbox.checked)
+ }
+ })
+ checkbox.disabled = true
+ checkbox.click()
+ assert_equals(state, "start")
+
+ state = "checkbox"
+ checkbox.disabled = false
+ checkbox.click()
+ assert_equals(state, "checkbox")
+
+ state = "form"
+ button.click()
+ assert_equals(state, "done")
+
+ t.done()
+}, "disabled checkbox still has activation behavior, part 2")
+
+async_test(function(t) {
+ var state = "start"
+
+ var form = document.createElement("form")
+ form.onsubmit = t.step_func(() => {
+ if(state == "start" || state == "radio") {
+ state = "failure"
+ } else if(state == "form") {
+ state = "done"
+ }
+ return false
+ })
+ dump.appendChild(form)
+ var button = form.appendChild(document.createElement("button"))
+ button.type = "submit"
+ var radio = button.appendChild(document.createElement("input"))
+ radio.type = "radio"
+ radio.onclick = t.step_func(() => {
+ if(state == "start") {
+ assert_unreached()
+ } else if(state == "radio") {
+ assert_true(radio.checked)
+ }
+ })
+ radio.disabled = true
+ radio.click()
+ assert_equals(state, "start")
+
+ state = "radio"
+ radio.disabled = false
+ radio.click()
+ assert_equals(state, "radio")
+
+ state = "form"
+ button.click()
+ assert_equals(state, "done")
+
+ t.done()
+}, "disabled radio still has activation behavior")
+
+async_test(function(t) {
+ var input = document.createElement("input")
+ input.type = "checkbox"
+ input.onclick = t.step_func_done(function() {
+ assert_true(input.checked)
+ })
+ input.click()
+}, "disconnected checkbox should be checked")
+
+async_test(function(t) {
+ var input = document.createElement("input")
+ input.type = "radio"
+ input.onclick = t.step_func_done(function() {
+ assert_true(input.checked)
+ })
+ input.click()
+}, "disconnected radio should be checked")
+
+async_test(t => {
+ const input = document.createElement('input');
+ input.type = 'checkbox';
+ input.onclick = t.step_func_done(() => {
+ assert_true(input.checked);
+ });
+ input.dispatchEvent(new MouseEvent('click'));
+}, `disconnected checkbox should be checked from dispatchEvent(new MouseEvent('click'))`);
+
+async_test(t => {
+ const input = document.createElement('input');
+ input.type = 'radio';
+ input.onclick = t.step_func_done(() => {
+ assert_true(input.checked);
+ });
+ input.dispatchEvent(new MouseEvent('click'));
+}, `disconnected radio should be checked from dispatchEvent(new MouseEvent('click'))`);
+
+test(() => {
+ const input = document.createElement("input");
+ input.type = "checkbox";
+ input.disabled = true;
+ input.dispatchEvent(new MouseEvent("click"));
+ assert_true(input.checked);
+}, `disabled checkbox should be checked from dispatchEvent(new MouseEvent("click"))`);
+
+test(() => {
+ const input = document.createElement("input");
+ input.type = "radio";
+ input.disabled = true;
+ input.dispatchEvent(new MouseEvent("click"));
+ assert_true(input.checked);
+}, `disabled radio should be checked from dispatchEvent(new MouseEvent("click"))`);
+
+async_test(t => {
+ const input = document.createElement("input");
+ input.type = "checkbox";
+ input.disabled = true;
+ input.onclick = t.step_func_done();
+ input.dispatchEvent(new MouseEvent("click"));
+}, `disabled checkbox should fire onclick`);
+
+async_test(t => {
+ const input = document.createElement("input");
+ input.type = "radio";
+ input.disabled = true;
+ input.onclick = t.step_func_done();
+ input.dispatchEvent(new MouseEvent("click"));
+}, `disabled radio should fire onclick`);
+
+async_test(t => {
+ const input = document.createElement("input");
+ input.type = "checkbox";
+ input.disabled = true;
+ input.onclick = t.step_func(ev => {
+ assert_true(input.checked);
+ ev.preventDefault();
+ queueMicrotask(t.step_func_done(() => {
+ assert_false(input.checked);
+ }));
+ });
+ input.dispatchEvent(new MouseEvent("click", { cancelable: true }));
+}, `disabled checkbox should get legacy-canceled-activation behavior`);
+
+async_test(t => {
+ const input = document.createElement("input");
+ input.type = "radio";
+ input.disabled = true;
+ input.onclick = t.step_func(ev => {
+ assert_true(input.checked);
+ ev.preventDefault();
+ queueMicrotask(t.step_func_done(() => {
+ assert_false(input.checked);
+ }));
+ });
+ input.dispatchEvent(new MouseEvent("click", { cancelable: true }));
+}, `disabled radio should get legacy-canceled-activation behavior`);
+
+test(t => {
+ const input = document.createElement("input");
+ input.type = "checkbox";
+ input.disabled = true;
+ const ev = new MouseEvent("click", { cancelable: true });
+ ev.preventDefault();
+ input.dispatchEvent(ev);
+ assert_false(input.checked);
+}, `disabled checkbox should get legacy-canceled-activation behavior 2`);
+
+test(t => {
+ const input = document.createElement("input");
+ input.type = "radio";
+ input.disabled = true;
+ const ev = new MouseEvent("click", { cancelable: true });
+ ev.preventDefault();
+ input.dispatchEvent(ev);
+ assert_false(input.checked);
+}, `disabled radio should get legacy-canceled-activation behavior 2`);
+
+for (const type of ["checkbox", "radio"]) {
+ for (const handler of ["oninput", "onchange"]) {
+ async_test(t => {
+ const input = document.createElement("input");
+ input.type = type;
+ input.onclick = t.step_func(ev => {
+ input.disabled = true;
+ });
+ input[handler] = t.step_func(ev => {
+ assert_equals(input.checked, true);
+ t.done();
+ });
+ dump.append(input);
+ input.click();
+ }, `disabling ${type} in onclick listener shouldn't suppress ${handler}`);
+ }
+}
+
+async_test(function(t) {
+ var form = document.createElement("form")
+ var didSubmit = false
+ form.onsubmit = t.step_func(() => {
+ didSubmit = true
+ return false
+ })
+ var input = form.appendChild(document.createElement("input"))
+ input.type = "submit"
+ input.click()
+ assert_false(didSubmit)
+ t.done()
+}, "disconnected form should not submit")
+
+async_test(t => {
+ const form = document.createElement("form");
+ form.onsubmit = t.step_func(ev => {
+ ev.preventDefault();
+ assert_unreached("The form is unexpectedly submitted.");
+ });
+ dump.append(form);
+ const input = form.appendChild(document.createElement("input"));
+ input.type = "submit"
+ input.disabled = true;
+ input.dispatchEvent(new MouseEvent("click", { cancelable: true }));
+ t.done();
+}, "disabled submit button should not activate");
+
+async_test(t => {
+ const form = document.createElement("form");
+ form.onsubmit = t.step_func(ev => {
+ ev.preventDefault();
+ assert_unreached("The form is unexpectedly submitted.");
+ });
+ dump.append(form);
+ const input = form.appendChild(document.createElement("input"));
+ input.onclick = t.step_func(() => {
+ input.disabled = true;
+ });
+ input.type = "submit"
+ input.dispatchEvent(new MouseEvent("click", { cancelable: true }));
+ t.done();
+}, "submit button should not activate if the event listener disables it");
+
+async_test(t => {
+ const form = document.createElement("form");
+ form.onsubmit = t.step_func(ev => {
+ ev.preventDefault();
+ assert_unreached("The form is unexpectedly submitted.");
+ });
+ dump.append(form);
+ const input = form.appendChild(document.createElement("input"));
+ input.onclick = t.step_func(() => {
+ input.type = "submit"
+ input.disabled = true;
+ });
+ input.click();
+ t.done();
+}, "submit button that morphed from checkbox should not activate");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-click.tentative.html b/testing/web-platform/tests/dom/events/Event-dispatch-click.tentative.html
new file mode 100644
index 0000000000..cfdae55ef2
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-click.tentative.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<title>Clicks on input element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=dump style=display:none></div>
+<script>
+var dump = document.getElementById("dump")
+
+test(t => {
+ const input = document.createElement("input");
+ input.type = "checkbox";
+ input.disabled = true;
+ const label = document.createElement("label");
+ label.append(input);
+ dump.append(label);
+ label.click();
+ assert_false(input.checked);
+}, "disabled checkbox should not be checked from label click");
+
+test(t => {
+ const input = document.createElement("input");
+ input.type = "radio";
+ input.disabled = true;
+ const label = document.createElement("label");
+ label.append(input);
+ dump.append(label);
+ label.click();
+ assert_false(input.checked);
+}, "disabled radio should not be checked from label click");
+
+test(t => {
+ const input = document.createElement("input");
+ input.type = "checkbox";
+ input.disabled = true;
+ const label = document.createElement("label");
+ label.append(input);
+ dump.append(label);
+ label.dispatchEvent(new MouseEvent("click"));
+ assert_false(input.checked);
+}, "disabled checkbox should not be checked from label click by dispatchEvent");
+
+test(t => {
+ const input = document.createElement("input");
+ input.type = "radio";
+ input.disabled = true;
+ const label = document.createElement("label");
+ label.append(input);
+ dump.append(label);
+ label.dispatchEvent(new MouseEvent("click"));
+ assert_false(input.checked);
+}, "disabled radio should not be checked from label click by dispatchEvent");
+
+test(t => {
+ const checkbox = dump.appendChild(document.createElement("input"));
+ checkbox.type = "checkbox";
+ checkbox.onclick = ev => {
+ checkbox.type = "date";
+ ev.preventDefault();
+ };
+ checkbox.dispatchEvent(new MouseEvent("click", { cancelable: true }));
+ assert_false(checkbox.checked);
+}, "checkbox morphed into another type should not mutate checked state");
+
+test(t => {
+ const radio1 = dump.appendChild(document.createElement("input"));
+ const radio2 = dump.appendChild(radio1.cloneNode());
+ radio1.type = radio2.type = "radio";
+ radio1.name = radio2.name = "foo";
+ radio2.checked = true;
+ radio1.onclick = ev => {
+ radio1.type = "date";
+ ev.preventDefault();
+ };
+ radio1.dispatchEvent(new MouseEvent("click", { cancelable: true }));
+ assert_false(radio1.checked);
+ assert_true(radio2.checked);
+}, "radio morphed into another type should not steal the existing checked state");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-detached-click.html b/testing/web-platform/tests/dom/events/Event-dispatch-detached-click.html
new file mode 100644
index 0000000000..76ea3d78ba
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-detached-click.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Click event on an element not in the document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var EVENT = "click";
+ var TARGET = document.createElement("somerandomelement");
+ var t = async_test("Click event can be dispatched to an element that is not in the document.")
+ TARGET.addEventListener(EVENT, t.step_func(function(evt) {
+ assert_equals(evt.target, TARGET);
+ assert_equals(evt.srcElement, TARGET);
+ t.done();
+ }), true);
+ var e = document.createEvent("Event");
+ e.initEvent(EVENT, true, true);
+ TARGET.dispatchEvent(e);
+});
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-detached-input-and-change.html b/testing/web-platform/tests/dom/events/Event-dispatch-detached-input-and-change.html
new file mode 100644
index 0000000000..a53ae71ac2
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-detached-input-and-change.html
@@ -0,0 +1,190 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<title>input and change events for detached checkbox and radio elements</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+
+test(() => {
+ const input = document.createElement('input');
+ input.type = 'checkbox';
+
+ let inputEventFired = false;
+ input.addEventListener('input', () => inputEventFired = true);
+ let changeEventFired = false;
+ input.addEventListener('change', () => changeEventFired = true);
+ input.click();
+ assert_false(inputEventFired);
+ assert_false(changeEventFired);
+}, 'detached checkbox should not emit input or change events on click().');
+
+test(() => {
+ const input = document.createElement('input');
+ input.type = 'radio';
+
+ let inputEventFired = false;
+ input.addEventListener('input', () => inputEventFired = true);
+ let changeEventFired = false;
+ input.addEventListener('change', () => changeEventFired = true);
+ input.click();
+ assert_false(inputEventFired);
+ assert_false(changeEventFired);
+}, 'detached radio should not emit input or change events on click().');
+
+test(() => {
+ const input = document.createElement('input');
+ input.type = 'checkbox';
+
+ let inputEventFired = false;
+ input.addEventListener('input', () => inputEventFired = true);
+ let changeEventFired = false;
+ input.addEventListener('change', () => changeEventFired = true);
+ input.dispatchEvent(new MouseEvent('click'));
+ assert_false(inputEventFired);
+ assert_false(changeEventFired);
+}, `detached checkbox should not emit input or change events on dispatchEvent(new MouseEvent('click')).`);
+
+test(() => {
+ const input = document.createElement('input');
+ input.type = 'radio';
+
+ let inputEventFired = false;
+ input.addEventListener('input', () => inputEventFired = true);
+ let changeEventFired = false;
+ input.addEventListener('change', () => changeEventFired = true);
+ input.dispatchEvent(new MouseEvent('click'));
+ assert_false(inputEventFired);
+ assert_false(changeEventFired);
+}, `detached radio should not emit input or change events on dispatchEvent(new MouseEvent('click')).`);
+
+
+test(() => {
+ const input = document.createElement('input');
+ input.type = 'checkbox';
+ document.body.appendChild(input);
+
+ let inputEventFired = false;
+ input.addEventListener('input', () => inputEventFired = true);
+ let changeEventFired = false;
+ input.addEventListener('change', () => changeEventFired = true);
+ input.click();
+ assert_true(inputEventFired);
+ assert_true(changeEventFired);
+}, 'attached checkbox should emit input and change events on click().');
+
+test(() => {
+ const input = document.createElement('input');
+ input.type = 'radio';
+ document.body.appendChild(input);
+
+ let inputEventFired = false;
+ input.addEventListener('input', () => inputEventFired = true);
+ let changeEventFired = false;
+ input.addEventListener('change', () => changeEventFired = true);
+ input.click();
+ assert_true(inputEventFired);
+ assert_true(changeEventFired);
+}, 'attached radio should emit input and change events on click().');
+
+test(() => {
+ const input = document.createElement('input');
+ input.type = 'checkbox';
+ document.body.appendChild(input);
+
+ let inputEventFired = false;
+ input.addEventListener('input', () => inputEventFired = true);
+ let changeEventFired = false;
+ input.addEventListener('change', () => changeEventFired = true);
+ input.dispatchEvent(new MouseEvent('click'));
+ assert_true(inputEventFired);
+ assert_true(changeEventFired);
+}, `attached checkbox should emit input and change events on dispatchEvent(new MouseEvent('click')).`);
+
+test(() => {
+ const input = document.createElement('input');
+ input.type = 'radio';
+ document.body.appendChild(input);
+
+ let inputEventFired = false;
+ input.addEventListener('input', () => inputEventFired = true);
+ let changeEventFired = false;
+ input.addEventListener('change', () => changeEventFired = true);
+ input.dispatchEvent(new MouseEvent('click'));
+ assert_true(inputEventFired);
+ assert_true(changeEventFired);
+}, `attached radio should emit input and change events on dispatchEvent(new MouseEvent('click')).`);
+
+
+test(() => {
+ const input = document.createElement('input');
+ input.type = 'checkbox';
+ const shadowHost = document.createElement('div');
+ document.body.appendChild(shadowHost);
+ const shadowRoot = shadowHost.attachShadow({mode: 'open'});
+ shadowRoot.appendChild(input);
+
+ let inputEventFired = false;
+ input.addEventListener('input', () => inputEventFired = true);
+ let changeEventFired = false;
+ input.addEventListener('change', () => changeEventFired = true);
+ input.click();
+ assert_true(inputEventFired);
+ assert_true(changeEventFired);
+}, 'attached to shadow dom checkbox should emit input and change events on click().');
+
+test(() => {
+ const input = document.createElement('input');
+ input.type = 'radio';
+ const shadowHost = document.createElement('div');
+ document.body.appendChild(shadowHost);
+ const shadowRoot = shadowHost.attachShadow({mode: 'open'});
+ shadowRoot.appendChild(input);
+
+ let inputEventFired = false;
+ input.addEventListener('input', () => inputEventFired = true);
+ let changeEventFired = false;
+ input.addEventListener('change', () => changeEventFired = true);
+ input.click();
+ assert_true(inputEventFired);
+ assert_true(changeEventFired);
+}, 'attached to shadow dom radio should emit input and change events on click().');
+
+test(() => {
+ const input = document.createElement('input');
+ input.type = 'checkbox';
+ const shadowHost = document.createElement('div');
+ document.body.appendChild(shadowHost);
+ const shadowRoot = shadowHost.attachShadow({mode: 'open'});
+ shadowRoot.appendChild(input);
+
+ let inputEventFired = false;
+ input.addEventListener('input', () => inputEventFired = true);
+ let changeEventFired = false;
+ input.addEventListener('change', () => changeEventFired = true);
+ input.dispatchEvent(new MouseEvent('click'));
+ assert_true(inputEventFired);
+ assert_true(changeEventFired);
+}, `attached to shadow dom checkbox should emit input and change events on dispatchEvent(new MouseEvent('click')).`);
+
+test(() => {
+ const input = document.createElement('input');
+ input.type = 'radio';
+ const shadowHost = document.createElement('div');
+ document.body.appendChild(shadowHost);
+ const shadowRoot = shadowHost.attachShadow({mode: 'open'});
+ shadowRoot.appendChild(input);
+
+ let inputEventFired = false;
+ input.addEventListener('input', () => inputEventFired = true);
+ let changeEventFired = false;
+ input.addEventListener('change', () => changeEventFired = true);
+ input.dispatchEvent(new MouseEvent('click'));
+ assert_true(inputEventFired);
+ assert_true(changeEventFired);
+}, `attached to shadow dom radio should emit input and change events on dispatchEvent(new MouseEvent('click')).`);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-handlers-changed.html b/testing/web-platform/tests/dom/events/Event-dispatch-handlers-changed.html
new file mode 100644
index 0000000000..24e6fd70cb
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-handlers-changed.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title> Dispatch additional events inside an event listener </title>
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-dispatch">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+
+<table id="table" border="1" style="display: none">
+ <tbody id="table-body">
+ <tr id="table-row">
+ <td id="table-cell">Shady Grove</td>
+ <td>Aeolian</td>
+ </tr>
+ <tr id="parent">
+ <td id="target">Over the river, Charlie</td>
+ <td>Dorian</td>
+ </tr>
+ </tbody>
+</table>
+
+<script>
+test(function() {
+ var event_type = "bar";
+ var target = document.getElementById("target");
+ var parent = document.getElementById("parent");
+ var tbody = document.getElementById("table-body");
+ var table = document.getElementById("table");
+ var body = document.body;
+ var html = document.documentElement;
+ var targets = [window, document, html, body, table, tbody, parent, target];
+ var expected_targets = [
+ window,
+ document,
+ html,
+ body,
+ table,
+ tbody,
+ parent,
+ target,
+ target,
+ target, // The additional listener for target runs as we copy its listeners twice
+ parent,
+ tbody,
+ table,
+ body,
+ html,
+ document,
+ window
+ ];
+ var expected_listeners = [0,0,0,0,0,0,0,0,1,3,1,1,1,1,1,1,1];
+
+ var actual_targets = [], actual_listeners = [];
+ var test_event_function = function(i) {
+ return this.step_func(function(evt) {
+ actual_targets.push(evt.currentTarget);
+ actual_listeners.push(i);
+
+ if (evt.eventPhase != evt.BUBBLING_PHASE && evt.currentTarget.foo != 1) {
+ evt.currentTarget.removeEventListener(event_type, event_handlers[0], true);
+ evt.currentTarget.addEventListener(event_type, event_handlers[2], true);
+ evt.currentTarget.foo = 1;
+ }
+
+ if (evt.eventPhase != evt.CAPTURING_PHASE && evt.currentTarget.foo != 3) {
+ evt.currentTarget.removeEventListener(event_type, event_handlers[0], false);
+ evt.currentTarget.addEventListener(event_type, event_handlers[3], false);
+ evt.currentTarget.foo = 3;
+ }
+ });
+ }.bind(this);
+ var event_handlers = [
+ test_event_function(0),
+ test_event_function(1),
+ test_event_function(2),
+ test_event_function(3),
+ ];
+
+ for (var i = 0; i < targets.length; ++i) {
+ targets[i].addEventListener(event_type, event_handlers[0], true);
+ targets[i].addEventListener(event_type, event_handlers[1], false);
+ }
+
+ var evt = document.createEvent("Event");
+ evt.initEvent(event_type, true, true);
+ target.dispatchEvent(evt);
+
+ assert_array_equals(actual_targets, expected_targets, "actual_targets");
+ assert_array_equals(actual_listeners, expected_listeners, "actual_listeners");
+});
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-listener-order.window.js b/testing/web-platform/tests/dom/events/Event-dispatch-listener-order.window.js
new file mode 100644
index 0000000000..a01a472872
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-listener-order.window.js
@@ -0,0 +1,20 @@
+test(t => {
+ const hostParent = document.createElement("section"),
+ host = hostParent.appendChild(document.createElement("div")),
+ shadowRoot = host.attachShadow({ mode: "closed" }),
+ targetParent = shadowRoot.appendChild(document.createElement("p")),
+ target = targetParent.appendChild(document.createElement("span")),
+ path = [hostParent, host, shadowRoot, targetParent, target],
+ expected = [],
+ result = [];
+ path.forEach((node, index) => {
+ expected.splice(index, 0, "capturing " + node.nodeName);
+ expected.splice(index + 1, 0, "bubbling " + node.nodeName);
+ });
+ path.forEach(node => {
+ node.addEventListener("test", () => { result.push("bubbling " + node.nodeName) });
+ node.addEventListener("test", () => { result.push("capturing " + node.nodeName) }, true);
+ });
+ target.dispatchEvent(new CustomEvent('test', { detail: {}, bubbles: true, composed: true }));
+ assert_array_equals(result, expected);
+});
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-multiple-cancelBubble.html b/testing/web-platform/tests/dom/events/Event-dispatch-multiple-cancelBubble.html
new file mode 100644
index 0000000000..2873fd7794
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-multiple-cancelBubble.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Multiple dispatchEvent() and cancelBubble</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id=log></div>
+
+<div id="parent" style="display: none">
+ <input id="target" type="hidden" value=""/>
+</div>
+
+<script>
+test(function() {
+ var event_type = "foo";
+ var target = document.getElementById("target");
+ var parent = document.getElementById("parent");
+ var actual_result;
+ var test_event = function(evt) {
+ actual_result.push(evt.currentTarget);
+
+ if (parent == evt.currentTarget) {
+ evt.cancelBubble = true;
+ }
+ };
+
+ var evt = document.createEvent("Event");
+ evt.initEvent(event_type, true, true);
+
+ target.addEventListener(event_type, test_event, false);
+ parent.addEventListener(event_type, test_event, false);
+ document.addEventListener(event_type, test_event, false);
+ window.addEventListener(event_type, test_event, false);
+
+ actual_result = [];
+ target.dispatchEvent(evt);
+ assert_array_equals(actual_result, [target, parent]);
+
+ actual_result = [];
+ parent.dispatchEvent(evt);
+ assert_array_equals(actual_result, [parent]);
+
+ actual_result = [];
+ document.dispatchEvent(evt);
+ assert_array_equals(actual_result, [document, window]);
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-multiple-stopPropagation.html b/testing/web-platform/tests/dom/events/Event-dispatch-multiple-stopPropagation.html
new file mode 100644
index 0000000000..72644bd861
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-multiple-stopPropagation.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title> Multiple dispatchEvent() and stopPropagation() </title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id=log></div>
+
+<div id="parent" style="display: none">
+ <input id="target" type="hidden" value=""/>
+</div>
+
+<script>
+test(function() {
+ var event_type = "foo";
+ var target = document.getElementById("target");
+ var parent = document.getElementById("parent");
+ var actual_result;
+ var test_event = function(evt) {
+ actual_result.push(evt.currentTarget);
+
+ if (parent == evt.currentTarget) {
+ evt.stopPropagation();
+ }
+ };
+
+ var evt = document.createEvent("Event");
+ evt.initEvent(event_type, true, true);
+
+ target.addEventListener(event_type, test_event, false);
+ parent.addEventListener(event_type, test_event, false);
+ document.addEventListener(event_type, test_event, false);
+ window.addEventListener(event_type, test_event, false);
+
+ actual_result = [];
+ target.dispatchEvent(evt);
+ assert_array_equals(actual_result, [target, parent]);
+
+ actual_result = [];
+ parent.dispatchEvent(evt);
+ assert_array_equals(actual_result, [parent]);
+
+ actual_result = [];
+ document.dispatchEvent(evt);
+ assert_array_equals(actual_result, [document, window]);
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-omitted-capture.html b/testing/web-platform/tests/dom/events/Event-dispatch-omitted-capture.html
new file mode 100644
index 0000000000..77074d9a3e
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-omitted-capture.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>EventTarget.addEventListener: capture argument omitted</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-dispatch">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<table id="table" border="1" style="display: none">
+ <tbody id="table-body">
+ <tr id="table-row">
+ <td id="table-cell">Shady Grove</td>
+ <td>Aeolian</td>
+ </tr>
+ <tr id="parent">
+ <td id="target">Over the river, Charlie</td>
+ <td>Dorian</td>
+ </tr>
+ </tbody>
+</table>
+<script>
+test(function() {
+ var event_type = "foo";
+ var target = document.getElementById("target");
+ var targets = [
+ target,
+ document.getElementById("parent"),
+ document.getElementById("table-body"),
+ document.getElementById("table"),
+ document.body,
+ document.documentElement,
+ document,
+ window
+ ];
+ var phases = [
+ Event.AT_TARGET,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE
+ ];
+
+ var actual_targets = [], actual_phases = [];
+ var test_event = function(evt) {
+ actual_targets.push(evt.currentTarget);
+ actual_phases.push(evt.eventPhase);
+ }
+
+ for (var i = 0; i < targets.length; i++) {
+ targets[i].addEventListener(event_type, test_event);
+ }
+
+ var evt = document.createEvent("Event");
+ evt.initEvent(event_type, true, true);
+
+ target.dispatchEvent(evt);
+
+ for (var i = 0; i < targets.length; i++) {
+ targets[i].removeEventListener(event_type, test_event);
+ }
+
+ target.dispatchEvent(evt);
+
+ assert_array_equals(actual_targets, targets, "targets");
+ assert_array_equals(actual_phases, phases, "phases");
+}, "EventTarget.addEventListener with the capture argument omitted");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-on-disabled-elements.html b/testing/web-platform/tests/dom/events/Event-dispatch-on-disabled-elements.html
new file mode 100644
index 0000000000..e7d6b455bb
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-on-disabled-elements.html
@@ -0,0 +1,251 @@
+<!doctype html>
+<meta charset="utf8">
+<meta name="timeout" content="long">
+<title>Events must dispatch on disabled elements</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<style>
+ @keyframes fade {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+ }
+</style>
+<body>
+<script>
+// HTML elements that can be disabled
+const formElements = ["button", "input", "select", "textarea"];
+
+test(() => {
+ for (const localName of formElements) {
+ const elem = document.createElement(localName);
+ elem.disabled = true;
+ // pass becomes true if the event is called and it's the right type.
+ let pass = false;
+ const listener = ({ type }) => {
+ pass = type === "click";
+ };
+ elem.addEventListener("click", listener, { once: true });
+ elem.dispatchEvent(new Event("click"));
+ assert_true(
+ pass,
+ `Untrusted "click" Event didn't dispatch on ${elem.constructor.name}.`
+ );
+ }
+}, "Can dispatch untrusted 'click' Events at disabled HTML elements.");
+
+test(() => {
+ for (const localName of formElements) {
+ const elem = document.createElement(localName);
+ elem.disabled = true;
+ // pass becomes true if the event is called and it's the right type.
+ let pass = false;
+ const listener = ({ type }) => {
+ pass = type === "pass";
+ };
+ elem.addEventListener("pass", listener, { once: true });
+ elem.dispatchEvent(new Event("pass"));
+ assert_true(
+ pass,
+ `Untrusted "pass" Event didn't dispatch on ${elem.constructor.name}`
+ );
+ }
+}, "Can dispatch untrusted Events at disabled HTML elements.");
+
+test(() => {
+ for (const localName of formElements) {
+ const elem = document.createElement(localName);
+ elem.disabled = true;
+ // pass becomes true if the event is called and it's the right type.
+ let pass = false;
+ const listener = ({ type }) => {
+ pass = type === "custom-pass";
+ };
+ elem.addEventListener("custom-pass", listener, { once: true });
+ elem.dispatchEvent(new CustomEvent("custom-pass"));
+ assert_true(
+ pass,
+ `CustomEvent "custom-pass" didn't dispatch on ${elem.constructor.name}`
+ );
+ }
+}, "Can dispatch CustomEvents at disabled HTML elements.");
+
+test(() => {
+ for (const localName of formElements) {
+ const elem = document.createElement(localName);
+
+ // Element is disabled... so this click() MUST NOT fire an event.
+ elem.disabled = true;
+ let pass = true;
+ elem.onclick = e => {
+ pass = false;
+ };
+ elem.click();
+ assert_true(
+ pass,
+ `.click() must not dispatch "click" event on disabled ${
+ elem.constructor.name
+ }.`
+ );
+
+ // Element is (re)enabled... so this click() fires an event.
+ elem.disabled = false;
+ pass = false;
+ elem.onclick = e => {
+ pass = true;
+ };
+ elem.click();
+ assert_true(
+ pass,
+ `.click() must dispatch "click" event on enabled ${
+ elem.constructor.name
+ }.`
+ );
+ }
+}, "Calling click() on disabled elements must not dispatch events.");
+
+promise_test(async () => {
+ // For each form element type, set up transition event handlers.
+ for (const localName of formElements) {
+ const elem = document.createElement(localName);
+ elem.disabled = true;
+ document.body.appendChild(elem);
+ const eventPromises = [
+ "transitionrun",
+ "transitionstart",
+ "transitionend",
+ ].map(eventType => {
+ return new Promise(r => {
+ elem.addEventListener(eventType, r);
+ });
+ });
+ // Flushing style triggers transition.
+ getComputedStyle(elem).opacity;
+ elem.style.transition = "opacity .1s";
+ elem.style.opacity = 0;
+ getComputedStyle(elem).opacity;
+ // All the events fire...
+ await Promise.all(eventPromises);
+ elem.remove();
+ }
+}, "CSS Transitions transitionrun, transitionstart, transitionend events fire on disabled form elements");
+
+promise_test(async () => {
+ // For each form element type, set up transition event handlers.
+ for (const localName of formElements) {
+ const elem = document.createElement(localName);
+ elem.disabled = true;
+ document.body.appendChild(elem);
+ getComputedStyle(elem).opacity;
+ elem.style.transition = "opacity 100s";
+ // We use ontransitionstart to cancel the event.
+ elem.ontransitionstart = () => {
+ elem.style.display = "none";
+ };
+ const promiseToCancel = new Promise(r => {
+ elem.ontransitioncancel = r;
+ });
+ // Flushing style triggers the transition.
+ elem.style.opacity = 0;
+ getComputedStyle(elem).opacity;
+ await promiseToCancel;
+ // And we are done with this element.
+ elem.remove();
+ }
+}, "CSS Transitions transitioncancel event fires on disabled form elements");
+
+promise_test(async () => {
+ // For each form element type, set up transition event handlers.
+ for (const localName of formElements) {
+ const elem = document.createElement(localName);
+ document.body.appendChild(elem);
+ elem.disabled = true;
+ const animationStartPromise = new Promise(r => {
+ elem.addEventListener("animationstart", () => {
+ // Seek to the second iteration to trigger the animationiteration event
+ elem.style.animationDelay = "-100s"
+ r();
+ });
+ });
+ const animationIterationPromise = new Promise(r => {
+ elem.addEventListener("animationiteration", ()=>{
+ elem.style.animationDelay = "-200s"
+ r();
+ });
+ });
+ const animationEndPromise = new Promise(r => {
+ elem.addEventListener("animationend", r);
+ });
+ elem.style.animation = "fade 100s 2";
+ elem.classList.add("animate");
+ // All the events fire...
+ await Promise.all([
+ animationStartPromise,
+ animationIterationPromise,
+ animationEndPromise,
+ ]);
+ elem.remove();
+ }
+}, "CSS Animation animationstart, animationiteration, animationend fire on disabled form elements");
+
+promise_test(async () => {
+ // For each form element type, set up transition event handlers.
+ for (const localName of formElements) {
+ const elem = document.createElement(localName);
+ document.body.appendChild(elem);
+ elem.disabled = true;
+
+ const promiseToCancel = new Promise(r => {
+ elem.addEventListener("animationcancel", r);
+ });
+
+ elem.addEventListener("animationstart", () => {
+ // Cancel the animation by hiding it.
+ elem.style.display = "none";
+ });
+
+ // Trigger the animation
+ elem.style.animation = "fade 100s";
+ elem.classList.add("animate");
+ await promiseToCancel;
+ // And we are done with this element.
+ elem.remove();
+ }
+}, "CSS Animation's animationcancel event fires on disabled form elements");
+
+promise_test(async () => {
+ for (const localName of formElements) {
+ const elem = document.createElement(localName);
+ elem.disabled = true;
+ document.body.appendChild(elem);
+ // Element is disabled, so clicking must not fire events
+ let pass = true;
+ elem.onclick = e => {
+ pass = false;
+ };
+ // Disabled elements are not clickable.
+ await test_driver.click(elem);
+ assert_true(
+ pass,
+ `${elem.constructor.name} is disabled, so onclick must not fire.`
+ );
+ // Element is (re)enabled... so this click() will fire an event.
+ pass = false;
+ elem.disabled = false;
+ elem.onclick = () => {
+ pass = true;
+ };
+ await test_driver.click(elem);
+ assert_true(
+ pass,
+ `${elem.constructor.name} is enabled, so onclick must fire.`
+ );
+ elem.remove();
+ }
+}, "Real clicks on disabled elements must not dispatch events.");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-order-at-target.html b/testing/web-platform/tests/dom/events/Event-dispatch-order-at-target.html
new file mode 100644
index 0000000000..79673c3256
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-order-at-target.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Listeners are invoked in correct order (AT_TARGET phase)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+"use strict";
+
+test(() => {
+ const el = document.createElement("div");
+ const expectedOrder = ["capturing", "bubbling"];
+
+ let actualOrder = [];
+ el.addEventListener("click", evt => {
+ assert_equals(evt.eventPhase, Event.AT_TARGET);
+ actualOrder.push("bubbling");
+ }, false);
+ el.addEventListener("click", evt => {
+ assert_equals(evt.eventPhase, Event.AT_TARGET);
+ actualOrder.push("capturing");
+ }, true);
+
+ el.dispatchEvent(new Event("click", {bubbles: true}));
+ assert_array_equals(actualOrder, expectedOrder, "bubbles: true");
+
+ actualOrder = [];
+ el.dispatchEvent(new Event("click", {bubbles: false}));
+ assert_array_equals(actualOrder, expectedOrder, "bubbles: false");
+}, "Listeners are invoked in correct order (AT_TARGET phase)");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-order.html b/testing/web-platform/tests/dom/events/Event-dispatch-order.html
new file mode 100644
index 0000000000..ca94434595
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-order.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Event phases order</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(function() {
+ document.addEventListener('DOMContentLoaded', this.step_func_done(function() {
+ var parent = document.getElementById('parent');
+ var child = document.getElementById('child');
+
+ var order = [];
+
+ parent.addEventListener('click', this.step_func(function(){ order.push(1) }), true);
+ child.addEventListener('click', this.step_func(function(){ order.push(2) }), false);
+ parent.addEventListener('click', this.step_func(function(){ order.push(3) }), false);
+
+ child.dispatchEvent(new Event('click', {bubbles: true}));
+
+ assert_array_equals(order, [1, 2, 3]);
+ }));
+}, "Event phases order");
+</script>
+<div id="parent">
+ <div id="child"></div>
+</div>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-other-document.html b/testing/web-platform/tests/dom/events/Event-dispatch-other-document.html
new file mode 100644
index 0000000000..689b48087a
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-other-document.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<title>Custom event on an element in another document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var doc = document.implementation.createHTMLDocument("Demo");
+ var element = doc.createElement("div");
+ var called = false;
+ element.addEventListener("foo", this.step_func(function(ev) {
+ assert_false(called);
+ called = true;
+ assert_equals(ev.target, element);
+ assert_equals(ev.srcElement, element);
+ }));
+ doc.body.appendChild(element);
+
+ var event = new Event("foo");
+ element.dispatchEvent(event);
+ assert_true(called);
+});
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-propagation-stopped.html b/testing/web-platform/tests/dom/events/Event-dispatch-propagation-stopped.html
new file mode 100644
index 0000000000..889f8cfe11
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-propagation-stopped.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title> Calling stopPropagation() prior to dispatchEvent() </title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id=log></div>
+
+<table id="table" border="1" style="display: none">
+ <tbody id="table-body">
+ <tr id="table-row">
+ <td id="table-cell">Shady Grove</td>
+ <td>Aeolian</td>
+ </tr>
+ <tr id="parent">
+ <td id="target">Over the river, Charlie</td>
+ <td>Dorian</td>
+ </tr>
+ </tbody>
+</table>
+
+<script>
+test(function() {
+ var event = "foo";
+ var target = document.getElementById("target");
+ var parent = document.getElementById("parent");
+ var tbody = document.getElementById("table-body");
+ var table = document.getElementById("table");
+ var body = document.body;
+ var html = document.documentElement;
+ var current_targets = [window, document, html, body, table, tbody, parent, target];
+ var expected_targets = [];
+ var actual_targets = [];
+ var expected_phases = [];
+ var actual_phases = [];
+
+ var test_event = function(evt) {
+ actual_targets.push(evt.currentTarget);
+ actual_phases.push(evt.eventPhase);
+ };
+
+ for (var i = 0; i < current_targets.length; ++i) {
+ current_targets[i].addEventListener(event, test_event, true);
+ current_targets[i].addEventListener(event, test_event, false);
+ }
+
+ var evt = document.createEvent("Event");
+ evt.initEvent(event, true, true);
+ evt.stopPropagation();
+ target.dispatchEvent(evt);
+
+ assert_array_equals(actual_targets, expected_targets, "actual_targets");
+ assert_array_equals(actual_phases, expected_phases, "actual_phases");
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-redispatch.html b/testing/web-platform/tests/dom/events/Event-dispatch-redispatch.html
new file mode 100644
index 0000000000..cf861ca177
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-redispatch.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<meta charset=urf-8>
+<title>EventTarget#dispatchEvent(): redispatching a native event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<button>click me!</button>
+<div id=log></div>
+<script>
+var test_contentLoaded_redispatching = async_test("Redispatching DOMContentLoaded event after being dispatched");
+var test_mouseup_redispatching = async_test("Redispatching mouseup event whose default action dispatches a click event");
+var test_redispatching_of_dispatching_event = async_test("Redispatching event which is being dispatched");
+
+var buttonElement = document.querySelector("button");
+var contentLoadedEvent;
+
+var waitForLoad = new Promise(resolve => {
+ window.addEventListener("load", () => { requestAnimationFrame(resolve); }, {capture: false, once: true});
+});
+
+document.addEventListener("DOMContentLoaded", event => {
+ contentLoadedEvent = event;
+ test_redispatching_of_dispatching_event.step(() => {
+ assert_throws_dom("InvalidStateError", () => {
+ document.dispatchEvent(contentLoadedEvent);
+ }, "Trusted DOMContentLoaded event");
+ });
+}, {capture: true, once: true});
+
+window.addEventListener("load", loadEvent => {
+ let untrustedContentLoadedEvent;
+ buttonElement.addEventListener("DOMContentLoaded", event => {
+ untrustedContentLoadedEvent = event;
+ test_contentLoaded_redispatching.step(() => {
+ assert_false(untrustedContentLoadedEvent.isTrusted, "Redispatched DOMContentLoaded event shouldn't be trusted");
+ });
+ test_redispatching_of_dispatching_event.step(() => {
+ assert_throws_dom("InvalidStateError", () => {
+ document.dispatchEvent(untrustedContentLoadedEvent);
+ }, "Untrusted DOMContentLoaded event");
+ });
+ });
+
+ test_contentLoaded_redispatching.step(() => {
+ assert_true(contentLoadedEvent.isTrusted, "Received DOMContentLoaded event should be trusted before redispatching");
+ buttonElement.dispatchEvent(contentLoadedEvent);
+ assert_false(contentLoadedEvent.isTrusted, "Received DOMContentLoaded event shouldn't be trusted after redispatching");
+ assert_not_equals(untrustedContentLoadedEvent, undefined, "Untrusted DOMContentLoaded event should've been fired");
+ test_contentLoaded_redispatching.done();
+ });
+}, {capture: true, once: true});
+
+async function testMouseUpAndClickEvent() {
+ let mouseupEvent;
+ buttonElement.addEventListener("mouseup", event => {
+ mouseupEvent = event;
+ test_mouseup_redispatching.step(() => {
+ assert_true(mouseupEvent.isTrusted, "First mouseup event should be trusted");
+ });
+ test_redispatching_of_dispatching_event.step(() => {
+ assert_throws_dom("InvalidStateError", () => {
+ buttonElement.dispatchEvent(mouseupEvent);
+ }, "Trusted mouseup event");
+ });
+ }, {once: true});
+
+ let clickEvent;
+ buttonElement.addEventListener("click", event => {
+ clickEvent = event;
+ test_mouseup_redispatching.step(() => {
+ assert_true(clickEvent.isTrusted, "First click event should be trusted");
+ });
+ test_redispatching_of_dispatching_event.step(() => {
+ assert_throws_dom("InvalidStateError", function() {
+ buttonElement.dispatchEvent(event);
+ }, "Trusted click event");
+ });
+ buttonElement.addEventListener("mouseup", event => {
+ test_mouseup_redispatching.step(() => {
+ assert_false(event.isTrusted, "Redispatched mouseup event shouldn't be trusted");
+ });
+ test_redispatching_of_dispatching_event.step(() => {
+ assert_throws_dom("InvalidStateError", function() {
+ buttonElement.dispatchEvent(event);
+ }, "Untrusted mouseup event");
+ });
+ }, {once: true});
+ function onClick() {
+ test_mouseup_redispatching.step(() => {
+ assert_true(false, "click event shouldn't be fired for dispatched mouseup event");
+ });
+ }
+ test_mouseup_redispatching.step(() => {
+ assert_true(mouseupEvent.isTrusted, "Received mouseup event should be trusted before redispatching from click event listener");
+ buttonElement.addEventListener("click", onClick);
+ buttonElement.dispatchEvent(mouseupEvent);
+ buttonElement.removeEventListener("click", onClick);
+ assert_false(mouseupEvent.isTrusted, "Received mouseup event shouldn't be trusted after redispatching");
+ assert_true(clickEvent.isTrusted, "First click event should still be trusted even after redispatching mouseup event");
+ });
+ }, {once: true});
+
+ await waitForLoad;
+ let bounds = buttonElement.getBoundingClientRect();
+ test(() => { assert_true(true); }, `Synthesizing click on button...`);
+ new test_driver.click(buttonElement)
+ .then(() => {
+ test_mouseup_redispatching.step(() => {
+ assert_not_equals(clickEvent, undefined, "mouseup and click events should've been fired");
+ });
+ test_mouseup_redispatching.done();
+ test_redispatching_of_dispatching_event.done();
+ }, (reason) => {
+ test_mouseup_redispatching.step(() => {
+ assert_true(false, `Failed to send mouse click due to ${reason}`);
+ });
+ test_mouseup_redispatching.done();
+ test_redispatching_of_dispatching_event.done();
+ });
+}
+testMouseUpAndClickEvent();
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-reenter.html b/testing/web-platform/tests/dom/events/Event-dispatch-reenter.html
new file mode 100644
index 0000000000..71f8517bdd
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-reenter.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title> Dispatch additional events inside an event listener </title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<table id="table" border="1" style="display: none">
+ <tbody id="table-body">
+ <tr id="table-row">
+ <td id="table-cell">Shady Grove</td>
+ <td>Aeolian</td>
+ </tr>
+ <tr id="parent">
+ <td id="target">Over the river, Charlie</td>
+ <td>Dorian</td>
+ </tr>
+ </tbody>
+</table>
+<script>
+test(function() {
+ var event_type = "foo";
+ var target = document.getElementById("target");
+ var parent = document.getElementById("parent");
+ var tbody = document.getElementById("table-body");
+ var table = document.getElementById("table");
+ var body = document.body;
+ var html = document.documentElement;
+ var targets = [window, document, html, body, table, tbody, parent, target];
+ var expected_targets = [
+ window, document, html, body, table,
+ target, parent, tbody,
+ table, body, html, document, window,
+ tbody, parent, target];
+ var actual_targets = [];
+ var expected_types = [
+ "foo", "foo", "foo", "foo", "foo",
+ "bar", "bar", "bar",
+ "bar", "bar", "bar", "bar", "bar",
+ "foo", "foo", "foo"
+ ];
+
+ var actual_targets = [], actual_types = [];
+ var test_event = this.step_func(function(evt) {
+ actual_targets.push(evt.currentTarget);
+ actual_types.push(evt.type);
+
+ if (table == evt.currentTarget && event_type == evt.type) {
+ var e = document.createEvent("Event");
+ e.initEvent("bar", true, true);
+ target.dispatchEvent(e);
+ }
+ });
+
+ for (var i = 0; i < targets.length; ++i) {
+ targets[i].addEventListener(event_type, test_event, true);
+ targets[i].addEventListener("bar", test_event, false);
+ }
+
+ var evt = document.createEvent("Event");
+ evt.initEvent(event_type, false, true);
+ target.dispatchEvent(evt);
+
+ assert_array_equals(actual_targets, expected_targets, "actual_targets");
+ assert_array_equals(actual_types, expected_types, "actual_types");
+});
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-target-moved.html b/testing/web-platform/tests/dom/events/Event-dispatch-target-moved.html
new file mode 100644
index 0000000000..facb2c7b95
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-target-moved.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title> Determined event propagation path - target moved </title>
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-dispatch">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<table id="table" border="1" style="display: none">
+ <tbody id="table-body">
+ <tr id="table-row">
+ <td id="table-cell">Shady Grove</td>
+ <td>Aeolian</td>
+ </tr>
+ <tr id="parent">
+ <td id="target">Over the river, Charlie</td>
+ <td>Dorian</td>
+ </tr>
+ </tbody>
+</table>
+<script>
+test(function() {
+ var event_type = "foo";
+ var target = document.getElementById("target");
+ var parent = document.getElementById("parent");
+ var tbody = document.getElementById("table-body");
+ var table = document.getElementById("table");
+ var body = document.body;
+ var html = document.documentElement;
+ var targets = [window, document, html, body, table, tbody, parent, target];
+ var expected_targets = targets.concat([target, parent, tbody, table, body, html, document, window]);
+ var phases = [
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.AT_TARGET,
+ Event.AT_TARGET,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ ];
+
+ var actual_targets = [], actual_phases = [];
+ var test_event = this.step_func(function(evt) {
+ if (parent === target.parentNode) {
+ var table_row = document.getElementById("table-row");
+ table_row.appendChild(parent.removeChild(target));
+ }
+
+ actual_targets.push(evt.currentTarget);
+ actual_phases.push(evt.eventPhase);
+ });
+
+ for (var i = 0; i < targets.length; i++) {
+ targets[i].addEventListener(event_type, test_event, true);
+ targets[i].addEventListener(event_type, test_event, false);
+ }
+
+ var evt = document.createEvent("Event");
+ evt.initEvent(event_type, true, true);
+ target.dispatchEvent(evt);
+
+ assert_array_equals(actual_targets, expected_targets, "targets");
+ assert_array_equals(actual_phases, phases, "phases");
+}, "Event propagation path when an element in it is moved within the DOM");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-target-removed.html b/testing/web-platform/tests/dom/events/Event-dispatch-target-removed.html
new file mode 100644
index 0000000000..531799c3ad
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-target-removed.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Determined event propagation path - target removed</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-dispatch">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<table id="table" border="1" style="display: none">
+ <tbody id="table-body">
+ <tr id="table-row">
+ <td id="table-cell">Shady Grove</td>
+ <td>Aeolian</td>
+ </tr>
+ <tr id="parent">
+ <td id="target">Over the river, Charlie</td>
+ <td>Dorian</td>
+ </tr>
+ </tbody>
+</table>
+<script>
+test(function() {
+ var event_type = "foo";
+ var target = document.getElementById("target");
+ var parent = document.getElementById("parent");
+ var tbody = document.getElementById("table-body");
+ var table = document.getElementById("table");
+ var body = document.body;
+ var html = document.documentElement;
+ var targets = [window, document, html, body, table, tbody, parent, target];
+ var expected_targets = targets.concat([target, parent, tbody, table, body, html, document, window]);
+ var phases = [
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.CAPTURING_PHASE,
+ Event.AT_TARGET,
+ Event.AT_TARGET,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ Event.BUBBLING_PHASE,
+ ];
+
+ var actual_targets = [], actual_phases = [];
+ var test_event = this.step_func(function(evt) {
+ if (parent === target.parentNode) {
+ parent.removeChild(target);
+ }
+
+ actual_targets.push(evt.currentTarget);
+ actual_phases.push(evt.eventPhase);
+ });
+
+ for (var i = 0; i < targets.length; i++) {
+ targets[i].addEventListener(event_type, test_event, true);
+ targets[i].addEventListener(event_type, test_event, false);
+ }
+
+ var evt = document.createEvent("Event");
+ evt.initEvent(event_type, true, true);
+ target.dispatchEvent(evt);
+
+ assert_array_equals(actual_targets, expected_targets, "targets");
+ assert_array_equals(actual_phases, phases, "phases");
+}, "Event propagation path when an element in it is removed from the DOM");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-dispatch-throwing.html b/testing/web-platform/tests/dom/events/Event-dispatch-throwing.html
new file mode 100644
index 0000000000..7d1c0d94a0
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-dispatch-throwing.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>Throwing in event listeners</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+setup({allow_uncaught_exception:true})
+
+test(function() {
+ var errorEvents = 0;
+ window.onerror = this.step_func(function(e) {
+ assert_equals(typeof e, 'string');
+ ++errorEvents;
+ });
+
+ var element = document.createElement('div');
+
+ element.addEventListener('click', function() {
+ throw new Error('Error from only listener');
+ });
+
+ element.dispatchEvent(new Event('click'));
+
+ assert_equals(errorEvents, 1);
+}, "Throwing in event listener with a single listeners");
+
+test(function() {
+ var errorEvents = 0;
+ window.onerror = this.step_func(function(e) {
+ assert_equals(typeof e, 'string');
+ ++errorEvents;
+ });
+
+ var element = document.createElement('div');
+
+ var secondCalled = false;
+
+ element.addEventListener('click', function() {
+ throw new Error('Error from first listener');
+ });
+ element.addEventListener('click', this.step_func(function() {
+ secondCalled = true;
+ }), false);
+
+ element.dispatchEvent(new Event('click'));
+
+ assert_equals(errorEvents, 1);
+ assert_true(secondCalled);
+}, "Throwing in event listener with multiple listeners");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-init-while-dispatching.html b/testing/web-platform/tests/dom/events/Event-init-while-dispatching.html
new file mode 100644
index 0000000000..2aa1f6701c
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-init-while-dispatching.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Re-initializing events while dispatching them</title>
+<link rel="author" title="Josh Matthews" href="mailto:josh@joshmatthews.net">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var events = {
+ 'KeyboardEvent': {
+ 'constructor': function() { return new KeyboardEvent("type", {key: "A"}); },
+ 'init': function(ev) { ev.initKeyboardEvent("type2", true, true, null, "a", 1, "", true, "") },
+ 'check': function(ev) {
+ assert_equals(ev.key, "A", "initKeyboardEvent key setter should short-circuit");
+ assert_false(ev.repeat, "initKeyboardEvent repeat setter should short-circuit");
+ assert_equals(ev.location, 0, "initKeyboardEvent location setter should short-circuit");
+ }
+ },
+ 'MouseEvent': {
+ 'constructor': function() { return new MouseEvent("type"); },
+ 'init': function(ev) { ev.initMouseEvent("type2", true, true, null, 0, 1, 1, 1, 1, true, true, true, true, 1, null) },
+ 'check': function(ev) {
+ assert_equals(ev.screenX, 0, "initMouseEvent screenX setter should short-circuit");
+ assert_equals(ev.screenY, 0, "initMouseEvent screenY setter should short-circuit");
+ assert_equals(ev.clientX, 0, "initMouseEvent clientX setter should short-circuit");
+ assert_equals(ev.clientY, 0, "initMouseEvent clientY setter should short-circuit");
+ assert_false(ev.ctrlKey, "initMouseEvent ctrlKey setter should short-circuit");
+ assert_false(ev.altKey, "initMouseEvent altKey setter should short-circuit");
+ assert_false(ev.shiftKey, "initMouseEvent shiftKey setter should short-circuit");
+ assert_false(ev.metaKey, "initMouseEvent metaKey setter should short-circuit");
+ assert_equals(ev.button, 0, "initMouseEvent button setter should short-circuit");
+ }
+ },
+ 'CustomEvent': {
+ 'constructor': function() { return new CustomEvent("type") },
+ 'init': function(ev) { ev.initCustomEvent("type2", true, true, 1) },
+ 'check': function(ev) {
+ assert_equals(ev.detail, null, "initCustomEvent detail setter should short-circuit");
+ }
+ },
+ 'UIEvent': {
+ 'constructor': function() { return new UIEvent("type") },
+ 'init': function(ev) { ev.initUIEvent("type2", true, true, window, 1) },
+ 'check': function(ev) {
+ assert_equals(ev.view, null, "initUIEvent view setter should short-circuit");
+ assert_equals(ev.detail, 0, "initUIEvent detail setter should short-circuit");
+ }
+ },
+ 'Event': {
+ 'constructor': function() { return new Event("type") },
+ 'init': function(ev) { ev.initEvent("type2", true, true) },
+ 'check': function(ev) {
+ assert_equals(ev.bubbles, false, "initEvent bubbles setter should short-circuit");
+ assert_equals(ev.cancelable, false, "initEvent cancelable setter should short-circuit");
+ assert_equals(ev.type, "type", "initEvent type setter should short-circuit");
+ }
+ }
+};
+
+var names = Object.keys(events);
+for (var i = 0; i < names.length; i++) {
+ var t = async_test("Calling init" + names[i] + " while dispatching.");
+ t.step(function() {
+ var e = events[names[i]].constructor();
+
+ var target = document.createElement("div")
+ target.addEventListener("type", t.step_func(function() {
+ events[names[i]].init(e);
+
+ var o = e;
+ while ((o = Object.getPrototypeOf(o))) {
+ if (!(o.constructor.name in events)) {
+ break;
+ }
+ events[o.constructor.name].check(e);
+ }
+ }), false);
+
+ assert_equals(target.dispatchEvent(e), true, "dispatchEvent must return true")
+ });
+ t.done();
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-initEvent.html b/testing/web-platform/tests/dom/events/Event-initEvent.html
new file mode 100644
index 0000000000..ad1018d4da
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-initEvent.html
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<title>Event.initEvent</title>
+<link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var booleans = [true, false];
+booleans.forEach(function(bubbles) {
+ booleans.forEach(function(cancelable) {
+ test(function() {
+ var e = document.createEvent("Event")
+ e.initEvent("type", bubbles, cancelable)
+
+ // Step 2.
+ // Stop (immediate) propagation flag is tested later
+ assert_equals(e.defaultPrevented, false, "defaultPrevented")
+ assert_equals(e.returnValue, true, "returnValue")
+ // Step 3.
+ assert_equals(e.isTrusted, false, "isTrusted")
+ // Step 4.
+ assert_equals(e.target, null, "target")
+ assert_equals(e.srcElement, null, "srcElement")
+ // Step 5.
+ assert_equals(e.type, "type", "type")
+ // Step 6.
+ assert_equals(e.bubbles, bubbles, "bubbles")
+ // Step 7.
+ assert_equals(e.cancelable, cancelable, "cancelable")
+ }, "Properties of initEvent(type, " + bubbles + ", " + cancelable + ")")
+ })
+})
+
+test(function() {
+ var e = document.createEvent("Event")
+ e.initEvent("type 1", true, false)
+ assert_equals(e.type, "type 1", "type (first init)")
+ assert_equals(e.bubbles, true, "bubbles (first init)")
+ assert_equals(e.cancelable, false, "cancelable (first init)")
+
+ e.initEvent("type 2", false, true)
+ assert_equals(e.type, "type 2", "type (second init)")
+ assert_equals(e.bubbles, false, "bubbles (second init)")
+ assert_equals(e.cancelable, true, "cancelable (second init)")
+}, "Calling initEvent multiple times (getting type).")
+
+test(function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=998809
+ var e = document.createEvent("Event")
+ e.initEvent("type 1", true, false)
+ assert_equals(e.bubbles, true, "bubbles (first init)")
+ assert_equals(e.cancelable, false, "cancelable (first init)")
+
+ e.initEvent("type 2", false, true)
+ assert_equals(e.type, "type 2", "type (second init)")
+ assert_equals(e.bubbles, false, "bubbles (second init)")
+ assert_equals(e.cancelable, true, "cancelable (second init)")
+}, "Calling initEvent multiple times (not getting type).")
+
+// Step 2.
+async_test(function() {
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17715
+
+ var e = document.createEvent("Event")
+ e.initEvent("type", false, false)
+ assert_equals(e.type, "type", "type (first init)")
+ assert_equals(e.bubbles, false, "bubbles (first init)")
+ assert_equals(e.cancelable, false, "cancelable (first init)")
+
+ var target = document.createElement("div")
+ target.addEventListener("type", this.step_func(function() {
+ e.initEvent("fail", true, true)
+ assert_equals(e.type, "type", "type (second init)")
+ assert_equals(e.bubbles, false, "bubbles (second init)")
+ assert_equals(e.cancelable, false, "cancelable (second init)")
+ }), false)
+
+ assert_equals(target.dispatchEvent(e), true, "dispatchEvent must return true")
+
+ this.done()
+}, "Calling initEvent must not have an effect during dispatching.")
+
+test(function() {
+ var e = document.createEvent("Event")
+ e.stopPropagation()
+ e.initEvent("type", false, false)
+ var target = document.createElement("div")
+ var called = false
+ target.addEventListener("type", function() { called = true }, false)
+ assert_false(e.cancelBubble, "cancelBubble must be false")
+ assert_true(target.dispatchEvent(e), "dispatchEvent must return true")
+ assert_true(called, "Listener must be called")
+}, "Calling initEvent must unset the stop propagation flag.")
+
+test(function() {
+ var e = document.createEvent("Event")
+ e.stopImmediatePropagation()
+ e.initEvent("type", false, false)
+ var target = document.createElement("div")
+ var called = false
+ target.addEventListener("type", function() { called = true }, false)
+ assert_true(target.dispatchEvent(e), "dispatchEvent must return true")
+ assert_true(called, "Listener must be called")
+}, "Calling initEvent must unset the stop immediate propagation flag.")
+
+async_test(function() {
+ var e = document.createEvent("Event")
+ e.initEvent("type", false, false)
+
+ var target = document.createElement("div")
+ target.addEventListener("type", this.step_func(function() {
+ e.initEvent("type2", true, true);
+ assert_equals(e.type, "type", "initEvent type setter should short-circuit");
+ assert_false(e.bubbles, "initEvent bubbles setter should short-circuit");
+ assert_false(e.cancelable, "initEvent cancelable setter should short-circuit");
+ }), false)
+ assert_equals(target.dispatchEvent(e), true, "dispatchEvent must return true")
+
+ this.done()
+}, "Calling initEvent during propagation.")
+
+test(function() {
+ var e = document.createEvent("Event")
+ assert_throws_js(TypeError, function() {
+ e.initEvent()
+ })
+}, "First parameter to initEvent should be mandatory.")
+
+test(function() {
+ var e = document.createEvent("Event")
+ e.initEvent("type")
+ assert_equals(e.type, "type", "type")
+ assert_false(e.bubbles, "bubbles")
+ assert_false(e.cancelable, "cancelable")
+}, "Tests initEvent's default parameter values.")
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-isTrusted.any.js b/testing/web-platform/tests/dom/events/Event-isTrusted.any.js
new file mode 100644
index 0000000000..00bcecd0ed
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-isTrusted.any.js
@@ -0,0 +1,11 @@
+test(function() {
+ var desc1 = Object.getOwnPropertyDescriptor(new Event("x"), "isTrusted");
+ assert_not_equals(desc1, undefined);
+ assert_equals(typeof desc1.get, "function");
+
+ var desc2 = Object.getOwnPropertyDescriptor(new Event("x"), "isTrusted");
+ assert_not_equals(desc2, undefined);
+ assert_equals(typeof desc2.get, "function");
+
+ assert_equals(desc1.get, desc2.get);
+});
diff --git a/testing/web-platform/tests/dom/events/Event-propagation.html b/testing/web-platform/tests/dom/events/Event-propagation.html
new file mode 100644
index 0000000000..33989eb4bf
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-propagation.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<title>Event propagation tests</title>
+<link rel=author title="Aryeh Gregor" href=ayg@aryeh.name>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+"use strict";
+
+function testPropagationFlag(ev, expected, desc) {
+ test(function() {
+ var called = false;
+ var callback = function() { called = true };
+ this.add_cleanup(function() {
+ document.head.removeEventListener("foo", callback)
+ });
+ document.head.addEventListener("foo", callback);
+ document.head.dispatchEvent(ev);
+ assert_equals(called, expected, "Propagation flag");
+ // dispatchEvent resets the propagation flags so it will happily dispatch
+ // the event the second time around.
+ document.head.dispatchEvent(ev);
+ assert_equals(called, true, "Propagation flag after first dispatch");
+ }, desc);
+}
+
+var ev = document.createEvent("Event");
+ev.initEvent("foo", true, false);
+testPropagationFlag(ev, true, "Newly-created Event");
+ev.stopPropagation();
+testPropagationFlag(ev, false, "After stopPropagation()");
+ev.initEvent("foo", true, false);
+testPropagationFlag(ev, true, "Reinitialized after stopPropagation()");
+
+var ev = document.createEvent("Event");
+ev.initEvent("foo", true, false);
+ev.stopImmediatePropagation();
+testPropagationFlag(ev, false, "After stopImmediatePropagation()");
+ev.initEvent("foo", true, false);
+testPropagationFlag(ev, true, "Reinitialized after stopImmediatePropagation()");
+
+var ev = document.createEvent("Event");
+ev.initEvent("foo", true, false);
+ev.cancelBubble = true;
+testPropagationFlag(ev, false, "After cancelBubble=true");
+ev.initEvent("foo", true, false);
+testPropagationFlag(ev, true, "Reinitialized after cancelBubble=true");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-returnValue.html b/testing/web-platform/tests/dom/events/Event-returnValue.html
new file mode 100644
index 0000000000..08df2d4141
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-returnValue.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Event.returnValue</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://dom.spec.whatwg.org/#dom-event-returnvalue">
+ <meta name="flags" content="dom">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id="log"></div>
+ <script>
+test(function() {
+ var ev = new Event("foo");
+ assert_true(ev.returnValue, "returnValue");
+}, "When an event is created, returnValue should be initialized to true.");
+test(function() {
+ var ev = new Event("foo", {"cancelable": false});
+ assert_false(ev.cancelable, "cancelable (before)");
+ ev.preventDefault();
+ assert_false(ev.cancelable, "cancelable (after)");
+ assert_true(ev.returnValue, "returnValue");
+}, "preventDefault() should not change returnValue if cancelable is false.");
+test(function() {
+ var ev = new Event("foo", {"cancelable": false});
+ assert_false(ev.cancelable, "cancelable (before)");
+ ev.returnValue = false;
+ assert_false(ev.cancelable, "cancelable (after)");
+ assert_true(ev.returnValue, "returnValue");
+}, "returnValue=false should have no effect if cancelable is false.");
+test(function() {
+ var ev = new Event("foo", {"cancelable": true});
+ assert_true(ev.cancelable, "cancelable (before)");
+ ev.preventDefault();
+ assert_true(ev.cancelable, "cancelable (after)");
+ assert_false(ev.returnValue, "returnValue");
+}, "preventDefault() should change returnValue if cancelable is true.");
+test(function() {
+ var ev = new Event("foo", {"cancelable": true});
+ assert_true(ev.cancelable, "cancelable (before)");
+ ev.returnValue = false;
+ assert_true(ev.cancelable, "cancelable (after)");
+ assert_false(ev.returnValue, "returnValue");
+}, "returnValue should change returnValue if cancelable is true.");
+test(function() {
+ var ev = document.createEvent("Event");
+ ev.returnValue = false;
+ ev.initEvent("foo", true, true);
+ assert_true(ev.bubbles, "bubbles");
+ assert_true(ev.cancelable, "cancelable");
+ assert_true(ev.returnValue, "returnValue");
+}, "initEvent should unset returnValue.");
+test(function() {
+ var ev = new Event("foo", {"cancelable": true});
+ ev.preventDefault();
+ ev.returnValue = true;// no-op
+ assert_true(ev.defaultPrevented);
+ assert_false(ev.returnValue);
+}, "returnValue=true should have no effect once the canceled flag was set.");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/events/Event-stopImmediatePropagation.html b/testing/web-platform/tests/dom/events/Event-stopImmediatePropagation.html
new file mode 100644
index 0000000000..b75732257a
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-stopImmediatePropagation.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event's stopImmediatePropagation</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-event-stopimmediatepropagation">
+<link rel="author" href="mailto:d@domenic.me" title="Domenic Denicola">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="target"></div>
+
+<script>
+"use strict";
+
+setup({ single_test: true });
+
+const target = document.querySelector("#target");
+
+let timesCalled = 0;
+target.addEventListener("test", e => {
+ ++timesCalled;
+ e.stopImmediatePropagation();
+ assert_equals(e.cancelBubble, true, "The stop propagation flag must have been set");
+});
+target.addEventListener("test", () => {
+ ++timesCalled;
+});
+
+const e = new Event("test");
+target.dispatchEvent(e);
+assert_equals(timesCalled, 1, "The second listener must not have been called");
+
+done();
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-stopPropagation-cancel-bubbling.html b/testing/web-platform/tests/dom/events/Event-stopPropagation-cancel-bubbling.html
new file mode 100644
index 0000000000..5c2c49f338
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-stopPropagation-cancel-bubbling.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+test(t => {
+ const element = document.createElement('div');
+
+ element.addEventListener('click', () => {
+ event.stopPropagation();
+ }, { capture: true });
+
+ element.addEventListener('click',
+ t.unreached_func('stopPropagation in the capture handler should have canceled this bubble handler.'));
+
+ element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
+});
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-subclasses-constructors.html b/testing/web-platform/tests/dom/events/Event-subclasses-constructors.html
new file mode 100644
index 0000000000..08a5ded011
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-subclasses-constructors.html
@@ -0,0 +1,179 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Event constructors</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function assert_props(iface, event, defaults) {
+ assert_true(event instanceof self[iface]);
+ expected[iface].properties.forEach(function(p) {
+ var property = p[0], value = p[defaults ? 1 : 2];
+ assert_true(property in event,
+ "Event " + format_value(event) + " should have a " +
+ property + " property");
+ assert_equals(event[property], value,
+ "The value of the " + property + " property should be " +
+ format_value(value));
+ });
+ if ("parent" in expected[iface]) {
+ assert_props(expected[iface].parent, event, defaults);
+ }
+}
+
+// Class declarations don't go on the global by default, so put it there ourselves:
+
+self.SubclassedEvent = class SubclassedEvent extends Event {
+ constructor(name, props) {
+ super(name, props);
+ if (props && typeof(props) == "object" && "customProp" in props) {
+ this.customProp = props.customProp;
+ } else {
+ this.customProp = 5;
+ }
+ }
+
+ get fixedProp() {
+ return 17;
+ }
+}
+
+var EventModifierInit = [
+ ["ctrlKey", false, true],
+ ["shiftKey", false, true],
+ ["altKey", false, true],
+ ["metaKey", false, true],
+];
+var expected = {
+ "Event": {
+ "properties": [
+ ["bubbles", false, true],
+ ["cancelable", false, true],
+ ["isTrusted", false, false],
+ ],
+ },
+
+ "UIEvent": {
+ "parent": "Event",
+ "properties": [
+ ["view", null, window],
+ ["detail", 0, 7],
+ ],
+ },
+
+ "FocusEvent": {
+ "parent": "UIEvent",
+ "properties": [
+ ["relatedTarget", null, document],
+ ],
+ },
+
+ "MouseEvent": {
+ "parent": "UIEvent",
+ "properties": EventModifierInit.concat([
+ ["screenX", 0, 40],
+ ["screenY", 0, 40],
+ ["clientX", 0, 40],
+ ["clientY", 0, 40],
+ ["button", 0, 40],
+ ["buttons", 0, 40],
+ ["relatedTarget", null, document],
+ ]),
+ },
+
+ "WheelEvent": {
+ "parent": "MouseEvent",
+ "properties": [
+ ["deltaX", 0.0, 3.1],
+ ["deltaY", 0.0, 3.1],
+ ["deltaZ", 0.0, 3.1],
+ ["deltaMode", 0, 40],
+ ],
+ },
+
+ "KeyboardEvent": {
+ "parent": "UIEvent",
+ "properties": EventModifierInit.concat([
+ ["key", "", "string"],
+ ["code", "", "string"],
+ ["location", 0, 7],
+ ["repeat", false, true],
+ ["isComposing", false, true],
+ ["charCode", 0, 7],
+ ["keyCode", 0, 7],
+ ["which", 0, 7],
+ ]),
+ },
+
+ "CompositionEvent": {
+ "parent": "UIEvent",
+ "properties": [
+ ["data", "", "string"],
+ ],
+ },
+
+ "SubclassedEvent": {
+ "parent": "Event",
+ "properties": [
+ ["customProp", 5, 8],
+ ["fixedProp", 17, 17],
+ ],
+ },
+};
+
+Object.keys(expected).forEach(function(iface) {
+ test(function() {
+ var event = new self[iface]("type");
+ assert_props(iface, event, true);
+ }, iface + " constructor (no argument)");
+
+ test(function() {
+ var event = new self[iface]("type", undefined);
+ assert_props(iface, event, true);
+ }, iface + " constructor (undefined argument)");
+
+ test(function() {
+ var event = new self[iface]("type", null);
+ assert_props(iface, event, true);
+ }, iface + " constructor (null argument)");
+
+ test(function() {
+ var event = new self[iface]("type", {});
+ assert_props(iface, event, true);
+ }, iface + " constructor (empty argument)");
+
+ test(function() {
+ var dictionary = {};
+ expected[iface].properties.forEach(function(p) {
+ var property = p[0], value = p[1];
+ dictionary[property] = value;
+ });
+ var event = new self[iface]("type", dictionary);
+ assert_props(iface, event, true);
+ }, iface + " constructor (argument with default values)");
+
+ test(function() {
+ function fill_in(iface, dictionary) {
+ if ("parent" in expected[iface]) {
+ fill_in(expected[iface].parent, dictionary)
+ }
+ expected[iface].properties.forEach(function(p) {
+ var property = p[0], value = p[2];
+ dictionary[property] = value;
+ });
+ }
+
+ var dictionary = {};
+ fill_in(iface, dictionary);
+
+ var event = new self[iface]("type", dictionary);
+ assert_props(iface, event, false);
+ }, iface + " constructor (argument with non-default values)");
+});
+
+test(function () {
+ assert_throws_js(TypeError, function() {
+ new UIEvent("x", { view: 7 })
+ });
+}, "UIEvent constructor (view argument with wrong type)")
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-timestamp-cross-realm-getter.html b/testing/web-platform/tests/dom/events/Event-timestamp-cross-realm-getter.html
new file mode 100644
index 0000000000..45823de26b
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-timestamp-cross-realm-getter.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>event.timeStamp is initialized using event's relevant global object</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#ref-for-dom-event-timestamp%E2%91%A1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+const t = async_test();
+t.step_timeout(() => {
+ const iframeDelayed = document.createElement("iframe");
+ iframeDelayed.onload = t.step_func_done(() => {
+ // Use eval() to eliminate false-positive test result for WebKit builds before r280256,
+ // which invoked WebIDL accessors in context of lexical (caller) global object.
+ const timeStampExpected = iframeDelayed.contentWindow.eval(`new Event("foo").timeStamp`);
+ const eventDelayed = new iframeDelayed.contentWindow.Event("foo");
+
+ const {get} = Object.getOwnPropertyDescriptor(Event.prototype, "timeStamp");
+ assert_approx_equals(get.call(eventDelayed), timeStampExpected, 5, "via Object.getOwnPropertyDescriptor");
+
+ Object.setPrototypeOf(eventDelayed, Event.prototype);
+ assert_approx_equals(eventDelayed.timeStamp, timeStampExpected, 5, "via Object.setPrototypeOf");
+ });
+ document.body.append(iframeDelayed);
+}, 1000);
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-timestamp-high-resolution.html b/testing/web-platform/tests/dom/events/Event-timestamp-high-resolution.html
new file mode 100644
index 0000000000..a049fef64b
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-timestamp-high-resolution.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="text/javascript">
+'use strict';
+for (let eventType of ["MouseEvent", "KeyboardEvent", "WheelEvent", "FocusEvent"]) {
+ test(function() {
+ let before = performance.now();
+ let e = new window[eventType]('test');
+ let after = performance.now();
+ assert_greater_than_equal(e.timeStamp, before, "Event timestamp should be greater than performance.now() timestamp taken before its creation");
+ assert_less_than_equal(e.timeStamp, after, "Event timestamp should be less than performance.now() timestamp taken after its creation");
+ }, `Constructed ${eventType} timestamp should be high resolution and have the same time origin as performance.now()`);
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-timestamp-high-resolution.https.html b/testing/web-platform/tests/dom/events/Event-timestamp-high-resolution.https.html
new file mode 100644
index 0000000000..70f9742947
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-timestamp-high-resolution.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="text/javascript">
+'use strict';
+for (let eventType of ["GamepadEvent"]) {
+ test(function() {
+ let before = performance.now();
+ let e = new window[eventType]('test');
+ let after = performance.now();
+ assert_greater_than_equal(e.timeStamp, before, "Event timestamp should be greater than performance.now() timestamp taken before its creation");
+ assert_less_than_equal(e.timeStamp, after, "Event timestamp should be less than performance.now() timestamp taken after its creation");
+ }, `Constructed ${eventType} timestamp should be high resolution and have the same time origin as performance.now()`);
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-timestamp-safe-resolution.html b/testing/web-platform/tests/dom/events/Event-timestamp-safe-resolution.html
new file mode 100644
index 0000000000..24f2dec93c
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-timestamp-safe-resolution.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="text/javascript">
+'use strict';
+
+// Computes greatest common divisor of a and b using Euclid's algorithm
+function computeGCD(a, b) {
+ if (!Number.isInteger(a) || !Number.isInteger(b)) {
+ throw new Error('Parameters must be integer numbers');
+ }
+
+ var r;
+ while (b != 0) {
+ r = a % b;
+ a = b;
+ b = r;
+ }
+ return (a < 0) ? -a : a;
+}
+
+// Finds minimum resolution Δ given a set of samples which are known to be in the form of N*Δ.
+// We use GCD of all samples as a simple estimator.
+function estimateMinimumResolution(samples) {
+ var gcd;
+ for (const sample of samples) {
+ gcd = gcd ? computeGCD(gcd, sample) : sample;
+ }
+
+ return gcd;
+}
+
+test(function() {
+ const samples = [];
+ for (var i = 0; i < 1e3; i++) {
+ var deltaInMicroSeconds = 0;
+ const e1 = new MouseEvent('test1');
+ do {
+ const e2 = new MouseEvent('test2');
+ deltaInMicroSeconds = Math.round((e2.timeStamp - e1.timeStamp) * 1000);
+ } while (deltaInMicroSeconds == 0) // only collect non-zero samples
+
+ samples.push(deltaInMicroSeconds);
+ }
+
+ const minResolution = estimateMinimumResolution(samples);
+ assert_greater_than_equal(minResolution, 5);
+}, 'Event timestamp should not have a resolution better than 5 microseconds');
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/events/Event-type-empty.html b/testing/web-platform/tests/dom/events/Event-type-empty.html
new file mode 100644
index 0000000000..225b85a613
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-type-empty.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Event.type set to the empty string</title>
+<link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-event-type">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function do_test(t, e) {
+ assert_equals(e.type, "", "type");
+ assert_equals(e.bubbles, false, "bubbles");
+ assert_equals(e.cancelable, false, "cancelable");
+
+ var target = document.createElement("div");
+ var handled = false;
+ target.addEventListener("", t.step_func(function(e) {
+ handled = true;
+ }));
+ assert_true(target.dispatchEvent(e));
+ assert_true(handled);
+}
+
+async_test(function() {
+ var e = document.createEvent("Event");
+ e.initEvent("", false, false);
+ do_test(this, e);
+ this.done();
+}, "initEvent");
+
+async_test(function() {
+ var e = new Event("");
+ do_test(this, e);
+ this.done();
+}, "Constructor");
+</script>
diff --git a/testing/web-platform/tests/dom/events/Event-type.html b/testing/web-platform/tests/dom/events/Event-type.html
new file mode 100644
index 0000000000..22792f5c6c
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/Event-type.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>Event.type</title>
+<link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-event-type">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var e = document.createEvent("Event")
+ assert_equals(e.type, "");
+}, "Event.type should initially be the empty string");
+test(function() {
+ var e = document.createEvent("Event")
+ e.initEvent("foo", false, false)
+ assert_equals(e.type, "foo")
+}, "Event.type should be initialized by initEvent");
+test(function() {
+ var e = new Event("bar")
+ assert_equals(e.type, "bar")
+}, "Event.type should be initialized by the constructor");
+</script>
diff --git a/testing/web-platform/tests/dom/events/EventListener-addEventListener.sub.window.js b/testing/web-platform/tests/dom/events/EventListener-addEventListener.sub.window.js
new file mode 100644
index 0000000000..b44bc33285
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventListener-addEventListener.sub.window.js
@@ -0,0 +1,9 @@
+async_test(function(t) {
+ let crossOriginFrame = document.createElement('iframe');
+ crossOriginFrame.src = 'https://{{hosts[alt][]}}:{{ports[https][0]}}/common/blank.html';
+ document.body.appendChild(crossOriginFrame);
+ crossOriginFrame.addEventListener('load', t.step_func_done(function() {
+ let crossOriginWindow = crossOriginFrame.contentWindow;
+ window.addEventListener('click', crossOriginWindow);
+ }));
+}, "EventListener.addEventListener doesn't throw when a cross origin object is passed in.");
diff --git a/testing/web-platform/tests/dom/events/EventListener-handleEvent-cross-realm.html b/testing/web-platform/tests/dom/events/EventListener-handleEvent-cross-realm.html
new file mode 100644
index 0000000000..663d04213f
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventListener-handleEvent-cross-realm.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Cross-realm EventListener throws TypeError of its associated Realm</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#ref-for-prepare-to-run-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe name="eventListenerGlobalObject" src="resources/empty-document.html"></iframe>
+
+<script>
+setup({ allow_uncaught_exception: true });
+
+test_onload(() => {
+ const eventTarget = new EventTarget;
+ const eventListener = new eventListenerGlobalObject.Object;
+
+ eventTarget.addEventListener("foo", eventListener);
+ assert_reports_exception(eventListenerGlobalObject.TypeError, () => { eventTarget.dispatchEvent(new Event("foo")); });
+}, "EventListener is cross-realm plain object without 'handleEvent' property");
+
+test_onload(() => {
+ const eventTarget = new EventTarget;
+ const eventListener = new eventListenerGlobalObject.Object;
+ eventListener.handleEvent = {};
+
+ eventTarget.addEventListener("foo", eventListener);
+ assert_reports_exception(eventListenerGlobalObject.TypeError, () => { eventTarget.dispatchEvent(new Event("foo")); });
+}, "EventListener is cross-realm plain object with non-callable 'handleEvent' property");
+
+test_onload(() => {
+ const eventTarget = new EventTarget;
+ const { proxy, revoke } = Proxy.revocable(() => {}, {});
+ revoke();
+
+ const eventListener = new eventListenerGlobalObject.Object;
+ eventListener.handleEvent = proxy;
+
+ eventTarget.addEventListener("foo", eventListener);
+ assert_reports_exception(eventListenerGlobalObject.TypeError, () => { eventTarget.dispatchEvent(new Event("foo")); });
+}, "EventListener is cross-realm plain object with revoked Proxy as 'handleEvent' property");
+
+test_onload(() => {
+ const eventTarget = new EventTarget;
+ const { proxy, revoke } = eventListenerGlobalObject.Proxy.revocable({}, {});
+ revoke();
+
+ eventTarget.addEventListener("foo", proxy);
+ assert_reports_exception(eventListenerGlobalObject.TypeError, () => { eventTarget.dispatchEvent(new Event("foo")); });
+}, "EventListener is cross-realm non-callable revoked Proxy");
+
+test_onload(() => {
+ const eventTarget = new EventTarget;
+ const { proxy, revoke } = eventListenerGlobalObject.Proxy.revocable(() => {}, {});
+ revoke();
+
+ eventTarget.addEventListener("foo", proxy);
+ assert_reports_exception(eventListenerGlobalObject.TypeError, () => { eventTarget.dispatchEvent(new Event("foo")); });
+}, "EventListener is cross-realm callable revoked Proxy");
+
+function test_onload(fn, desc) {
+ async_test(t => { window.addEventListener("load", t.step_func_done(fn)); }, desc);
+}
+
+function assert_reports_exception(expectedConstructor, fn) {
+ let error;
+ const onErrorHandler = event => { error = event.error; };
+
+ eventListenerGlobalObject.addEventListener("error", onErrorHandler);
+ fn();
+ eventListenerGlobalObject.removeEventListener("error", onErrorHandler);
+
+ assert_equals(typeof error, "object");
+ assert_equals(error.constructor, expectedConstructor);
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/EventListener-handleEvent.html b/testing/web-platform/tests/dom/events/EventListener-handleEvent.html
new file mode 100644
index 0000000000..06bc1f6e2a
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventListener-handleEvent.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>EventListener::handleEvent()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://dom.spec.whatwg.org/#callbackdef-eventlistener">
+<div id=log></div>
+<script>
+setup({ allow_uncaught_exception: true });
+
+test(function(t) {
+ var type = "foo";
+ var target = document.createElement("div");
+ var eventListener = {
+ handleEvent: function(evt) {
+ var that = this;
+ t.step(function() {
+ assert_equals(evt.type, type);
+ assert_equals(evt.target, target);
+ assert_equals(evt.srcElement, target);
+ assert_equals(that, eventListener);
+ });
+ },
+ };
+
+ target.addEventListener(type, eventListener);
+ target.dispatchEvent(new Event(type));
+}, "calls `handleEvent` method of `EventListener`");
+
+test(function(t) {
+ var type = "foo";
+ var target = document.createElement("div");
+ var calls = 0;
+
+ target.addEventListener(type, {
+ get handleEvent() {
+ calls++;
+ return function() {};
+ },
+ });
+
+ assert_equals(calls, 0);
+ target.dispatchEvent(new Event(type));
+ target.dispatchEvent(new Event(type));
+ assert_equals(calls, 2);
+}, "performs `Get` every time event is dispatched");
+
+test(function(t) {
+ var type = "foo";
+ var target = document.createElement("div");
+ var calls = 0;
+ var eventListener = function() { calls++; };
+ eventListener.handleEvent = t.unreached_func("`handleEvent` method should not be called on functions");
+
+ target.addEventListener(type, eventListener);
+ target.dispatchEvent(new Event(type));
+ assert_equals(calls, 1);
+}, "doesn't call `handleEvent` method on callable `EventListener`");
+
+const uncaught_error_test = async (t, getHandleEvent) => {
+ const type = "foo";
+ const target = document.createElement("div");
+
+ let calls = 0;
+ target.addEventListener(type, {
+ get handleEvent() {
+ calls++;
+ return getHandleEvent();
+ },
+ });
+
+ const timeout = () => {
+ return new Promise(resolve => {
+ t.step_timeout(resolve, 0);
+ });
+ };
+
+ const eventWatcher = new EventWatcher(t, window, "error", timeout);
+ const errorPromise = eventWatcher.wait_for("error");
+
+ target.dispatchEvent(new Event(type));
+
+ const event = await errorPromise;
+ assert_equals(calls, 1, "handleEvent property was not looked up");
+ throw event.error;
+};
+
+promise_test(t => {
+ const error = { name: "test" };
+
+ return promise_rejects_exactly(t, error,
+ uncaught_error_test(t, () => { throw error; }));
+}, "rethrows errors when getting `handleEvent`");
+
+promise_test(t => {
+ return promise_rejects_js(t, TypeError, uncaught_error_test(t, () => null));
+}, "throws if `handleEvent` is falsy and not callable");
+
+promise_test(t => {
+ return promise_rejects_js(t, TypeError, uncaught_error_test(t, () => 42));
+}, "throws if `handleEvent` is thruthy and not callable");
+</script>
diff --git a/testing/web-platform/tests/dom/events/EventListener-incumbent-global-1.sub.html b/testing/web-platform/tests/dom/events/EventListener-incumbent-global-1.sub.html
new file mode 100644
index 0000000000..9d941385cb
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventListener-incumbent-global-1.sub.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe src="{{location[scheme]}}://{{domains[www1]}}:{{ports[http][0]}}{{location[path]}}/../EventListener-incumbent-global-subframe-1.sub.html"></iframe>
+<script>
+
+var t = async_test("Check the incumbent global EventListeners are called with");
+
+onload = t.step_func(function() {
+ onmessage = t.step_func_done(function(e) {
+ var d = e.data;
+ assert_equals(d.actual, d.expected, d.reason);
+ });
+
+ frames[0].postMessage("start", "*");
+});
+
+</script>
diff --git a/testing/web-platform/tests/dom/events/EventListener-incumbent-global-2.sub.html b/testing/web-platform/tests/dom/events/EventListener-incumbent-global-2.sub.html
new file mode 100644
index 0000000000..4433c098d7
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventListener-incumbent-global-2.sub.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe src="{{location[scheme]}}://{{domains[www1]}}:{{ports[http][0]}}{{location[path]}}/../EventListener-incumbent-global-subframe-2.sub.html"></iframe>
+<script>
+
+var t = async_test("Check the incumbent global EventListeners are called with");
+
+onload = t.step_func(function() {
+ onmessage = t.step_func_done(function(e) {
+ var d = e.data;
+ assert_equals(d.actual, d.expected, d.reason);
+ });
+
+ frames[0].postMessage("start", "*");
+});
+
+</script>
diff --git a/testing/web-platform/tests/dom/events/EventListener-incumbent-global-subframe-1.sub.html b/testing/web-platform/tests/dom/events/EventListener-incumbent-global-subframe-1.sub.html
new file mode 100644
index 0000000000..25487cc5e0
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventListener-incumbent-global-subframe-1.sub.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<iframe src="{{location[scheme]}}://{{domains[www2]}}:{{ports[http][0]}}{{location[path]}}/../EventListener-incumbent-global-subsubframe.sub.html"></iframe>
+<script>
+ document.domain = "{{host}}";
+ onmessage = function(e) {
+ if (e.data == "start") {
+ frames[0].document.body.addEventListener("click", frames[0].postMessage.bind(frames[0], "respond", "*", undefined));
+ frames[0].postMessage("sendclick", "*");
+ } else {
+ parent.postMessage(e.data, "*");
+ }
+ }
+</script>
diff --git a/testing/web-platform/tests/dom/events/EventListener-incumbent-global-subframe-2.sub.html b/testing/web-platform/tests/dom/events/EventListener-incumbent-global-subframe-2.sub.html
new file mode 100644
index 0000000000..9c7235e2ad
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventListener-incumbent-global-subframe-2.sub.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<iframe src="{{location[scheme]}}://{{domains[www2]}}:{{ports[http][0]}}{{location[path]}}/../EventListener-incumbent-global-subsubframe.sub.html"></iframe>
+<script>
+ document.domain = "{{host}}";
+ onmessage = function(e) {
+ if (e.data == "start") {
+ frames[0].document.body.addEventListener("click", frames[0].getTheListener());
+ frames[0].postMessage("sendclick", "*");
+ } else {
+ parent.postMessage(e.data, "*");
+ }
+ }
+</script>
diff --git a/testing/web-platform/tests/dom/events/EventListener-incumbent-global-subsubframe.sub.html b/testing/web-platform/tests/dom/events/EventListener-incumbent-global-subsubframe.sub.html
new file mode 100644
index 0000000000..dd683f6f65
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventListener-incumbent-global-subsubframe.sub.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<script>
+ function getTheListener() {
+ return postMessage.bind(this, "respond", "*", undefined)
+ }
+ document.domain = "{{host}}";
+ onmessage = function (e) {
+ if (e.data == "sendclick") {
+ document.body.click();
+ } else {
+ parent.postMessage(
+ {
+ actual: e.origin,
+ expected: parent.location.origin,
+ reason: "Incumbent should have been the caller of addEventListener()"
+ },
+ "*")
+ };
+ }
+</script>
diff --git a/testing/web-platform/tests/dom/events/EventListener-invoke-legacy.html b/testing/web-platform/tests/dom/events/EventListener-invoke-legacy.html
new file mode 100644
index 0000000000..a01afcd8d1
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventListener-invoke-legacy.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Invoke legacy event listener</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ @keyframes test {
+ 0% { color: red; }
+ 100% { color: green; }
+ }
+</style>
+<div id="log"></div>
+<script>
+function runLegacyEventTest(type, legacyType, ctor, setup) {
+ function createTestElem(t) {
+ var elem = document.createElement('div');
+ document.body.appendChild(elem);
+ t.add_cleanup(function() {
+ document.body.removeChild(elem);
+ });
+ return elem;
+ }
+
+ async_test(function(t) {
+ var elem = createTestElem(t);
+ var gotEvent = false;
+ elem.addEventListener(legacyType,
+ t.unreached_func("listener of " + legacyType + " should not be invoked"));
+ elem.addEventListener(type, t.step_func(function() {
+ assert_false(gotEvent, "unexpected " + type + " event");
+ gotEvent = true;
+ t.step_timeout(function() { t.done(); }, 100);
+ }));
+ setup(elem);
+ }, "Listener of " + type);
+
+ async_test(function(t) {
+ var elem = createTestElem(t);
+ var count = 0;
+ elem.addEventListener(legacyType, t.step_func(function() {
+ ++count;
+ if (count > 1) {
+ assert_unreached("listener of " + legacyType + " should not be invoked again");
+ return;
+ }
+ elem.dispatchEvent(new window[ctor](type));
+ t.done();
+ }));
+ setup(elem);
+ }, "Legacy listener of " + type);
+}
+
+function setupTransition(elem) {
+ getComputedStyle(elem).color;
+ elem.style.color = 'green';
+ elem.style.transition = 'color 30ms';
+}
+
+function setupAnimation(elem) {
+ elem.style.animation = 'test 30ms';
+}
+
+runLegacyEventTest('transitionend', 'webkitTransitionEnd', "TransitionEvent", setupTransition);
+runLegacyEventTest('animationend', 'webkitAnimationEnd', "AnimationEvent", setupAnimation);
+runLegacyEventTest('animationstart', 'webkitAnimationStart', "AnimationEvent", setupAnimation);
+</script>
diff --git a/testing/web-platform/tests/dom/events/EventListenerOptions-capture.html b/testing/web-platform/tests/dom/events/EventListenerOptions-capture.html
new file mode 100644
index 0000000000..f72cf3ca54
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventListenerOptions-capture.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>EventListenerOptions.capture</title>
+<link rel="author" title="Rick Byers" href="mailto:rbyers@chromium.org">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-eventlisteneroptions-capture">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+
+<script>
+
+function testCaptureValue(captureValue, expectedValue) {
+ var handlerPhase = undefined;
+ var handler = function handler(e) {
+ assert_equals(handlerPhase, undefined, "Handler invoked after remove");
+ handlerPhase = e.eventPhase;
+ }
+ document.addEventListener('test', handler, captureValue);
+ document.body.dispatchEvent(new Event('test', {bubbles: true}));
+ document.removeEventListener('test', handler, captureValue);
+ document.body.dispatchEvent(new Event('test', {bubbles: true}));
+ assert_equals(handlerPhase, expectedValue, "Incorrect event phase for value: " + JSON.stringify(captureValue));
+}
+
+test(function() {
+ testCaptureValue(true, Event.CAPTURING_PHASE);
+ testCaptureValue(false, Event.BUBBLING_PHASE);
+ testCaptureValue(null, Event.BUBBLING_PHASE);
+ testCaptureValue(undefined, Event.BUBBLING_PHASE);
+ testCaptureValue(2.3, Event.CAPTURING_PHASE);
+ testCaptureValue(-1000.3, Event.CAPTURING_PHASE);
+ testCaptureValue(NaN, Event.BUBBLING_PHASE);
+ testCaptureValue(+0.0, Event.BUBBLING_PHASE);
+ testCaptureValue(-0.0, Event.BUBBLING_PHASE);
+ testCaptureValue("", Event.BUBBLING_PHASE);
+ testCaptureValue("AAAA", Event.CAPTURING_PHASE);
+}, "Capture boolean should be honored correctly");
+
+test(function() {
+ testCaptureValue({}, Event.BUBBLING_PHASE);
+ testCaptureValue({capture:true}, Event.CAPTURING_PHASE);
+ testCaptureValue({capture:false}, Event.BUBBLING_PHASE);
+ testCaptureValue({capture:2}, Event.CAPTURING_PHASE);
+ testCaptureValue({capture:0}, Event.BUBBLING_PHASE);
+}, "Capture option should be honored correctly");
+
+test(function() {
+ var supportsCapture = false;
+ var query_options = {
+ get capture() {
+ supportsCapture = true;
+ return false;
+ },
+ get dummy() {
+ assert_unreached("dummy value getter invoked");
+ return false;
+ }
+ };
+
+ document.addEventListener('test_event', null, query_options);
+ assert_true(supportsCapture, "addEventListener doesn't support the capture option");
+ supportsCapture = false;
+ document.removeEventListener('test_event', null, query_options);
+ assert_true(supportsCapture, "removeEventListener doesn't support the capture option");
+}, "Supports capture option");
+
+function testOptionEquality(addOptionValue, removeOptionValue, expectedEquality) {
+ var handlerInvoked = false;
+ var handler = function handler(e) {
+ assert_equals(handlerInvoked, false, "Handler invoked multiple times");
+ handlerInvoked = true;
+ }
+ document.addEventListener('test', handler, addOptionValue);
+ document.removeEventListener('test', handler, removeOptionValue);
+ document.body.dispatchEvent(new Event('test', {bubbles: true}));
+ assert_equals(!handlerInvoked, expectedEquality, "equivalence of options " +
+ JSON.stringify(addOptionValue) + " and " + JSON.stringify(removeOptionValue));
+ if (handlerInvoked)
+ document.removeEventListener('test', handler, addOptionValue);
+}
+
+test(function() {
+ // Option values that should be treated as equivalent
+ testOptionEquality({}, false, true);
+ testOptionEquality({capture: false}, false, true);
+ testOptionEquality(true, {capture: true}, true);
+ testOptionEquality({capture: null}, undefined, true);
+ testOptionEquality({capture: true}, {dummy: false, capture: 1}, true);
+ testOptionEquality({dummy: true}, false, true);
+
+ // Option values that should be treated as distinct
+ testOptionEquality(true, false, false);
+ testOptionEquality(true, {capture:false}, false);
+ testOptionEquality({}, true, false);
+
+}, "Equivalence of option values");
+
+</script>
diff --git a/testing/web-platform/tests/dom/events/EventTarget-add-listener-platform-object.html b/testing/web-platform/tests/dom/events/EventTarget-add-listener-platform-object.html
new file mode 100644
index 0000000000..d5565c22b3
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventTarget-add-listener-platform-object.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>addEventListener with a platform object</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+</script>
+<my-custom-click id=click>Click me!</my-custom-click>
+<script>
+"use strict";
+setup({ single_test: true });
+
+class MyCustomClick extends HTMLElement {
+ connectedCallback() {
+ this.addEventListener("click", this);
+ }
+
+ handleEvent(event) {
+ if (event.target === this) {
+ this.dataset.yay = "It worked!";
+ }
+ }
+}
+window.customElements.define("my-custom-click", MyCustomClick);
+
+const customElement = document.getElementById("click");
+customElement.click();
+
+assert_equals(customElement.dataset.yay, "It worked!");
+
+done();
+</script>
diff --git a/testing/web-platform/tests/dom/events/EventTarget-add-remove-listener.any.js b/testing/web-platform/tests/dom/events/EventTarget-add-remove-listener.any.js
new file mode 100644
index 0000000000..b1d7ffb3e0
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventTarget-add-remove-listener.any.js
@@ -0,0 +1,21 @@
+// META: title=EventTarget's addEventListener + removeEventListener
+
+"use strict";
+
+function listener(evt) {
+ evt.preventDefault();
+ return false;
+}
+
+test(() => {
+ const et = new EventTarget();
+ et.addEventListener("x", listener, false);
+ let event = new Event("x", { cancelable: true });
+ let ret = et.dispatchEvent(event);
+ assert_false(ret);
+
+ et.removeEventListener("x", listener);
+ event = new Event("x", { cancelable: true });
+ ret = et.dispatchEvent(event);
+ assert_true(ret);
+}, "Removing an event listener without explicit capture arg should succeed");
diff --git a/testing/web-platform/tests/dom/events/EventTarget-addEventListener.any.js b/testing/web-platform/tests/dom/events/EventTarget-addEventListener.any.js
new file mode 100644
index 0000000000..e22da4aff8
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventTarget-addEventListener.any.js
@@ -0,0 +1,9 @@
+// META: title=EventTarget.addEventListener
+
+// Step 1.
+test(function() {
+ const et = new EventTarget();
+ assert_equals(et.addEventListener("x", null, false), undefined);
+ assert_equals(et.addEventListener("x", null, true), undefined);
+ assert_equals(et.addEventListener("x", null), undefined);
+}, "Adding a null event listener should succeed");
diff --git a/testing/web-platform/tests/dom/events/EventTarget-constructible.any.js b/testing/web-platform/tests/dom/events/EventTarget-constructible.any.js
new file mode 100644
index 0000000000..b0e7614e62
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventTarget-constructible.any.js
@@ -0,0 +1,62 @@
+"use strict";
+
+test(() => {
+ const target = new EventTarget();
+ const event = new Event("foo", { bubbles: true, cancelable: false });
+ let callCount = 0;
+
+ function listener(e) {
+ assert_equals(e, event);
+ ++callCount;
+ }
+
+ target.addEventListener("foo", listener);
+
+ target.dispatchEvent(event);
+ assert_equals(callCount, 1);
+
+ target.dispatchEvent(event);
+ assert_equals(callCount, 2);
+
+ target.removeEventListener("foo", listener);
+ target.dispatchEvent(event);
+ assert_equals(callCount, 2);
+}, "A constructed EventTarget can be used as expected");
+
+test(() => {
+ class NicerEventTarget extends EventTarget {
+ on(...args) {
+ this.addEventListener(...args);
+ }
+
+ off(...args) {
+ this.removeEventListener(...args);
+ }
+
+ dispatch(type, detail) {
+ this.dispatchEvent(new CustomEvent(type, { detail }));
+ }
+ }
+
+ const target = new NicerEventTarget();
+ const event = new Event("foo", { bubbles: true, cancelable: false });
+ const detail = "some data";
+ let callCount = 0;
+
+ function listener(e) {
+ assert_equals(e.detail, detail);
+ ++callCount;
+ }
+
+ target.on("foo", listener);
+
+ target.dispatch("foo", detail);
+ assert_equals(callCount, 1);
+
+ target.dispatch("foo", detail);
+ assert_equals(callCount, 2);
+
+ target.off("foo", listener);
+ target.dispatch("foo", detail);
+ assert_equals(callCount, 2);
+}, "EventTarget can be subclassed");
diff --git a/testing/web-platform/tests/dom/events/EventTarget-dispatchEvent-returnvalue.html b/testing/web-platform/tests/dom/events/EventTarget-dispatchEvent-returnvalue.html
new file mode 100644
index 0000000000..c4466e0d6c
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventTarget-dispatchEvent-returnvalue.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>EventTarget.dispatchEvent: return value</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-dispatch">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-event-preventdefault">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-event-returnvalue">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-event-defaultprevented">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<table id="table" border="1" style="display: none">
+ <tbody id="table-body">
+ <tr id="table-row">
+ <td id="table-cell">Shady Grove</td>
+ <td>Aeolian</td>
+ </tr>
+ <tr id="parent">
+ <td id="target">Over the river, Charlie</td>
+ <td>Dorian</td>
+ </tr>
+ </tbody>
+</table>
+<script>
+test(function() {
+ var event_type = "foo";
+ var target = document.getElementById("target");
+ var parent = document.getElementById("parent");
+ var default_prevented;
+ var return_value;
+
+ parent.addEventListener(event_type, function(e) {}, true);
+ target.addEventListener(event_type, function(e) {
+ evt.preventDefault();
+ default_prevented = evt.defaultPrevented;
+ return_value = evt.returnValue;
+ }, true);
+ target.addEventListener(event_type, function(e) {}, true);
+
+ var evt = document.createEvent("Event");
+ evt.initEvent(event_type, true, true);
+
+ assert_true(parent.dispatchEvent(evt));
+ assert_false(target.dispatchEvent(evt));
+ assert_true(default_prevented);
+ assert_false(return_value);
+}, "Return value of EventTarget.dispatchEvent() affected by preventDefault().");
+
+test(function() {
+ var event_type = "foo";
+ var target = document.getElementById("target");
+ var parent = document.getElementById("parent");
+ var default_prevented;
+ var return_value;
+
+ parent.addEventListener(event_type, function(e) {}, true);
+ target.addEventListener(event_type, function(e) {
+ evt.returnValue = false;
+ default_prevented = evt.defaultPrevented;
+ return_value = evt.returnValue;
+ }, true);
+ target.addEventListener(event_type, function(e) {}, true);
+
+ var evt = document.createEvent("Event");
+ evt.initEvent(event_type, true, true);
+
+ assert_true(parent.dispatchEvent(evt));
+ assert_false(target.dispatchEvent(evt));
+ assert_true(default_prevented);
+ assert_false(return_value);
+}, "Return value of EventTarget.dispatchEvent() affected by returnValue.");
+</script>
diff --git a/testing/web-platform/tests/dom/events/EventTarget-dispatchEvent.html b/testing/web-platform/tests/dom/events/EventTarget-dispatchEvent.html
new file mode 100644
index 0000000000..783561f5fb
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventTarget-dispatchEvent.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>EventTarget.dispatchEvent</title>
+<link rel="author" title="Olli Pettay" href="mailto:Olli.Pettay@gmail.com">
+<link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-eventtarget-dispatchevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/dom/nodes/Document-createEvent.js"></script>
+<div id="log"></div>
+<script>
+setup({
+ "allow_uncaught_exception": true,
+})
+
+test(function() {
+ assert_throws_js(TypeError, function() { document.dispatchEvent(null) })
+}, "Calling dispatchEvent(null).")
+
+for (var alias in aliases) {
+ test(function() {
+ var e = document.createEvent(alias)
+ assert_equals(e.type, "", "Event type should be empty string before initialization")
+ assert_throws_dom("InvalidStateError", function() { document.dispatchEvent(e) })
+ }, "If the event's initialized flag is not set, an InvalidStateError must be thrown (" + alias + ").")
+}
+
+var dispatch_dispatch = async_test("If the event's dispatch flag is set, an InvalidStateError must be thrown.")
+dispatch_dispatch.step(function() {
+ var e = document.createEvent("Event")
+ e.initEvent("type", false, false)
+
+ var target = document.createElement("div")
+ target.addEventListener("type", dispatch_dispatch.step_func(function() {
+ assert_throws_dom("InvalidStateError", function() {
+ target.dispatchEvent(e)
+ })
+ assert_throws_dom("InvalidStateError", function() {
+ document.dispatchEvent(e)
+ })
+ }), false)
+
+ assert_equals(target.dispatchEvent(e), true, "dispatchEvent must return true")
+
+ dispatch_dispatch.done()
+})
+
+test(function() {
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17713
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17714
+
+ var e = document.createEvent("Event")
+ e.initEvent("type", false, false)
+
+ var called = []
+
+ var target = document.createElement("div")
+ target.addEventListener("type", function() {
+ called.push("First")
+ throw new Error()
+ }, false)
+
+ target.addEventListener("type", function() {
+ called.push("Second")
+ }, false)
+
+ assert_equals(target.dispatchEvent(e), true, "dispatchEvent must return true")
+ assert_array_equals(called, ["First", "Second"],
+ "Should have continued to call other event listeners")
+}, "Exceptions from event listeners must not be propagated.")
+
+async_test(function() {
+ var results = []
+ var outerb = document.createElement("b")
+ var middleb = outerb.appendChild(document.createElement("b"))
+ var innerb = middleb.appendChild(document.createElement("b"))
+ outerb.addEventListener("x", this.step_func(function() {
+ middleb.addEventListener("x", this.step_func(function() {
+ results.push("middle")
+ }), true)
+ results.push("outer")
+ }), true)
+ innerb.dispatchEvent(new Event("x"))
+ assert_array_equals(results, ["outer", "middle"])
+ this.done()
+}, "Event listeners added during dispatch should be called");
+
+async_test(function() {
+ var results = []
+ var b = document.createElement("b")
+ b.addEventListener("x", this.step_func(function() {
+ results.push(1)
+ }), true)
+ b.addEventListener("x", this.step_func(function() {
+ results.push(2)
+ }), false)
+ b.addEventListener("x", this.step_func(function() {
+ results.push(3)
+ }), true)
+ b.dispatchEvent(new Event("x"))
+ assert_array_equals(results, [1, 3, 2])
+ this.done()
+}, "Capturing event listeners should be called before non-capturing ones")
+</script>
diff --git a/testing/web-platform/tests/dom/events/EventTarget-removeEventListener.any.js b/testing/web-platform/tests/dom/events/EventTarget-removeEventListener.any.js
new file mode 100644
index 0000000000..289dfcfbab
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventTarget-removeEventListener.any.js
@@ -0,0 +1,8 @@
+// META: title=EventTarget.removeEventListener
+
+// Step 1.
+test(function() {
+ assert_equals(globalThis.removeEventListener("x", null, false), undefined);
+ assert_equals(globalThis.removeEventListener("x", null, true), undefined);
+ assert_equals(globalThis.removeEventListener("x", null), undefined);
+}, "removing a null event listener should succeed");
diff --git a/testing/web-platform/tests/dom/events/EventTarget-this-of-listener.html b/testing/web-platform/tests/dom/events/EventTarget-this-of-listener.html
new file mode 100644
index 0000000000..506564c413
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/EventTarget-this-of-listener.html
@@ -0,0 +1,182 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>EventTarget listeners this value</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-listener-invoke">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+
+ const nodes = [
+ document.createElement("p"),
+ document.createTextNode("some text"),
+ document.createDocumentFragment(),
+ document.createComment("a comment"),
+ document.createProcessingInstruction("target", "data")
+ ];
+
+ let callCount = 0;
+ for (const node of nodes) {
+ node.addEventListener("someevent", function () {
+ ++callCount;
+ assert_equals(this, node);
+ });
+
+ node.dispatchEvent(new CustomEvent("someevent"));
+ }
+
+ assert_equals(callCount, nodes.length);
+
+}, "the this value inside the event listener callback should be the node");
+
+test(() => {
+
+ const nodes = [
+ document.createElement("p"),
+ document.createTextNode("some text"),
+ document.createDocumentFragment(),
+ document.createComment("a comment"),
+ document.createProcessingInstruction("target", "data")
+ ];
+
+ let callCount = 0;
+ for (const node of nodes) {
+ const handler = {
+ handleEvent() {
+ ++callCount;
+ assert_equals(this, handler);
+ }
+ };
+
+ node.addEventListener("someevent", handler);
+
+ node.dispatchEvent(new CustomEvent("someevent"));
+ }
+
+ assert_equals(callCount, nodes.length);
+
+}, "the this value inside the event listener object handleEvent should be the object");
+
+test(() => {
+
+ const nodes = [
+ document.createElement("p"),
+ document.createTextNode("some text"),
+ document.createDocumentFragment(),
+ document.createComment("a comment"),
+ document.createProcessingInstruction("target", "data")
+ ];
+
+ let callCount = 0;
+ for (const node of nodes) {
+ const handler = {
+ handleEvent() {
+ assert_unreached("should not call the old handleEvent method");
+ }
+ };
+
+ node.addEventListener("someevent", handler);
+ handler.handleEvent = function () {
+ ++callCount;
+ assert_equals(this, handler);
+ };
+
+ node.dispatchEvent(new CustomEvent("someevent"));
+ }
+
+ assert_equals(callCount, nodes.length);
+
+}, "dispatchEvent should invoke the current handleEvent method of the object");
+
+test(() => {
+
+ const nodes = [
+ document.createElement("p"),
+ document.createTextNode("some text"),
+ document.createDocumentFragment(),
+ document.createComment("a comment"),
+ document.createProcessingInstruction("target", "data")
+ ];
+
+ let callCount = 0;
+ for (const node of nodes) {
+ const handler = {};
+
+ node.addEventListener("someevent", handler);
+ handler.handleEvent = function () {
+ ++callCount;
+ assert_equals(this, handler);
+ };
+
+ node.dispatchEvent(new CustomEvent("someevent"));
+ }
+
+ assert_equals(callCount, nodes.length);
+
+}, "addEventListener should not require handleEvent to be defined on object listeners");
+
+test(() => {
+
+ const nodes = [
+ document.createElement("p"),
+ document.createTextNode("some text"),
+ document.createDocumentFragment(),
+ document.createComment("a comment"),
+ document.createProcessingInstruction("target", "data")
+ ];
+
+ let callCount = 0;
+ for (const node of nodes) {
+ function handler() {
+ ++callCount;
+ assert_equals(this, node);
+ }
+
+ handler.handleEvent = () => {
+ assert_unreached("should not call the handleEvent method on a function");
+ };
+
+ node.addEventListener("someevent", handler);
+
+ node.dispatchEvent(new CustomEvent("someevent"));
+ }
+
+ assert_equals(callCount, nodes.length);
+
+}, "handleEvent properties added to a function before addEventListener are not reached");
+
+test(() => {
+
+ const nodes = [
+ document.createElement("p"),
+ document.createTextNode("some text"),
+ document.createDocumentFragment(),
+ document.createComment("a comment"),
+ document.createProcessingInstruction("target", "data")
+ ];
+
+ let callCount = 0;
+ for (const node of nodes) {
+ function handler() {
+ ++callCount;
+ assert_equals(this, node);
+ }
+
+ node.addEventListener("someevent", handler);
+
+ handler.handleEvent = () => {
+ assert_unreached("should not call the handleEvent method on a function");
+ };
+
+ node.dispatchEvent(new CustomEvent("someevent"));
+ }
+
+ assert_equals(callCount, nodes.length);
+
+}, "handleEvent properties added to a function after addEventListener are not reached");
+
+</script>
diff --git a/testing/web-platform/tests/dom/events/KeyEvent-initKeyEvent.html b/testing/web-platform/tests/dom/events/KeyEvent-initKeyEvent.html
new file mode 100644
index 0000000000..3fffaba014
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/KeyEvent-initKeyEvent.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>KeyEvent.initKeyEvent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+// The legacy KeyEvent.initKeyEvent shouldn't be defined in the wild anymore.
+// https://www.w3.org/TR/1999/WD-DOM-Level-2-19990923/events.html#Events-Event-initKeyEvent
+test(function() {
+ const event = document.createEvent("KeyboardEvent");
+ assert_true(event?.initKeyEvent === undefined);
+}, "KeyboardEvent.initKeyEvent shouldn't be defined (created by createEvent(\"KeyboardEvent\")");
+
+test(function() {
+ const event = new KeyboardEvent("keypress");
+ assert_true(event?.initKeyEvent === undefined);
+}, "KeyboardEvent.initKeyEvent shouldn't be defined (created by constructor)");
+
+test(function() {
+ assert_true(KeyboardEvent.prototype.initKeyEvent === undefined);
+}, "KeyboardEvent.prototype.initKeyEvent shouldn't be defined");
+</script>
diff --git a/testing/web-platform/tests/dom/events/event-disabled-dynamic.html b/testing/web-platform/tests/dom/events/event-disabled-dynamic.html
new file mode 100644
index 0000000000..3f995b02f1
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/event-disabled-dynamic.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test that disabled is honored immediately in presence of dynamic changes</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Andreas Farre" href="mailto:afarre@mozilla.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls:-the-disabled-attribute">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1405087">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<input type="button" value="Click" disabled>
+<script>
+async_test(t => {
+ // NOTE: This test will timeout if it fails.
+ window.addEventListener('load', t.step_func(() => {
+ let e = document.querySelector('input');
+ e.disabled = false;
+ e.onclick = t.step_func_done(() => {});
+ e.click();
+ }));
+}, "disabled is honored properly in presence of dynamic changes");
+</script>
diff --git a/testing/web-platform/tests/dom/events/event-global-extra.window.js b/testing/web-platform/tests/dom/events/event-global-extra.window.js
new file mode 100644
index 0000000000..0f14961c40
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/event-global-extra.window.js
@@ -0,0 +1,90 @@
+const otherWindow = document.body.appendChild(document.createElement("iframe")).contentWindow;
+
+["EventTarget", "XMLHttpRequest"].forEach(constructorName => {
+ async_test(t => {
+ const eventTarget = new otherWindow[constructorName]();
+ eventTarget.addEventListener("hi", t.step_func_done(e => {
+ assert_equals(otherWindow.event, undefined);
+ assert_equals(e, window.event);
+ }));
+ eventTarget.dispatchEvent(new Event("hi"));
+ }, "window.event for constructors from another global: " + constructorName);
+});
+
+// XXX: It would be good to test a subclass of EventTarget once we sort out
+// https://github.com/heycam/webidl/issues/540
+
+async_test(t => {
+ const element = document.body.appendChild(otherWindow.document.createElement("meh"));
+ element.addEventListener("yo", t.step_func_done(e => {
+ assert_equals(e, window.event);
+ }));
+ element.dispatchEvent(new Event("yo"));
+}, "window.event and element from another document");
+
+async_test(t => {
+ const doc = otherWindow.document,
+ element = doc.body.appendChild(doc.createElement("meh")),
+ child = element.appendChild(doc.createElement("bleh"));
+ element.addEventListener("yoyo", t.step_func(e => {
+ document.body.appendChild(element);
+ assert_equals(element.ownerDocument, document);
+ assert_equals(window.event, e);
+ assert_equals(otherWindow.event, undefined);
+ }), true);
+ element.addEventListener("yoyo", t.step_func(e => {
+ assert_equals(element.ownerDocument, document);
+ assert_equals(window.event, e);
+ assert_equals(otherWindow.event, undefined);
+ }), true);
+ child.addEventListener("yoyo", t.step_func_done(e => {
+ assert_equals(child.ownerDocument, document);
+ assert_equals(window.event, e);
+ assert_equals(otherWindow.event, undefined);
+ }));
+ child.dispatchEvent(new Event("yoyo"));
+}, "window.event and moving an element post-dispatch");
+
+test(t => {
+ const host = document.createElement("div"),
+ shadow = host.attachShadow({ mode: "open" }),
+ child = shadow.appendChild(document.createElement("trala")),
+ furtherChild = child.appendChild(document.createElement("waddup"));
+ let counter = 0;
+ host.addEventListener("hi", t.step_func(e => {
+ assert_equals(window.event, e);
+ assert_equals(counter++, 3);
+ }));
+ child.addEventListener("hi", t.step_func(e => {
+ assert_equals(window.event, undefined);
+ assert_equals(counter++, 2);
+ }));
+ furtherChild.addEventListener("hi", t.step_func(e => {
+ host.appendChild(child);
+ assert_equals(window.event, undefined);
+ assert_equals(counter++, 0);
+ }));
+ furtherChild.addEventListener("hi", t.step_func(e => {
+ assert_equals(window.event, undefined);
+ assert_equals(counter++, 1);
+ }));
+ furtherChild.dispatchEvent(new Event("hi", { composed: true, bubbles: true }));
+ assert_equals(counter, 4);
+}, "window.event should not be affected by nodes moving post-dispatch");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ frame.src = "resources/event-global-extra-frame.html";
+ frame.onload = t.step_func_done((load_event) => {
+ const event = new Event("hi");
+ document.addEventListener("hi", frame.contentWindow.listener); // listener intentionally not wrapped in t.step_func
+ document.addEventListener("hi", t.step_func(e => {
+ assert_equals(event, e);
+ assert_equals(window.event, e);
+ }));
+ document.dispatchEvent(event);
+ assert_equals(frameState.event, event);
+ assert_equals(frameState.windowEvent, event);
+ assert_equals(frameState.parentEvent, load_event);
+ });
+}, "Listener from a different global");
diff --git a/testing/web-platform/tests/dom/events/event-global-is-still-set-when-coercing-beforeunload-result.html b/testing/web-platform/tests/dom/events/event-global-is-still-set-when-coercing-beforeunload-result.html
new file mode 100644
index 0000000000..a64c8b6b8b
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/event-global-is-still-set-when-coercing-beforeunload-result.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>window.event is still set when 'beforeunload' result is coerced to string</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#ref-for-window-current-event%E2%91%A1">
+<link rel="help" href="https://webidl.spec.whatwg.org/#call-a-user-objects-operation">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe id="iframe" src="resources/event-global-is-still-set-when-coercing-beforeunload-result-frame.html"></iframe>
+<body>
+<script>
+window.onload = () => {
+ async_test(t => {
+ iframe.onload = t.step_func_done(() => {
+ assert_equals(typeof window.currentEventInToString, "object");
+ assert_equals(window.currentEventInToString.type, "beforeunload");
+ });
+
+ iframe.contentWindow.location.href = "about:blank";
+ });
+};
+</script>
diff --git a/testing/web-platform/tests/dom/events/event-global-is-still-set-when-reporting-exception-onerror.html b/testing/web-platform/tests/dom/events/event-global-is-still-set-when-reporting-exception-onerror.html
new file mode 100644
index 0000000000..ceaac4fe2b
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/event-global-is-still-set-when-reporting-exception-onerror.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.onerror handler restores window.event after it reports an exception</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>
+
+<body>
+<iframe src="resources/empty-document.html"></iframe>
+<iframe src="resources/empty-document.html"></iframe>
+
+<script>
+setup({ allow_uncaught_exception: true });
+
+async_test(t => {
+ window.onload = t.step_func_done(onLoadEvent => {
+ frames[0].onerror = new frames[1].Function(`
+ top.eventDuringSecondOnError = top.window.event;
+ top.frames[0].eventDuringSecondOnError = top.frames[0].event;
+ top.frames[1].eventDuringSecondOnError = top.frames[1].event;
+ `);
+
+ window.onerror = new frames[0].Function(`
+ top.eventDuringFirstOnError = top.window.event;
+ top.frames[0].eventDuringFirstOnError = top.frames[0].event;
+ top.frames[1].eventDuringFirstOnError = top.frames[1].event;
+
+ foo; // cause second onerror
+ `);
+
+ const myEvent = new ErrorEvent("error", { error: new Error("myError") });
+ window.dispatchEvent(myEvent);
+
+ assert_equals(top.eventDuringFirstOnError, onLoadEvent);
+ assert_equals(frames[0].eventDuringFirstOnError, myEvent);
+ assert_equals(frames[1].eventDuringFirstOnError, undefined);
+
+ assert_equals(top.eventDuringSecondOnError, onLoadEvent);
+ assert_equals(frames[0].eventDuringSecondOnError, myEvent);
+ assert_equals(frames[1].eventDuringSecondOnError.error.name, "ReferenceError");
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/dom/events/event-global-set-before-handleEvent-lookup.window.js b/testing/web-platform/tests/dom/events/event-global-set-before-handleEvent-lookup.window.js
new file mode 100644
index 0000000000..8f934bcea9
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/event-global-set-before-handleEvent-lookup.window.js
@@ -0,0 +1,19 @@
+// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke (steps 8.2 - 12)
+// https://webidl.spec.whatwg.org/#call-a-user-objects-operation (step 10.1)
+
+test(() => {
+ const eventTarget = new EventTarget;
+
+ let currentEvent;
+ eventTarget.addEventListener("foo", {
+ get handleEvent() {
+ currentEvent = window.event;
+ return () => {};
+ }
+ });
+
+ const event = new Event("foo");
+ eventTarget.dispatchEvent(event);
+
+ assert_equals(currentEvent, event);
+}, "window.event is set before 'handleEvent' lookup");
diff --git a/testing/web-platform/tests/dom/events/event-global.html b/testing/web-platform/tests/dom/events/event-global.html
new file mode 100644
index 0000000000..3e8d25ecb5
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/event-global.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<title>window.event tests</title>
+<link rel="author" title="Mike Taylor" href="mailto:miketaylr@gmail.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+setup({allow_uncaught_exception: true});
+
+test(t => {
+ assert_own_property(window, "event");
+ assert_equals(window.event, undefined);
+}, "event exists on window, which is initially set to undefined");
+
+async_test(t => {
+ let target = document.createElement("div");
+ assert_equals(window.event, undefined, "undefined before dispatch");
+
+ let clickEvent = new Event("click");
+ target.addEventListener("click", t.step_func_done(e => {
+ assert_equals(window.event, clickEvent, "window.event set to current event during dispatch");
+ }));
+
+ target.dispatchEvent(clickEvent);
+ assert_equals(window.event, undefined, "undefined after dispatch");
+}, "window.event is only defined during dispatch");
+
+async_test(t => {
+ let parent = document.createElement("div");
+ let root = parent.attachShadow({mode: "closed"});
+ let span = document.createElement("span");
+ root.appendChild(span);
+
+ span.addEventListener("test", t.step_func(e => {
+ assert_equals(window.event, undefined);
+ assert_not_equals(window.event, e);
+ }));
+
+ parent.addEventListener("test", t.step_func_done(e => {
+ assert_equals(window.event, e);
+ assert_not_equals(window.event, undefined);
+ }));
+
+ parent.dispatchEvent(new Event("test", {composed: true}));
+}, "window.event is undefined if the target is in a shadow tree (event dispatched outside shadow tree)");
+
+async_test(t => {
+ let parent = document.createElement("div");
+ let root = parent.attachShadow({mode: "closed"});
+ let span = document.createElement("span");
+ root.appendChild(span);
+ let shadowNode = root.firstElementChild;
+
+ shadowNode.addEventListener("test", t.step_func((e) => {
+ assert_not_equals(window.event, e);
+ assert_equals(window.event, undefined);
+ }));
+
+ parent.addEventListener("test", t.step_func_done(e => {
+ assert_equals(window.event, e);
+ assert_not_equals(window.event, undefined);
+ }));
+
+ shadowNode.dispatchEvent(new Event("test", {composed: true, bubbles: true}));
+}, "window.event is undefined if the target is in a shadow tree (event dispatched inside shadow tree)");
+
+async_test(t => {
+ let parent = document.createElement("div");
+ let root = parent.attachShadow({mode: "open"});
+ document.body.append(parent)
+ let span = document.createElement("span");
+ root.append(span);
+ let shadowNode = root.firstElementChild;
+
+ shadowNode.addEventListener("error", t.step_func(e => {
+ assert_not_equals(window.event, e);
+ assert_equals(window.event, undefined);
+ }));
+
+ let windowOnErrorCalled = false;
+ window.onerror = t.step_func_done(() => {
+ windowOnErrorCalled = true;
+ assert_equals(typeof window.event, "object");
+ assert_equals(window.event.type, "error");
+ });
+
+ shadowNode.dispatchEvent(new ErrorEvent("error", {composed: true, bubbles: true}));
+ assert_true(windowOnErrorCalled);
+}, "window.event is undefined inside window.onerror if the target is in a shadow tree (ErrorEvent dispatched inside shadow tree)");
+
+async_test(t => {
+ let target1 = document.createElement("div");
+ let target2 = document.createElement("div");
+
+ target2.addEventListener("dude", t.step_func(() => {
+ assert_equals(window.event.type, "dude");
+ }));
+
+ target1.addEventListener("cool", t.step_func_done(() => {
+ assert_equals(window.event.type, "cool", "got expected event from global event during dispatch");
+ target2.dispatchEvent(new Event("dude"));
+ assert_equals(window.event.type, "cool", "got expected event from global event after handling a different event handler callback");
+ }));
+
+ target1.dispatchEvent(new Event("cool"));
+}, "window.event is set to the current event during dispatch");
+
+async_test(t => {
+ let target = document.createElement("div");
+
+ target.addEventListener("click", t.step_func_done(e => {
+ assert_equals(e, window.event);
+ }));
+
+ target.dispatchEvent(new Event("click"));
+}, "window.event is set to the current event, which is the event passed to dispatch");
+</script>
diff --git a/testing/web-platform/tests/dom/events/event-global.worker.js b/testing/web-platform/tests/dom/events/event-global.worker.js
new file mode 100644
index 0000000000..116cf32932
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/event-global.worker.js
@@ -0,0 +1,14 @@
+importScripts("/resources/testharness.js");
+test(t => {
+ let seen = false;
+ const event = new Event("hi");
+ assert_equals(self.event, undefined);
+ self.addEventListener("hi", t.step_func(e => {
+ seen = true;
+ assert_equals(self.event, undefined);
+ assert_equals(e, event);
+ }));
+ self.dispatchEvent(event);
+ assert_true(seen);
+}, "There's no self.event (that's why we call it window.event) in workers");
+done();
diff --git a/testing/web-platform/tests/dom/events/focus-event-document-move.html b/testing/web-platform/tests/dom/events/focus-event-document-move.html
new file mode 100644
index 0000000000..2943761ce1
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/focus-event-document-move.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://crbug.com/747207">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script>
+ function handleDown(node) {
+ var d2 = new Document();
+ d2.appendChild(node);
+ }
+</script>
+
+<!-- No crash should occur if a node is moved during mousedown. -->
+<div id='click' onmousedown='handleDown(this)'>Click me</div>
+
+<script>
+ const target = document.getElementById('click');
+ async_test(t => {
+ let actions = new test_driver.Actions()
+ .pointerMove(0, 0, {origin: target})
+ .pointerDown()
+ .pointerUp()
+ .send()
+ .then(t.step_func_done(() => {
+ assert_equals(null,document.getElementById('click'));
+ }))
+ .catch(e => t.step_func(() => assert_unreached('Error')));
+ },'Moving a node during mousedown should not crash');
+</script>
diff --git a/testing/web-platform/tests/dom/events/keypress-dispatch-crash.html b/testing/web-platform/tests/dom/events/keypress-dispatch-crash.html
new file mode 100644
index 0000000000..3207adbd8c
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/keypress-dispatch-crash.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<link rel="author" title="Robert Flack" href="mailto:flackr@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1209098">
+
+<!-- No crash should occur if a keypress is dispatched to a constructed document. -->
+
+<script>
+var newDoc = document.implementation.createDocument( "", null);
+var testNode = newDoc.createElement('div');
+newDoc.append(testNode);
+
+var syntheticEvent = document.createEvent('KeyboardEvents');
+syntheticEvent.initKeyboardEvent("keypress");
+testNode.dispatchEvent(syntheticEvent)
+</script>
diff --git a/testing/web-platform/tests/dom/events/legacy-pre-activation-behavior.window.js b/testing/web-platform/tests/dom/events/legacy-pre-activation-behavior.window.js
new file mode 100644
index 0000000000..e9e84bfad1
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/legacy-pre-activation-behavior.window.js
@@ -0,0 +1,10 @@
+test(t => {
+ const input = document.body.appendChild(document.createElement('input'));
+ input.type = "radio";
+ t.add_cleanup(() => input.remove());
+ const clickEvent = new MouseEvent('click', { button: 0, which: 1 });
+ input.addEventListener('change', t.step_func(() => {
+ assert_equals(clickEvent.eventPhase, Event.NONE);
+ }));
+ input.dispatchEvent(clickEvent);
+}, "Use NONE phase during legacy-pre-activation behavior");
diff --git a/testing/web-platform/tests/dom/events/mouse-event-retarget.html b/testing/web-platform/tests/dom/events/mouse-event-retarget.html
new file mode 100644
index 0000000000..c9ce6240d4
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/mouse-event-retarget.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<title>Script created MouseEvent properly retargets and adjusts offsetX</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+body {
+ margin: 8px;
+ padding: 0;
+}
+</style>
+
+<div id="target">Hello</div>
+
+<script>
+async_test(t => {
+ target.addEventListener('click', ev => {
+ t.step(() => assert_equals(ev.offsetX, 42));
+ t.done();
+ });
+
+ const ev = new MouseEvent('click', { clientX: 50 });
+ target.dispatchEvent(ev);
+}, "offsetX is correctly adjusted");
+</script>
diff --git a/testing/web-platform/tests/dom/events/no-focus-events-at-clicking-editable-content-in-link.html b/testing/web-platform/tests/dom/events/no-focus-events-at-clicking-editable-content-in-link.html
new file mode 100644
index 0000000000..dc08636c46
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/no-focus-events-at-clicking-editable-content-in-link.html
@@ -0,0 +1,80 @@
+<!doctype html>
+<html>
+<head>
+<meta chareset="utf-8">
+<title>Clicking editable content in link shouldn't cause redundant focus related events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+</head>
+<body>
+<a href="#"><span contenteditable>Hello</span></a>
+<a href="#" contenteditable><span>Hello</span></a>
+<script>
+function promiseTicks() {
+ return new Promise(resolve => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(resolve);
+ });
+ });
+}
+
+async function clickElementAndCollectFocusEvents(x, y, options) {
+ await promiseTicks();
+ let events = [];
+ for (const eventType of ["focus", "blur", "focusin", "focusout"]) {
+ document.addEventListener(eventType, event => {
+ events.push(`type: ${event.type}, target: ${event.target.nodeName}`);
+ }, {capture: true});
+ }
+
+ const waitForClickEvent = new Promise(resolve => {
+ addEventListener("click", resolve, {capture: true, once: true});
+ });
+
+ await new test_driver
+ .Actions()
+ .pointerMove(x, y, options)
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ await waitForClickEvent;
+ await promiseTicks();
+ return events;
+}
+
+promise_test(async t => {
+ document.activeElement?.blur();
+ const editingHost = document.querySelector("span[contenteditable]");
+ editingHost.blur();
+ const focusEvents =
+ await clickElementAndCollectFocusEvents(5, 5, {origin: editingHost});
+ assert_array_equals(
+ focusEvents,
+ [
+ "type: focus, target: SPAN",
+ "type: focusin, target: SPAN",
+ ],
+ "Click event shouldn't cause redundant focus events");
+}, "Click editable element in link");
+
+promise_test(async t => {
+ document.activeElement?.blur();
+ const editingHost = document.querySelector("a[contenteditable]");
+ editingHost.blur();
+ const focusEvents =
+ await clickElementAndCollectFocusEvents(5, 5, {origin: editingHost});
+ assert_array_equals(
+ focusEvents,
+ [
+ "type: focus, target: A",
+ "type: focusin, target: A",
+ ],
+ "Click event shouldn't cause redundant focus events");
+}, "Click editable link");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-body.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-body.html
new file mode 100644
index 0000000000..5574fe0acb
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-body.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>non-passive mousewheel event listener on body</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<link rel="help" href="https://github.com/w3c/uievents/issues/331">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.body,
+ eventName: 'mousewheel',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-div.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-div.html
new file mode 100644
index 0000000000..6fbf692cd7
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-div.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>non-passive mousewheel event listener on div</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<link rel="help" href="https://github.com/w3c/uievents/issues/331">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<style>
+ html, body {
+ overflow: hidden;
+ margin: 0;
+ }
+ #div {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ overflow: scroll;
+ }
+</style>
+<div class=remove-on-cleanup id=div>
+ <div style="height: 200vh"></div>
+</div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.getElementById('div'),
+ eventName: 'mousewheel',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-document.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-document.html
new file mode 100644
index 0000000000..7d07393c69
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-document.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>non-passive mousewheel event listener on document</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<link rel="help" href="https://github.com/w3c/uievents/issues/331">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document,
+ eventName: 'mousewheel',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-root.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-root.html
new file mode 100644
index 0000000000..e85fbacaba
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-root.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>non-passive mousewheel event listener on root</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<link rel="help" href="https://github.com/w3c/uievents/issues/331">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.documentElement,
+ eventName: 'mousewheel',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-window.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-window.html
new file mode 100644
index 0000000000..29b09f8561
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-window.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>non-passive mousewheel event listener on window</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<link rel="help" href="https://github.com/w3c/uievents/issues/331">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: window,
+ eventName: 'mousewheel',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-body.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-body.html
new file mode 100644
index 0000000000..f417bdd0a6
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-body.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>non-passive touchmove event listener on body</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.body,
+ eventName: 'touchmove',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-div.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-div.html
new file mode 100644
index 0000000000..11c9345407
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-div.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>non-passive touchmove event listener on div</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.getElementById('touchDiv'),
+ eventName: 'touchmove',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-document.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-document.html
new file mode 100644
index 0000000000..8b95a8d492
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-document.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>non-passive touchmove event listener on document</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document,
+ eventName: 'touchmove',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-root.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-root.html
new file mode 100644
index 0000000000..c41ab72bd8
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-root.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>non-passive touchmove event listener on root</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.documentElement,
+ eventName: 'touchmove',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-window.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-window.html
new file mode 100644
index 0000000000..3d6675c566
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchmove-event-listener-on-window.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<link rel="help" href="https://github.com/WICG/interventions/issues/35">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: window,
+ eventName: 'touchstart',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-body.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-body.html
new file mode 100644
index 0000000000..f6e6ecb06d
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-body.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>non-passive touchstart event listener on body</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.body,
+ eventName: 'touchstart',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-div.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-div.html
new file mode 100644
index 0000000000..2e7c6e6b3b
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-div.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>non-passive touchstart event listener on div</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.getElementById('touchDiv'),
+ eventName: 'touchstart',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-document.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-document.html
new file mode 100644
index 0000000000..22fcbdc322
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-document.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>non-passive touchstart event listener on document</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document,
+ eventName: 'touchstart',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-root.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-root.html
new file mode 100644
index 0000000000..56c51349a0
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-root.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>non-passive touchstart event listener on root</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.documentElement,
+ eventName: 'touchstart',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-window.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-window.html
new file mode 100644
index 0000000000..4e9d424a9d
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-touchstart-event-listener-on-window.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>non-passive touchstart event listener on window</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: window,
+ eventName: 'touchstart',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-body.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-body.html
new file mode 100644
index 0000000000..070cadc291
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-body.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>non-passive wheel event listener on body</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.body,
+ eventName: 'wheel',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-div.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-div.html
new file mode 100644
index 0000000000..c49d18ac13
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-div.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>non-passive wheel event listener on div</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<style>
+ html, body {
+ overflow: hidden;
+ margin: 0;
+ }
+ #div {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ overflow: scroll;
+ }
+</style>
+<div class=remove-on-cleanup id=div>
+ <div style="height: 200vh"></div>
+</div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.getElementById('div'),
+ eventName: 'wheel',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-document.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-document.html
new file mode 100644
index 0000000000..31a55cad43
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-document.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>non-passive wheel event listener on document</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document,
+ eventName: 'wheel',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-root.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-root.html
new file mode 100644
index 0000000000..b7bacbfc7c
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-root.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>non-passive wheel event listener on root</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.documentElement,
+ eventName: 'wheel',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-window.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-window.html
new file mode 100644
index 0000000000..c236059df4
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-window.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>non-passive wheel event listener on window</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: window,
+ eventName: 'wheel',
+ passive: false,
+ expectCancelable: true,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-body.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-body.html
new file mode 100644
index 0000000000..9db12cfbdc
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-body.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>passive mousewheel event listener on body</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<link rel="help" href="https://github.com/w3c/uievents/issues/331">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.body,
+ eventName: 'mousewheel',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-div.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-div.html
new file mode 100644
index 0000000000..373670856b
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-div.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>passive mousewheel event listener on div</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<link rel="help" href="https://github.com/w3c/uievents/issues/331">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<style>
+ html, body {
+ overflow: hidden;
+ margin: 0;
+ }
+ #div {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ overflow: scroll;
+ }
+</style>
+<div class=remove-on-cleanup id=div>
+ <div style="height: 200vh"></div>
+</div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.getElementById('div'),
+ eventName: 'mousewheel',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-document.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-document.html
new file mode 100644
index 0000000000..71262280b6
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-document.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>passive mousewheel event listener on document</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<link rel="help" href="https://github.com/w3c/uievents/issues/331">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document,
+ eventName: 'mousewheel',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-root.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-root.html
new file mode 100644
index 0000000000..fc641d172e
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-root.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>passive mousewheel event listener on root</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<link rel="help" href="https://github.com/w3c/uievents/issues/331">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.documentElement,
+ eventName: 'mousewheel',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-window.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-window.html
new file mode 100644
index 0000000000..f60955c7c4
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-mousewheel-event-listener-on-window.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>passive mousewheel event listener on window</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<link rel="help" href="https://github.com/w3c/uievents/issues/331">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: window,
+ eventName: 'mousewheel',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-body.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-body.html
new file mode 100644
index 0000000000..2349bad258
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-body.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>passive touchmove event listener on body</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.body,
+ eventName: 'touchmove',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-div.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-div.html
new file mode 100644
index 0000000000..a61b34851e
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-div.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>passive touchmove event listener on div</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.getElementById('touchDiv'),
+ eventName: 'touchmove',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-document.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-document.html
new file mode 100644
index 0000000000..b49971b5b0
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-document.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>passive touchmove event listener on document</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document,
+ eventName: 'touchmove',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-root.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-root.html
new file mode 100644
index 0000000000..b851704590
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-root.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>passive touchmove event listener on root</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.documentElement,
+ eventName: 'touchmove',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-window.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-window.html
new file mode 100644
index 0000000000..351d6ace84
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchmove-event-listener-on-window.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>passive touchmove event listener on window</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: window,
+ eventName: 'touchstart',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-body.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-body.html
new file mode 100644
index 0000000000..c3d2b577fd
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-body.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>passive touchstart event listener on body</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.body,
+ eventName: 'touchstart',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-div.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-div.html
new file mode 100644
index 0000000000..103e7f0d23
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-div.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>passive touchstart event listener on div</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.getElementById('touchDiv'),
+ eventName: 'touchstart',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-document.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-document.html
new file mode 100644
index 0000000000..2e4de2405f
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-document.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>passive touchstart event listener on document</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document,
+ eventName: 'touchstart',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-root.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-root.html
new file mode 100644
index 0000000000..0f52e9a16f
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-root.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>passive touchstart event listener on root</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.documentElement,
+ eventName: 'touchstart',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-window.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-window.html
new file mode 100644
index 0000000000..c47af8101f
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-touchstart-event-listener-on-window.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>passive touchstart event listener on window</title>
+<link rel="help" href="https://w3c.github.io/touch-events/#cancelability">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/wait-for.js"></script>
+<script src="resources/touching.js"></script>
+<style>
+#touchDiv {
+ width: 100px;
+ height: 100px;
+}
+</style>
+<div id="touchDiv"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: window,
+ eventName: 'touchstart',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-body.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-body.html
new file mode 100644
index 0000000000..fe0869b022
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-body.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>passive wheel event listener on body</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.body,
+ eventName: 'wheel',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-div.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-div.html
new file mode 100644
index 0000000000..e2ca6e795a
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-div.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>passive wheel event listener on div</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<style>
+ html, body {
+ overflow: hidden;
+ margin: 0;
+ }
+ #div {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ overflow: scroll;
+ }
+</style>
+<div class=remove-on-cleanup id=div>
+ <div style="height: 200vh"></div>
+</div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.getElementById('div'),
+ eventName: 'wheel',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-document.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-document.html
new file mode 100644
index 0000000000..61b716f7bb
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-document.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>passive wheel event listener on document</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document,
+ eventName: 'wheel',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-root.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-root.html
new file mode 100644
index 0000000000..6b383bc871
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-root.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>passive wheel event listener on root</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: document.documentElement,
+ eventName: 'wheel',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-window.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-window.html
new file mode 100644
index 0000000000..a1e901f552
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/passive-wheel-event-listener-on-window.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>passive wheel event listener on window</title>
+<link rel="help" href="https://w3c.github.io/uievents/#cancelability-of-wheel-events">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/scrolling.js"></script>
+<div class=remove-on-cleanup style="height: 200vh"></div>
+<script>
+ document.body.onload = () => runTest({
+ target: window,
+ eventName: 'wheel',
+ passive: true,
+ expectCancelable: false,
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/resources/scrolling.js b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/resources/scrolling.js
new file mode 100644
index 0000000000..88e10f5efd
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/resources/scrolling.js
@@ -0,0 +1,34 @@
+function raf() {
+ return new Promise((resolve) => {
+ // rAF twice.
+ window.requestAnimationFrame(() => {
+ window.requestAnimationFrame(resolve);
+ });
+ });
+}
+
+async function runTest({target, eventName, passive, expectCancelable}) {
+ await raf();
+
+ let cancelable = null;
+ let arrived = false;
+ target.addEventListener(eventName, function (event) {
+ cancelable = event.cancelable;
+ arrived = true;
+ }, {passive:passive, once:true});
+
+ promise_test(async (t) => {
+ t.add_cleanup(() => {
+ document.querySelector('.remove-on-cleanup')?.remove();
+ });
+ const pos_x = Math.floor(window.innerWidth / 2);
+ const pos_y = Math.floor(window.innerHeight / 2);
+ const delta_x = 0;
+ const delta_y = 100;
+
+ await new test_driver.Actions()
+ .scroll(pos_x, pos_y, delta_x, delta_y).send();
+ await t.step_wait(() => arrived, `Didn't get event ${eventName} on ${target.localName}`);
+ assert_equals(cancelable, expectCancelable);
+ });
+}
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/resources/touching.js b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/resources/touching.js
new file mode 100644
index 0000000000..620d26804b
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/resources/touching.js
@@ -0,0 +1,34 @@
+function waitForCompositorCommit() {
+ return new Promise((resolve) => {
+ // rAF twice.
+ window.requestAnimationFrame(() => {
+ window.requestAnimationFrame(resolve);
+ });
+ });
+}
+
+function injectInput(touchDiv) {
+ return new test_driver.Actions()
+ .addPointer("touch_pointer", "touch")
+ .pointerMove(0, 0, {origin: touchDiv})
+ .pointerDown()
+ .pointerMove(30, 30)
+ .pointerUp()
+ .send();
+}
+
+function runTest({target, eventName, passive, expectCancelable}) {
+ let touchDiv = document.getElementById("touchDiv");
+ let cancelable = null;
+ let arrived = false;
+ target.addEventListener(eventName, function (event) {
+ cancelable = event.cancelable;
+ arrived = true;
+ }, {passive});
+ promise_test(async () => {
+ await waitForCompositorCommit();
+ await injectInput(touchDiv);
+ await waitFor(() => arrived);
+ assert_equals(cancelable, expectCancelable);
+ });
+}
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/resources/wait-for.js b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/resources/wait-for.js
new file mode 100644
index 0000000000..0bf3e55834
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/resources/wait-for.js
@@ -0,0 +1,15 @@
+function waitFor(condition, MAX_FRAME = 500) {
+ return new Promise((resolve, reject) => {
+ function tick(frames) {
+ // We requestAnimationFrame either for MAX_FRAME frames or until condition is
+ // met.
+ if (frames >= MAX_FRAME)
+ reject(new Error(`Condition did not become true after ${MAX_FRAME} frames`));
+ else if (condition())
+ resolve();
+ else
+ requestAnimationFrame(() => tick(frames + 1));
+ }
+ tick(0);
+ });
+}
diff --git a/testing/web-platform/tests/dom/events/non-cancelable-when-passive/synthetic-events-cancelable.html b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/synthetic-events-cancelable.html
new file mode 100644
index 0000000000..4287770b8d
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/non-cancelable-when-passive/synthetic-events-cancelable.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Synthetic events are always cancelable by default</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dictdef-eventinit">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const eventsMap = {
+ wheel: 'WheelEvent',
+ mousewheel: 'WheelEvent',
+ touchstart: 'TouchEvent',
+ touchmove: 'TouchEvent',
+ touchend: 'TouchEvent',
+ touchcancel: 'TouchEvent',
+}
+function isCancelable(eventName, interfaceName) {
+ test(() => {
+ assert_implements(interfaceName in self, `${interfaceName} should be supported`);
+ let defaultPrevented = null;
+ addEventListener(eventName, event => {
+ event.preventDefault();
+ defaultPrevented = event.defaultPrevented;
+ });
+ const event = new self[interfaceName](eventName);
+ assert_false(event.cancelable, 'cancelable');
+ const dispatchEventReturnValue = dispatchEvent(event);
+ assert_false(defaultPrevented, 'defaultPrevented');
+ assert_true(dispatchEventReturnValue, 'dispatchEvent() return value');
+ }, `Synthetic ${eventName} event with interface ${interfaceName} is not cancelable`);
+}
+for (const eventName in eventsMap) {
+ isCancelable(eventName, eventsMap[eventName]);
+ isCancelable(eventName, 'Event');
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/passive-by-default.html b/testing/web-platform/tests/dom/events/passive-by-default.html
new file mode 100644
index 0000000000..02029f4dac
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/passive-by-default.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>Default passive event listeners on window, document, document element, body</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#default-passive-value">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+ <div id="div"></div>
+<script>
+ function isListenerPassive(eventName, eventTarget, passive, expectPassive) {
+ test(() => {
+ let defaultPrevented = null;
+ let handler = event => {
+ event.preventDefault();
+ defaultPrevented = event.defaultPrevented;
+ eventTarget.removeEventListener(eventName, handler);
+ };
+ if (passive === 'omitted') {
+ eventTarget.addEventListener(eventName, handler);
+ } else {
+ eventTarget.addEventListener(eventName, handler, {passive});
+ }
+ let dispatchEventReturnValue = eventTarget.dispatchEvent(new Event(eventName, {cancelable: true}));
+ assert_equals(defaultPrevented, !expectPassive, 'defaultPrevented');
+ assert_equals(dispatchEventReturnValue, expectPassive, 'dispatchEvent() return value');
+ }, `${eventName} listener is ${expectPassive ? '' : 'non-'}passive ${passive === 'omitted' ? 'by default' : `with {passive:${passive}}`} for ${eventTarget.constructor.name}`);
+ }
+
+ const eventNames = {
+ touchstart: true,
+ touchmove: true,
+ wheel: true,
+ mousewheel: true,
+ touchend: false
+ };
+ const passiveEventTargets = [window, document, document.documentElement, document.body];
+ const div = document.getElementById('div');
+
+ for (const eventName in eventNames) {
+ for (const eventTarget of passiveEventTargets) {
+ isListenerPassive(eventName, eventTarget, 'omitted', eventNames[eventName]);
+ isListenerPassive(eventName, eventTarget, undefined, eventNames[eventName]);
+ isListenerPassive(eventName, eventTarget, false, false);
+ isListenerPassive(eventName, eventTarget, true, true);
+ }
+ isListenerPassive(eventName, div, 'omitted', false);
+ isListenerPassive(eventName, div, undefined, false);
+ isListenerPassive(eventName, div, false, false);
+ isListenerPassive(eventName, div, true, true);
+ }
+</script>
diff --git a/testing/web-platform/tests/dom/events/preventDefault-during-activation-behavior.html b/testing/web-platform/tests/dom/events/preventDefault-during-activation-behavior.html
new file mode 100644
index 0000000000..9287403134
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/preventDefault-during-activation-behavior.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<title>preventDefault during activation behavior</title>
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1197032">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/C#the-button-element">
+<link rel="help" href="https://dom.spec.whatwg.org/#dispatching-events">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Google" href="http://www.google.com/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+
+<form id="f">
+ <input type="submit" id="b">
+</form>
+
+<script>
+
+promise_test(async () => {
+ let form = document.getElementById("f");
+ let button = document.getElementById("b");
+
+ let cached_event;
+ let submit_fired = false;
+
+ let click_promise = new Promise((resolve, reject) => {
+ button.addEventListener("click", event => {
+ assert_false(submit_fired);
+ cached_event = event;
+ resolve();
+ });
+ });
+
+ form.addEventListener("submit", event => {
+ assert_false(submit_fired);
+ assert_true(!!cached_event, "click should have fired");
+
+ // Call preventDefault on the click event during its activation
+ // behavior, to test the bug that we're trying to test.
+ cached_event.preventDefault();
+
+ // prevent the form submission from navigating the page
+ event.preventDefault();
+
+ submit_fired = true;
+ });
+
+ assert_false(submit_fired);
+ button.click();
+ await click_promise;
+ assert_true(submit_fired);
+}, "behavior of preventDefault during activation behavior");
+
+</script>
diff --git a/testing/web-platform/tests/dom/events/relatedTarget.window.js b/testing/web-platform/tests/dom/events/relatedTarget.window.js
new file mode 100644
index 0000000000..ebc83ceb20
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/relatedTarget.window.js
@@ -0,0 +1,81 @@
+// https://dom.spec.whatwg.org/#concept-event-dispatch
+
+const host = document.createElement("div"),
+ child = host.appendChild(document.createElement("p")),
+ shadow = host.attachShadow({ mode: "closed" }),
+ slot = shadow.appendChild(document.createElement("slot"));
+
+test(() => {
+ for (target of [shadow, slot]) {
+ for (relatedTarget of [new XMLHttpRequest(), self, host]) {
+ const event = new FocusEvent("demo", { relatedTarget: relatedTarget });
+ target.dispatchEvent(event);
+ assert_equals(event.target, null);
+ assert_equals(event.relatedTarget, null);
+ }
+ }
+}, "Reset if target pointed to a shadow tree");
+
+test(() => {
+ for (relatedTarget of [shadow, slot]) {
+ for (target of [new XMLHttpRequest(), self, host]) {
+ const event = new FocusEvent("demo", { relatedTarget: relatedTarget });
+ target.dispatchEvent(event);
+ assert_equals(event.target, target);
+ assert_equals(event.relatedTarget, host);
+ }
+ }
+}, "Retarget a shadow-tree relatedTarget");
+
+test(t => {
+ const shadowChild = shadow.appendChild(document.createElement("div"));
+ shadowChild.addEventListener("demo", t.step_func(() => document.body.appendChild(shadowChild)));
+ const event = new FocusEvent("demo", { relatedTarget: new XMLHttpRequest() });
+ shadowChild.dispatchEvent(event);
+ assert_equals(shadowChild.parentNode, document.body);
+ assert_equals(event.target, null);
+ assert_equals(event.relatedTarget, null);
+ shadowChild.remove();
+}, "Reset if target pointed to a shadow tree pre-dispatch");
+
+test(t => {
+ const shadowChild = shadow.appendChild(document.createElement("div"));
+ document.body.addEventListener("demo", t.step_func(() => document.body.appendChild(shadowChild)));
+ const event = new FocusEvent("demo", { relatedTarget: shadowChild });
+ document.body.dispatchEvent(event);
+ assert_equals(shadowChild.parentNode, document.body);
+ assert_equals(event.target, document.body);
+ assert_equals(event.relatedTarget, host);
+ shadowChild.remove();
+}, "Retarget a shadow-tree relatedTarget, part 2");
+
+test(t => {
+ const event = new FocusEvent("heya", { relatedTarget: shadow, cancelable: true }),
+ callback = t.unreached_func();
+ host.addEventListener("heya", callback);
+ t.add_cleanup(() => host.removeEventListener("heya", callback));
+ event.preventDefault();
+ assert_true(event.defaultPrevented);
+ assert_false(host.dispatchEvent(event));
+ assert_equals(event.target, null);
+ assert_equals(event.relatedTarget, null);
+ // Check that the dispatch flag is cleared
+ event.initEvent("x");
+ assert_equals(event.type, "x");
+}, "Reset targets on early return");
+
+test(t => {
+ const input = document.body.appendChild(document.createElement("input")),
+ event = new MouseEvent("click", { relatedTarget: shadow });
+ let seen = false;
+ t.add_cleanup(() => input.remove());
+ input.type = "checkbox";
+ input.oninput = t.step_func(() => {
+ assert_equals(event.target, null);
+ assert_equals(event.relatedTarget, null);
+ assert_equals(event.composedPath().length, 0);
+ seen = true;
+ });
+ assert_true(input.dispatchEvent(event));
+ assert_true(seen);
+}, "Reset targets before activation behavior");
diff --git a/testing/web-platform/tests/dom/events/replace-event-listener-null-browsing-context-crash.html b/testing/web-platform/tests/dom/events/replace-event-listener-null-browsing-context-crash.html
new file mode 100644
index 0000000000..f41955eedd
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/replace-event-listener-null-browsing-context-crash.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Event listeners: replace listener after moving between documents</title>
+<link rel="author" title="Nate Chapin" href="mailto:japhet@chromium.org">
+<link rel="help" href="https://w3c.github.io/touch-events/#dom-globaleventhandlers-ontouchcancel">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1083793">
+<meta name="assert" content="Overwriting an attribute event listener after adopting the owning node to a different document should not crash"/>
+<progress id="p">
+<iframe id="i"></iframe>
+<script>
+var p = document.getElementById("p");
+i.contentDocument.adoptNode(p);
+p.setAttribute("ontouchcancel", "");
+document.body.appendChild(p);
+p.setAttribute("ontouchcancel", "");
+</script>
+
diff --git a/testing/web-platform/tests/dom/events/resources/empty-document.html b/testing/web-platform/tests/dom/events/resources/empty-document.html
new file mode 100644
index 0000000000..b9cd130a07
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/resources/empty-document.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<body>
diff --git a/testing/web-platform/tests/dom/events/resources/event-global-extra-frame.html b/testing/web-platform/tests/dom/events/resources/event-global-extra-frame.html
new file mode 100644
index 0000000000..241dda8b66
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/resources/event-global-extra-frame.html
@@ -0,0 +1,9 @@
+<script>
+function listener(e) {
+ parent.frameState = {
+ event: e,
+ windowEvent: window.event,
+ parentEvent: parent.event
+ }
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/resources/event-global-is-still-set-when-coercing-beforeunload-result-frame.html b/testing/web-platform/tests/dom/events/resources/event-global-is-still-set-when-coercing-beforeunload-result-frame.html
new file mode 100644
index 0000000000..5df4fa2793
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/resources/event-global-is-still-set-when-coercing-beforeunload-result-frame.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset="utf-8">
+<body>
+<script>
+window.onbeforeunload = () => ({ toString: () => { parent.currentEventInToString = window.event; } });
+</script>
diff --git a/testing/web-platform/tests/dom/events/resources/prefixed-animation-event-tests.js b/testing/web-platform/tests/dom/events/resources/prefixed-animation-event-tests.js
new file mode 100644
index 0000000000..021b6bb9df
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/resources/prefixed-animation-event-tests.js
@@ -0,0 +1,366 @@
+'use strict'
+
+// Runs a set of tests for a given prefixed/unprefixed animation event (e.g.
+// animationstart/webkitAnimationStart).
+//
+// The eventDetails object must have the following form:
+// {
+// isTransition: false, <-- can be omitted, default false
+// unprefixedType: 'animationstart',
+// prefixedType: 'webkitAnimationStart',
+// animationCssStyle: '1ms', <-- must NOT include animation name or
+// transition property
+// }
+function runAnimationEventTests(eventDetails) {
+ const {
+ isTransition,
+ unprefixedType,
+ prefixedType,
+ animationCssStyle
+ } = eventDetails;
+
+ // Derive the DOM event handler names, e.g. onanimationstart.
+ const unprefixedHandler = `on${unprefixedType}`;
+ const prefixedHandler = `on${prefixedType.toLowerCase()}`;
+
+ const style = document.createElement('style');
+ document.head.appendChild(style);
+ if (isTransition) {
+ style.sheet.insertRule(
+ `.baseStyle { width: 100px; transition: width ${animationCssStyle}; }`);
+ style.sheet.insertRule('.transition { width: 200px !important; }');
+ } else {
+ style.sheet.insertRule('@keyframes anim {}');
+ }
+
+ function triggerAnimation(div) {
+ if (isTransition) {
+ div.classList.add('transition');
+ } else {
+ div.style.animation = `anim ${animationCssStyle}`;
+ }
+ }
+
+ test(t => {
+ const div = createDiv(t);
+
+ assert_equals(div[unprefixedHandler], null,
+ `${unprefixedHandler} should initially be null`);
+ assert_equals(div[prefixedHandler], null,
+ `${prefixedHandler} should initially be null`);
+
+ // Setting one should not affect the other.
+ div[unprefixedHandler] = () => { };
+
+ assert_not_equals(div[unprefixedHandler], null,
+ `setting ${unprefixedHandler} should make it non-null`);
+ assert_equals(div[prefixedHandler], null,
+ `setting ${unprefixedHandler} should not affect ${prefixedHandler}`);
+
+ div[prefixedHandler] = () => { };
+
+ assert_not_equals(div[prefixedHandler], null,
+ `setting ${prefixedHandler} should make it non-null`);
+ assert_not_equals(div[unprefixedHandler], div[prefixedHandler],
+ 'the setters should be different');
+ }, `${unprefixedHandler} and ${prefixedHandler} are not aliases`);
+
+ // The below tests primarily test the interactions of prefixed animation
+ // events in the algorithm for invoking events:
+ // https://dom.spec.whatwg.org/#concept-event-listener-invoke
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedEventCount = 0;
+ addTestScopedEventHandler(t, div, prefixedHandler, () => {
+ receivedEventCount++;
+ });
+ addTestScopedEventListener(t, div, prefixedType, () => {
+ receivedEventCount++;
+ });
+
+ // The HTML spec[0] specifies that the prefixed event handlers have an
+ // 'Event handler event type' of the appropriate prefixed event type. E.g.
+ // onwebkitanimationend creates a listener for the event type
+ // 'webkitAnimationEnd'.
+ //
+ // [0]: https://html.spec.whatwg.org/multipage/webappapis.html#event-handlers-on-elements,-document-objects,-and-window-objects
+ div.dispatchEvent(new AnimationEvent(prefixedType));
+ assert_equals(receivedEventCount, 2,
+ 'prefixed listener and handler received event');
+ }, `dispatchEvent of a ${prefixedType} event does trigger a ` +
+ `prefixed event handler or listener`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedEvent = false;
+ addTestScopedEventHandler(t, div, unprefixedHandler, () => {
+ receivedEvent = true;
+ });
+ addTestScopedEventListener(t, div, unprefixedType, () => {
+ receivedEvent = true;
+ });
+
+ div.dispatchEvent(new AnimationEvent(prefixedType));
+ assert_false(receivedEvent,
+ 'prefixed listener or handler received event');
+ }, `dispatchEvent of a ${prefixedType} event does not trigger an ` +
+ `unprefixed event handler or listener`);
+
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedEvent = false;
+ addTestScopedEventHandler(t, div, prefixedHandler, () => {
+ receivedEvent = true;
+ });
+ addTestScopedEventListener(t, div, prefixedType, () => {
+ receivedEvent = true;
+ });
+
+ // The rewrite rules from
+ // https://dom.spec.whatwg.org/#concept-event-listener-invoke step 8 do not
+ // apply because isTrusted will be false.
+ div.dispatchEvent(new AnimationEvent(unprefixedType));
+ assert_false(receivedEvent, 'prefixed listener or handler received event');
+ }, `dispatchEvent of an ${unprefixedType} event does not trigger a ` +
+ `prefixed event handler or listener`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedEvent = false;
+ addTestScopedEventHandler(t, div, prefixedHandler, () => {
+ receivedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedType);
+ assert_true(receivedEvent, `received ${prefixedHandler} event`);
+ }, `${prefixedHandler} event handler should trigger for an animation`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedPrefixedEvent = false;
+ addTestScopedEventHandler(t, div, prefixedHandler, () => {
+ receivedPrefixedEvent = true;
+ });
+ let receivedUnprefixedEvent = false;
+ addTestScopedEventHandler(t, div, unprefixedHandler, () => {
+ receivedUnprefixedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedType);
+ assert_true(receivedUnprefixedEvent, `received ${unprefixedHandler} event`);
+ assert_false(receivedPrefixedEvent, `received ${prefixedHandler} event`);
+ }, `${prefixedHandler} event handler should not trigger if an unprefixed ` +
+ `event handler also exists`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedPrefixedEvent = false;
+ addTestScopedEventHandler(t, div, prefixedHandler, () => {
+ receivedPrefixedEvent = true;
+ });
+ let receivedUnprefixedEvent = false;
+ addTestScopedEventListener(t, div, unprefixedType, () => {
+ receivedUnprefixedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedHandler);
+ assert_true(receivedUnprefixedEvent, `received ${unprefixedHandler} event`);
+ assert_false(receivedPrefixedEvent, `received ${prefixedHandler} event`);
+ }, `${prefixedHandler} event handler should not trigger if an unprefixed ` +
+ `listener also exists`);
+
+ promise_test(async t => {
+ // We use a parent/child relationship to be able to register both prefixed
+ // and unprefixed event handlers without the deduplication logic kicking in.
+ const parent = createDiv(t);
+ const child = createDiv(t);
+ parent.appendChild(child);
+ // After moving the child, we have to clean style again.
+ getComputedStyle(child).transition;
+ getComputedStyle(child).width;
+
+ let observedUnprefixedType;
+ addTestScopedEventHandler(t, parent, unprefixedHandler, e => {
+ observedUnprefixedType = e.type;
+ });
+ let observedPrefixedType;
+ addTestScopedEventHandler(t, child, prefixedHandler, e => {
+ observedPrefixedType = e.type;
+ });
+
+ triggerAnimation(child);
+ await waitForEventThenAnimationFrame(t, unprefixedType);
+
+ assert_equals(observedUnprefixedType, unprefixedType);
+ assert_equals(observedPrefixedType, prefixedType);
+ }, `event types for prefixed and unprefixed ${unprefixedType} event ` +
+ `handlers should be named appropriately`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedEvent = false;
+ addTestScopedEventListener(t, div, prefixedType, () => {
+ receivedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedHandler);
+ assert_true(receivedEvent, `received ${prefixedType} event`);
+ }, `${prefixedType} event listener should trigger for an animation`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedPrefixedEvent = false;
+ addTestScopedEventListener(t, div, prefixedType, () => {
+ receivedPrefixedEvent = true;
+ });
+ let receivedUnprefixedEvent = false;
+ addTestScopedEventListener(t, div, unprefixedType, () => {
+ receivedUnprefixedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedHandler);
+ assert_true(receivedUnprefixedEvent, `received ${unprefixedType} event`);
+ assert_false(receivedPrefixedEvent, `received ${prefixedType} event`);
+ }, `${prefixedType} event listener should not trigger if an unprefixed ` +
+ `listener also exists`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedPrefixedEvent = false;
+ addTestScopedEventListener(t, div, prefixedType, () => {
+ receivedPrefixedEvent = true;
+ });
+ let receivedUnprefixedEvent = false;
+ addTestScopedEventHandler(t, div, unprefixedHandler, () => {
+ receivedUnprefixedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedHandler);
+ assert_true(receivedUnprefixedEvent, `received ${unprefixedType} event`);
+ assert_false(receivedPrefixedEvent, `received ${prefixedType} event`);
+ }, `${prefixedType} event listener should not trigger if an unprefixed ` +
+ `event handler also exists`);
+
+ promise_test(async t => {
+ // We use a parent/child relationship to be able to register both prefixed
+ // and unprefixed event listeners without the deduplication logic kicking in.
+ const parent = createDiv(t);
+ const child = createDiv(t);
+ parent.appendChild(child);
+ // After moving the child, we have to clean style again.
+ getComputedStyle(child).transition;
+ getComputedStyle(child).width;
+
+ let observedUnprefixedType;
+ addTestScopedEventListener(t, parent, unprefixedType, e => {
+ observedUnprefixedType = e.type;
+ });
+ let observedPrefixedType;
+ addTestScopedEventListener(t, child, prefixedType, e => {
+ observedPrefixedType = e.type;
+ });
+
+ triggerAnimation(child);
+ await waitForEventThenAnimationFrame(t, unprefixedHandler);
+
+ assert_equals(observedUnprefixedType, unprefixedType);
+ assert_equals(observedPrefixedType, prefixedType);
+ }, `event types for prefixed and unprefixed ${unprefixedType} event ` +
+ `listeners should be named appropriately`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedEvent = false;
+ addTestScopedEventListener(t, div, prefixedType.toLowerCase(), () => {
+ receivedEvent = true;
+ });
+ addTestScopedEventListener(t, div, prefixedType.toUpperCase(), () => {
+ receivedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedHandler);
+ assert_false(receivedEvent, `received ${prefixedType} event`);
+ }, `${prefixedType} event listener is case sensitive`);
+}
+
+// Below are utility functions.
+
+// Creates a div element, appends it to the document body and removes the
+// created element during test cleanup.
+function createDiv(test) {
+ const element = document.createElement('div');
+ element.classList.add('baseStyle');
+ document.body.appendChild(element);
+ test.add_cleanup(() => {
+ element.remove();
+ });
+
+ // Flush style before returning. Some browsers only do partial style re-calc,
+ // so ask for all important properties to make sure they are applied.
+ getComputedStyle(element).transition;
+ getComputedStyle(element).width;
+
+ return element;
+}
+
+// Adds an event handler for |handlerName| (calling |callback|) to the given
+// |target|, that will automatically be cleaned up at the end of the test.
+function addTestScopedEventHandler(test, target, handlerName, callback) {
+ assert_regexp_match(
+ handlerName, /^on/, 'Event handler names must start with "on"');
+ assert_equals(target[handlerName], null,
+ `${handlerName} must be supported and not previously set`);
+ target[handlerName] = callback;
+ // We need this cleaned up even if the event handler doesn't run.
+ test.add_cleanup(() => {
+ if (target[handlerName])
+ target[handlerName] = null;
+ });
+}
+
+// Adds an event listener for |type| (calling |callback|) to the given
+// |target|, that will automatically be cleaned up at the end of the test.
+function addTestScopedEventListener(test, target, type, callback) {
+ target.addEventListener(type, callback);
+ // We need this cleaned up even if the event handler doesn't run.
+ test.add_cleanup(() => {
+ target.removeEventListener(type, callback);
+ });
+}
+
+// Returns a promise that will resolve once the passed event (|eventName|) has
+// triggered and one more animation frame has happened. Automatically chooses
+// between an event handler or event listener based on whether |eventName|
+// begins with 'on'.
+//
+// We always listen on window as we don't want to interfere with the test via
+// triggering the prefixed event deduplication logic.
+function waitForEventThenAnimationFrame(test, eventName) {
+ return new Promise((resolve, _) => {
+ const eventFunc = eventName.startsWith('on')
+ ? addTestScopedEventHandler : addTestScopedEventListener;
+ eventFunc(test, window, eventName, () => {
+ // rAF once to give the event under test time to come through.
+ requestAnimationFrame(resolve);
+ });
+ });
+}
diff --git a/testing/web-platform/tests/dom/events/scrolling/iframe-chains.html b/testing/web-platform/tests/dom/events/scrolling/iframe-chains.html
new file mode 100644
index 0000000000..fb7d674aae
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/iframe-chains.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<style>
+
+body { margin: 0; padding: 10px; }
+.space { height: 2000px; }
+
+#scroller {
+ border: 3px solid green;
+ position: absolute;
+ z-index: 0;
+ overflow: auto;
+ padding: 10px;
+ width: 250px;
+ height: 150px;
+}
+
+.ifr {
+ border: 3px solid blue;
+ width: 200px;
+ height: 100px;
+}
+
+</style>
+</head>
+<body>
+<div id=scroller>
+ <iframe srcdoc="SCROLL ME" class=ifr></iframe>
+ <div class=space></div>
+</div>
+<div class=space></div>
+<script>
+
+promise_test(async t => {
+ await new test_driver.Actions().scroll(50, 50, 0, 50).send();
+ // Allow the possibility the scroll is not fully synchronous
+ await t.step_wait(() => scroller.scrollTop === 50);
+}, "Wheel scroll in iframe chains to containing element.");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/events/scrolling/input-text-scroll-event-when-using-arrow-keys.html b/testing/web-platform/tests/dom/events/scrolling/input-text-scroll-event-when-using-arrow-keys.html
new file mode 100644
index 0000000000..f84e446527
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/input-text-scroll-event-when-using-arrow-keys.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+</head>
+<body onload=runTest()>
+ <p>Moving the cursor using the arrow keys into an
+ input element fires scroll events when text has to scroll into view.
+ Uses arrow keys to move forward and backwards in the input
+ element.</p>
+ <input type="text" style='width: 50px'
+ value="Fooooooooooooooooooooooooooooooooooooooooooooooooo"/>
+ <textarea rows="4" cols="4">
+ Fooooooooooooooooooooooooooooooooooooooooooooooooo
+ </textarea>
+
+ <script>
+ async function moveCursorRightInsideElement(element, value){
+ var arrowRight = '\uE014';
+ for(var i=0;i<value;i++){
+ await test_driver.send_keys(element, arrowRight);
+ }
+ }
+
+ function runTest(){
+ promise_test(async(t) => { return new Promise(async (resolve, reject) => {
+ var input = document.getElementsByTagName('input')[0];
+ function handleScroll(){
+ resolve("Scroll Event successfully fired!");
+ }
+ input.addEventListener('scroll', handleScroll, false);
+ // move cursor to the right until the text scrolls
+ while(input.scrollLeft === 0){
+ await moveCursorRightInsideElement(input, 1);
+ }
+ // if there is no scroll event fired then test will fail by timeout
+ })},
+ /*
+ Moving the cursor using the arrow keys into an input element
+ fires scroll events when text has to scroll into view.
+ Uses arrow keys to move right in the input element.
+ */
+ "Scroll event fired for <input> element.");
+
+ promise_test(async(t) => { return new Promise(async (resolve, reject) => {
+ var textarea = document.getElementsByTagName('textarea')[0];
+ function handleScroll(){
+ resolve("Scroll Event successfully fired!");
+ }
+ textarea.addEventListener('scroll', handleScroll, false);
+ // move cursor to the right until the text scrolls
+ while(textarea.scrollLeft === 0){
+ await moveCursorRightInsideElement(textarea, 1);
+ }
+ // if there is no scroll event fired then test will fail by timeout
+ })},
+ /*
+ Moving the cursor using the arrow keys into a textarea element
+ fires scroll events when text has to scroll into view.
+ Uses arrow keys to move right in the textarea element.
+ */
+ "Scroll event fired for <textarea> element.");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/events/scrolling/overscroll-deltas.html b/testing/web-platform/tests/dom/events/scrolling/overscroll-deltas.html
new file mode 100644
index 0000000000..2158632250
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/overscroll-deltas.html
@@ -0,0 +1,147 @@
+<!DOCTYPE HTML>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+ #hspacer {
+ height: 100px;
+ width: 100vw;
+ top: 0;
+ /* on the right edge of targetDiv */
+ left: 200px;
+ position: absolute;
+ }
+
+ #vspacer {
+ height: 100vh;
+ width: 100px;
+ position: absolute;
+ }
+
+ #targetDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+ }
+
+ #innerDiv {
+ width: 400px;
+ height: 400px;
+ }
+</style>
+
+<body style="margin:0" onload=runTest()>
+ <div id="targetDiv">
+ <div id="innerDiv"></div>
+ </div>
+ <div id="hspacer"></div>
+ <div id="vspacer"></div>
+</body>
+
+<script>
+ var target_div = document.getElementById('targetDiv');
+ var overscrolled_x_deltas = [];
+ var overscrolled_y_deltas = [];
+ var scrollend_received = false;
+
+ function onOverscroll(event) {
+ overscrolled_x_deltas.push(event.deltaX);
+ overscrolled_y_deltas.push(event.deltaY);
+ }
+
+ async function resetScrollers(test) {
+ await waitForScrollReset(test, target_div);
+ await waitForScrollReset(test, document.scrollingElement);
+ }
+
+ function resetOverScrollDeltas() {
+ overscrolled_x_deltas = [];
+ overscrolled_y_deltas = [];
+ }
+
+ function fail() {
+ assert_true(false);
+ }
+
+ document.addEventListener("overscroll", onOverscroll);
+
+ function runTest() {
+ promise_test(async (t) => {
+ await resetScrollers(t);
+ await waitForCompositorCommit();
+ resetOverScrollDeltas();
+
+ assert_equals(document.scrollingElement.scrollTop, 0,
+ "document should not be scrolled");
+
+ let scrollend_promise = waitForScrollendEvent(t, target_div);
+ let max_target_div_scroll_top = target_div.scrollHeight - target_div.clientHeight;
+ target_div.scrollTop = target_div.scrollHeight;
+ await scrollend_promise;
+ assert_equals(target_div.scrollTop, max_target_div_scroll_top,
+ "target_div should be fully scrolled down");
+
+ let overscroll_promise = waitForOverscrollEvent(t, document, 2000);
+ scrollend_promise = waitForScrollendEvent(t, document, 2000);
+ target_div.addEventListener("scrollend", fail);
+ // Scroll target div vertically and wait for the doc to get scrollend event.
+ await scrollElementDown(target_div, target_div.clientHeight + 300);
+ await waitForCompositorCommit();
+ await overscroll_promise;
+ await scrollend_promise;
+
+ target_div.removeEventListener("scrollend", fail);
+ // Even though we request 300 extra pixels of scroll, the API above doesn't
+ // guarantee how much scroll delta will be generated - different browsers
+ // can consume different amounts for "touch slop" (for example). Ensure the
+ // overscroll reaches at least 50 pixels which is a fairly conservative
+ // value.
+ assert_greater_than(overscrolled_y_deltas.length, 0, "There should be at least one overscroll events when overscrolling.");
+ assert_equals(overscrolled_x_deltas.filter(function (x) { return x != 0; }).length, 0, "The deltaX attribute must be 0 when there is no scrolling in x direction.");
+ assert_greater_than_equal(Math.min(...overscrolled_y_deltas), 50, "The deltaY attribute must be the number of pixels overscrolled.");
+ assert_less_than_equal(Math.max(...overscrolled_y_deltas), 300, "The deltaY attribute must be <= the number of pixels overscrolled (300)");
+ assert_greater_than(document.scrollingElement.scrollTop, target_div.clientHeight - 1,
+ "document is scrolled by the height of target_div");
+ }, "testing, vertical");
+
+ promise_test(async (t) => {
+ await resetScrollers(t);
+ await waitForCompositorCommit();
+ resetOverScrollDeltas();
+
+ assert_equals(document.scrollingElement.scrollLeft, 0,
+ "document should not be scrolled");
+
+ let scrollend_promise = waitForScrollendEvent(t, target_div);
+ let max_target_div_scroll_left = target_div.scrollWidth - target_div.clientWidth;
+ target_div.scrollLeft = target_div.scrollWidth;
+ await scrollend_promise;
+ assert_equals(target_div.scrollLeft, max_target_div_scroll_left,
+ "target_div should be fully scrolled right");
+
+ let overscroll_promise = waitForOverscrollEvent(t, document, 2000);
+ scrollend_promise = waitForScrollendEvent(t, document, 2000);
+ target_div.addEventListener("scrollend", fail);
+ // Scroll target div horizontally and wait for the doc to get scrollend event.
+ await scrollElementLeft(target_div, target_div.clientWidth + 300);
+ await waitForCompositorCommit();
+ await overscroll_promise;
+ await scrollend_promise;
+
+ target_div.removeEventListener("scrollend", fail);
+ assert_greater_than(document.scrollingElement.scrollLeft, target_div.clientWidth - 1,
+ "document is scrolled by the height of target_div");
+ // TODO(bokan): It looks like Chrome inappropriately filters some scroll
+ // events despite |overscroll-behavior| being set to none. The overscroll
+ // amount here has been loosened but this should be fixed in Chrome.
+ // https://crbug.com/1112183.
+ assert_greater_than(overscrolled_x_deltas.length, 0, "There should be at least one overscroll events when overscrolling.");
+ assert_equals(overscrolled_y_deltas.filter(function(x){ return x!=0; }).length, 0, "The deltaY attribute must be 0 when there is no scrolling in y direction.");
+ assert_less_than_equal(Math.max(...overscrolled_x_deltas), 300, "The deltaX attribute must be <= number of pixels overscrolled (300)");
+ assert_greater_than_equal(Math.min(...overscrolled_x_deltas),50, "The deltaX attribute must be the number of pixels overscrolled.");
+ }, "testing, horizontal");
+ }
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-document.html b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-document.html
new file mode 100644
index 0000000000..c054ffca9c
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-document.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+#targetDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+}
+
+#innerDiv {
+ width: 400px;
+ height: 400px;
+}
+</style>
+
+<body style="margin:0" onload=runTest()>
+<div id="targetDiv">
+ <div id="innerDiv">
+ </div>
+</div>
+</body>
+
+<script>
+var target_div = document.getElementById('targetDiv');
+var overscrolled_x_delta = 0;
+var overscrolled_y_delta = 0;
+function onOverscroll(event) {
+ assert_false(event.cancelable);
+ // overscroll events are bubbled when the target node is document.
+ assert_true(event.bubbles);
+ overscrolled_x_delta = event.deltaX;
+ overscrolled_y_delta = event.deltaY;
+}
+document.addEventListener("overscroll", onOverscroll);
+
+function runTest() {
+ promise_test (async (t) => {
+ // Make sure that no overscroll event is sent to target_div.
+ target_div.addEventListener("overscroll",
+ t.unreached_func("target_div got unexpected overscroll event."));
+ await waitForCompositorCommit();
+
+ // Scroll up on target div and wait for the doc to get overscroll event.
+ await touchScrollInTarget(300, target_div, 'up');
+ await waitFor(() => { return overscrolled_y_delta < 0; },
+ 'Document did not receive overscroll event after scroll up on target.');
+ assert_equals(target_div.scrollTop, 0);
+
+ // Scroll left on target div and wait for the doc to get overscroll event.
+ await touchScrollInTarget(300, target_div, 'left');
+ await waitFor(() => { return overscrolled_x_delta < 0; },
+ 'Document did not receive overscroll event after scroll left on target.');
+ assert_equals(target_div.scrollLeft, 0);
+ }, 'Tests that the document gets overscroll event when no element scrolls ' +
+ 'after touch scrolling.');
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.html b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.html
new file mode 100644
index 0000000000..750080e656
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+#overscrollXDiv {
+ width: 600px;
+ height: 600px;
+ overflow: scroll;
+ overscroll-behavior-x: contain;
+}
+#overscrollYDiv {
+ width: 500px;
+ height: 500px;
+ overflow: scroll;
+ overscroll-behavior-y: none;
+}
+#targetDiv {
+ width: 400px;
+ height: 400px;
+ overflow: scroll;
+}
+.content {
+ width:800px;
+ height:800px;
+}
+</style>
+
+<body style="margin:0" onload=runTest()>
+<div id="overscrollXDiv">
+ <div class=content>
+ <div id="overscrollYDiv">
+ <div class=content>
+ <div id="targetDiv">
+ <div class="content">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+</body>
+
+<script>
+var target_div = document.getElementById('targetDiv');
+var overscrolled_x_delta = 0;
+var overscrolled_y_delta = 0;
+function onOverscrollX(event) {
+ assert_false(event.cancelable);
+ assert_false(event.bubbles);
+ overscrolled_x_delta = event.deltaX;
+}
+function onOverscrollY(event) {
+ assert_false(event.cancelable);
+ assert_false(event.bubbles);
+ overscrolled_y_delta = event.deltaY;
+}
+// Test that both "onoverscroll" and addEventListener("overscroll"... work.
+document.getElementById('overscrollXDiv').onoverscroll = onOverscrollX;
+document.getElementById('overscrollYDiv').
+ addEventListener("overscroll", onOverscrollY);
+
+function runTest() {
+ promise_test (async (t) => {
+ // Make sure that no overscroll event is sent to document or target_div.
+ document.addEventListener("overscroll",
+ t.unreached_func("Document got unexpected overscroll event."));
+ target_div.addEventListener("overscroll",
+ t.unreached_func("target_div got unexpected overscroll event."));
+ await waitForCompositorCommit();
+ // Scroll up on target div and wait for the element with overscroll-y to get
+ // overscroll event.
+ await touchScrollInTarget(300, target_div, 'up');
+ await waitFor(() => { return overscrolled_y_delta < 0; },
+ 'Expected element did not receive overscroll event after scroll up on ' +
+ 'target.');
+ assert_equals(target_div.scrollTop, 0);
+
+ // Scroll left on target div and wait for the element with overscroll-x to
+ // get overscroll event.
+ await touchScrollInTarget(300, target_div, 'left');
+ await waitFor(() => { return overscrolled_x_delta < 0; },
+ 'Expected element did not receive overscroll event after scroll left ' +
+ 'on target.');
+ assert_equals(target_div.scrollLeft, 0);
+ }, 'Tests that the last element in the cut scroll chain gets overscroll ' +
+ 'event when no element scrolls by touch.');
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.html b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.html
new file mode 100644
index 0000000000..cfc782a809
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+#scrollableDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+}
+
+#innerDiv {
+ width: 400px;
+ height: 400px;
+}
+</style>
+
+<body style="margin:0" onload=runTest()>
+<div id="scrollableDiv">
+ <div id="innerDiv">
+ </div>
+</div>
+</body>
+
+<script>
+var scrolling_div = document.getElementById('scrollableDiv');
+var overscrolled_x_delta = 0;
+var overscrolled_y_delta = 0;
+function onOverscroll(event) {
+ assert_false(event.cancelable);
+ assert_false(event.bubbles);
+ overscrolled_x_delta = event.deltaX;
+ overscrolled_y_delta = event.deltaY;
+}
+scrolling_div.addEventListener("overscroll", onOverscroll);
+
+function runTest() {
+ promise_test (async (t) => {
+ // Make sure that no overscroll event is sent to document.
+ document.addEventListener("overscroll",
+ t.unreached_func("Document got unexpected overscroll event."));
+ await waitForCompositorCommit();
+
+ // Do a horizontal scroll and wait for overscroll event.
+ await touchScrollInTarget(300, scrolling_div , 'right');
+ await waitFor(() => { return overscrolled_x_delta > 0; },
+ 'Scroller did not receive overscroll event after horizontal scroll.');
+ assert_equals(scrolling_div.scrollWidth - scrolling_div.scrollLeft,
+ scrolling_div.clientWidth);
+
+ overscrolled_x_delta = 0;
+ overscrolled_y_delta = 0;
+
+ // Do a vertical scroll and wait for overscroll event.
+ await touchScrollInTarget(300, scrolling_div, 'down');
+ await waitFor(() => { return overscrolled_y_delta > 0; },
+ 'Scroller did not receive overscroll event after vertical scroll.');
+ assert_equals(scrolling_div.scrollHeight - scrolling_div.scrollTop,
+ scrolling_div.clientHeight);
+ }, 'Tests that the scrolled element gets overscroll event after fully scrolling by touch.');
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-window.html b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-window.html
new file mode 100644
index 0000000000..ef5ae3daef
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-window.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+#targetDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+}
+
+#innerDiv {
+ width: 400px;
+ height: 400px;
+}
+</style>
+
+<body style="margin:0" onload=runTest()>
+<div id="targetDiv">
+ <div id="innerDiv">
+ </div>
+</div>
+</body>
+
+<script>
+var target_div = document.getElementById('targetDiv');
+var window_received_overscroll = false;
+
+function onOverscroll(event) {
+ assert_false(event.cancelable);
+ // overscroll events targetting document are bubbled to the window.
+ assert_true(event.bubbles);
+ window_received_overscroll = true;
+}
+window.addEventListener("overscroll", onOverscroll);
+
+function runTest() {
+ promise_test (async (t) => {
+ // Make sure that no overscroll event is sent to target_div.
+ target_div.addEventListener("overscroll",
+ t.unreached_func("target_div got unexpected overscroll event."));
+ // Scroll up on target div and wait for the window to get overscroll event.
+ await touchScrollInTarget(300, target_div, 'up');
+ await waitFor(() => { return window_received_overscroll; },
+ 'Window did not receive overscroll event after scroll up on target.');
+ }, 'Tests that the window gets overscroll event when no element scrolls' +
+ 'after touch scrolling.');
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/scroll_support.js b/testing/web-platform/tests/dom/events/scrolling/scroll_support.js
new file mode 100644
index 0000000000..52c2f58723
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/scroll_support.js
@@ -0,0 +1,278 @@
+async function waitForEvent(eventName, test, target, timeoutMs = 500) {
+ return new Promise((resolve, reject) => {
+ const timeoutCallback = test.step_timeout(() => {
+ reject(`No ${eventName} event received for target ${target}`);
+ }, timeoutMs);
+ target.addEventListener(eventName, (evt) => {
+ clearTimeout(timeoutCallback);
+ resolve(evt);
+ }, { once: true });
+ });
+}
+
+async function waitForScrollendEvent(test, target, timeoutMs = 500) {
+ return waitForEvent("scrollend", test, target, timeoutMs);
+}
+
+async function waitForOverscrollEvent(test, target, timeoutMs = 500) {
+ return waitForEvent("overscroll", test, target, timeoutMs);
+}
+
+async function waitForPointercancelEvent(test, target, timeoutMs = 500) {
+ return waitForEvent("pointercancel", test, target, timeoutMs);
+}
+
+// Resets the scroll position to (0,0). If a scroll is required, then the
+// promise is not resolved until the scrollend event is received.
+async function waitForScrollReset(test, scroller, timeoutMs = 500) {
+ return new Promise(resolve => {
+ if (scroller.scrollTop == 0 &&
+ scroller.scrollLeft == 0) {
+ resolve();
+ } else {
+ const eventTarget =
+ scroller == document.scrollingElement ? document : scroller;
+ scroller.scrollTop = 0;
+ scroller.scrollLeft = 0;
+ waitForScrollendEvent(test, eventTarget, timeoutMs).then(resolve);
+ }
+ });
+}
+
+async function createScrollendPromiseForTarget(test,
+ target_div,
+ timeoutMs = 500) {
+ return waitForScrollendEvent(test, target_div, timeoutMs).then(evt => {
+ assert_false(evt.cancelable, 'Event is not cancelable');
+ assert_false(evt.bubbles, 'Event targeting element does not bubble');
+ });
+}
+
+function verifyNoScrollendOnDocument(test) {
+ const callback =
+ test.unreached_func("window got unexpected scrollend event.");
+ window.addEventListener('scrollend', callback);
+ test.add_cleanup(() => {
+ window.removeEventListener('scrollend', callback);
+ });
+}
+
+async function verifyScrollStopped(test, target_div) {
+ const unscaled_pause_time_in_ms = 100;
+ const x = target_div.scrollLeft;
+ const y = target_div.scrollTop;
+ return new Promise(resolve => {
+ test.step_timeout(() => {
+ assert_equals(target_div.scrollLeft, x);
+ assert_equals(target_div.scrollTop, y);
+ resolve();
+ }, unscaled_pause_time_in_ms);
+ });
+}
+
+async function resetTargetScrollState(test, target_div) {
+ if (target_div.scrollTop != 0 || target_div.scrollLeft != 0) {
+ target_div.scrollTop = 0;
+ target_div.scrollLeft = 0;
+ return waitForScrollendEvent(test, target_div);
+ }
+}
+
+const MAX_FRAME = 700;
+const MAX_UNCHANGED_FRAMES = 20;
+
+// Returns a promise that resolves when the given condition is met or rejects
+// after MAX_FRAME animation frames.
+// TODO(crbug.com/1400399): deprecate. We should not use frame based waits in
+// WPT as frame rates may vary greatly in different testing environments.
+function waitFor(condition, error_message = 'Reaches the maximum frames.') {
+ return new Promise((resolve, reject) => {
+ function tick(frames) {
+ // We requestAnimationFrame either for MAX_FRAM frames or until condition
+ // is met.
+ if (frames >= MAX_FRAME)
+ reject(error_message);
+ else if (condition())
+ resolve();
+ else
+ requestAnimationFrame(tick.bind(this, frames + 1));
+ }
+ tick(0);
+ });
+}
+
+// TODO(crbug.com/1400446): Test driver should defer sending events until the
+// browser is ready. Also the term compositor-commit is misleading as not all
+// user-agents use a compositor process.
+function waitForCompositorCommit() {
+ return new Promise((resolve) => {
+ // rAF twice.
+ window.requestAnimationFrame(() => {
+ window.requestAnimationFrame(resolve);
+ });
+ });
+}
+
+// Please don't remove this. This is necessary for chromium-based browsers.
+// This shouldn't be necessary if the test harness deferred running the tests
+// until after paint holding. This can be a no-op on user-agents that do not
+// have a separate compositor thread.
+async function waitForCompositorReady() {
+ const animation =
+ document.body.animate({ opacity: [ 1, 1 ] }, {duration: 1 });
+ return animation.finished;
+}
+
+function waitForNextFrame() {
+ const startTime = performance.now();
+ return new Promise(resolve => {
+ window.requestAnimationFrame((frameTime) => {
+ if (frameTime < startTime) {
+ window.requestAnimationFrame(resolve);
+ } else {
+ resolve();
+ }
+ });
+ });
+}
+
+// TODO(crbug.com/1400399): Deprecate as frame rates may vary greatly in
+// different test environments.
+function waitForAnimationEnd(getValue) {
+ var last_changed_frame = 0;
+ var last_position = getValue();
+ return new Promise((resolve, reject) => {
+ function tick(frames) {
+ // We requestAnimationFrame either for MAX_FRAME or until
+ // MAX_UNCHANGED_FRAMES with no change have been observed.
+ if (frames >= MAX_FRAME || frames - last_changed_frame > MAX_UNCHANGED_FRAMES) {
+ resolve();
+ } else {
+ current_value = getValue();
+ if (last_position != current_value) {
+ last_changed_frame = frames;
+ last_position = current_value;
+ }
+ requestAnimationFrame(tick.bind(this, frames + 1));
+ }
+ }
+ tick(0);
+ })
+}
+
+// Scrolls in target according to move_path with pauses in between
+// The move_path should contains coordinates that are within target boundaries.
+// Keep in mind that 0,0 is the center of the target element and is also
+// the pointerDown position.
+// pointerUp() is fired after sequence of moves.
+function touchScrollInTargetSequentiallyWithPause(target, move_path, pause_time_in_ms = 100) {
+ const test_driver_actions = new test_driver.Actions()
+ .addPointer("pointer1", "touch")
+ .pointerMove(0, 0, {origin: target})
+ .pointerDown();
+
+ const substeps = 5;
+ let x = 0;
+ let y = 0;
+ // Do each move in 5 steps
+ for(let move of move_path) {
+ let step_x = (move.x - x) / substeps;
+ let step_y = (move.y - y) / substeps;
+ for(let step = 0; step < substeps; step++) {
+ x += step_x;
+ y += step_y;
+ test_driver_actions.pointerMove(x, y, {origin: target});
+ }
+ test_driver_actions.pause(pause_time_in_ms); // To prevent inertial scroll
+ }
+
+ return test_driver_actions.pointerUp().send();
+}
+
+function touchScrollInTarget(pixels_to_scroll, target, direction, pause_time_in_ms = 100) {
+ var x_delta = 0;
+ var y_delta = 0;
+ const num_movs = 5;
+ if (direction == "down") {
+ y_delta = -1 * pixels_to_scroll / num_movs;
+ } else if (direction == "up") {
+ y_delta = pixels_to_scroll / num_movs;
+ } else if (direction == "right") {
+ x_delta = -1 * pixels_to_scroll / num_movs;
+ } else if (direction == "left") {
+ x_delta = pixels_to_scroll / num_movs;
+ } else {
+ throw("scroll direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'");
+ }
+ return new test_driver.Actions()
+ .addPointer("pointer1", "touch")
+ .pointerMove(0, 0, {origin: target})
+ .pointerDown()
+ .pointerMove(x_delta, y_delta, {origin: target})
+ .pointerMove(2 * x_delta, 2 * y_delta, {origin: target})
+ .pointerMove(3 * x_delta, 3 * y_delta, {origin: target})
+ .pointerMove(4 * x_delta, 4 * y_delta, {origin: target})
+ .pointerMove(5 * x_delta, 5 * y_delta, {origin: target})
+ .pause(pause_time_in_ms)
+ .pointerUp()
+ .send();
+}
+
+// Trigger fling by doing pointerUp right after pointerMoves.
+function touchFlingInTarget(pixels_to_scroll, target, direction) {
+ touchScrollInTarget(pixels_to_scroll, target, direction, 0 /* pause_time */);
+}
+
+function mouseActionsInTarget(target, origin, delta, pause_time_in_ms = 100) {
+ return new test_driver.Actions()
+ .addPointer("pointer1", "mouse")
+ .pointerMove(origin.x, origin.y, { origin: target })
+ .pointerDown()
+ .pointerMove(origin.x + delta.x, origin.y + delta.y, { origin: target })
+ .pointerMove(origin.x + delta.x * 2, origin.y + delta.y * 2, { origin: target })
+ .pause(pause_time_in_ms)
+ .pointerUp()
+ .send();
+}
+
+// Returns a promise that resolves when the given condition holds for 10
+// animation frames or rejects if the condition changes to false within 10
+// animation frames.
+// TODO(crbug.com/1400399): Deprecate as frame rates may very greatly in
+// different test environments.
+function conditionHolds(condition, error_message = 'Condition is not true anymore.') {
+ const MAX_FRAME = 10;
+ return new Promise((resolve, reject) => {
+ function tick(frames) {
+ // We requestAnimationFrame either for 10 frames or until condition is
+ // violated.
+ if (frames >= MAX_FRAME)
+ resolve();
+ else if (!condition())
+ reject(error_message);
+ else
+ requestAnimationFrame(tick.bind(this, frames + 1));
+ }
+ tick(0);
+ });
+}
+
+function scrollElementDown(element, scroll_amount) {
+ let x = 0;
+ let y = 0;
+ let delta_x = 0;
+ let delta_y = scroll_amount;
+ let actions = new test_driver.Actions()
+ .scroll(x, y, delta_x, delta_y, {origin: element});
+ return actions.send();
+}
+
+function scrollElementLeft(element, scroll_amount) {
+ let x = 0;
+ let y = 0;
+ let delta_x = scroll_amount;
+ let delta_y = 0;
+ let actions = new test_driver.Actions()
+ .scroll(x, y, delta_x, delta_y, {origin: element});
+ return actions.send();
+}
diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-sequence-of-scrolls.tentative.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-sequence-of-scrolls.tentative.html
new file mode 100644
index 0000000000..dab6dcc9bd
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-sequence-of-scrolls.tentative.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+#targetDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+}
+
+#innerDiv {
+ width: 4000px;
+ height: 4000px;
+}
+</style>
+
+<body style="margin:0" onload=runTest()>
+<div id="targetDiv">
+ <div id="innerDiv">
+ </div>
+</div>
+</body>
+
+<script>
+const target_div = document.getElementById('targetDiv');
+
+async function testWithMovePath(t, move_path) {
+ // Skip the test on a Mac as they do not support touch screens.
+ const isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0;
+ if (isMac)
+ return;
+
+ await resetTargetScrollState(t, target_div);
+ await waitForCompositorReady();
+
+ verifyNoScrollendOnDocument(t);
+
+ let scrollend_count = 0;
+ const scrollend_listener = () => { scrollend_count += 1; };
+ target_div.addEventListener("scrollend", scrollend_listener);
+ t.add_cleanup(() => { target_div.removeEventListener('scrollend', scrollend_listener); });
+
+ const pointercancel_listener = () => {
+ assert_equals(scrollend_count, 0, 'scrollend should happen after pointercancel.');
+ };
+ target_div.addEventListener("pointercancel", pointercancel_listener);
+ t.add_cleanup(() => { target_div.removeEventListener('pointercancel', pointercancel_listener); });
+
+ // Because we have several pointer moves, we choose bigger timeout.
+ const timeoutMs = 3000;
+ const targetPointercancelPromise = waitForPointercancelEvent(t, target_div, timeoutMs);
+ const targetScrollendPromise = createScrollendPromiseForTarget(t, target_div, timeoutMs);
+
+ await touchScrollInTargetSequentiallyWithPause(target_div, move_path);
+
+ // Because we start scrolling after pointerdown, there is no pointerup, instead the target
+ // will receive a pointercancel, so we wait for pointercancel, and then continue.
+ await targetPointercancelPromise;
+ await targetScrollendPromise;
+ await verifyScrollStopped(t, target_div);
+ assert_equals(scrollend_count, 1, 'Only one scrollend event should be fired');
+}
+
+function runTest() {
+ promise_test (async (t) => {
+ // Scroll down & up & down on target div and wait for the target_div to get scrollend event.
+ const move_path = [
+ { x: 0, y: -80 }, // Scroll down
+ { x: 0, y: -40 }, // Scroll up
+ { x: 0, y: -80 }, // Scroll down
+ ];
+ await testWithMovePath(t, move_path);
+ }, "Move down, up and down again, receive scrollend event only once");
+
+ promise_test (async (t) => {
+ // Scroll right & left & right on target div and wait for the target_div to get scrollend event.
+ const move_path = [
+ { x: -80, y: 0 }, // Scroll right
+ { x: -40, y: 0 }, // Scroll left
+ { x: -80, y: 0 }, // Scroll right
+ ];
+ await testWithMovePath(t, move_path);
+ }, "Move right, left and right again, receive scrollend event only once");
+
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-snap.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-snap.html
new file mode 100644
index 0000000000..03079ddc6c
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-snap.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+div {
+ position: absolute;
+}
+#scroller {
+ width: 500px;
+ height: 500px;
+ overflow: scroll;
+ scroll-snap-type: both mandatory;
+ border: solid black 5px;
+}
+#space {
+ width: 2000px;
+ height: 2000px;
+}
+.target {
+ width: 200px;
+ height: 200px;
+ scroll-snap-align: start;
+ background-color: blue;
+}
+</style>
+
+<body style="margin:0" onload=runTests()>
+ <div id="scroller">
+ <div id="space"></div>
+ <div class="target" style="left: 0px; top: 0px;"></div>
+ <div class="target" style="left: 80px; top: 80px;"></div>
+ <div class="target" style="left: 200px; top: 200px;"></div>
+ </div>
+</body>
+
+<script>
+var scroller = document.getElementById("scroller");
+var space = document.getElementById("space");
+const MAX_FRAME_COUNT = 700;
+const MAX_UNCHANGED_FRAME = 20;
+
+function scrollTop() {
+ return scroller.scrollTop;
+}
+
+var scroll_arrived_after_scroll_end = false;
+var scroll_end_arrived = false;
+scroller.addEventListener("scroll", () => {
+ if (scroll_end_arrived)
+ scroll_arrived_after_scroll_end = true;
+});
+scroller.addEventListener("scrollend", () => {
+ scroll_end_arrived = true;
+});
+
+function runTests() {
+ promise_test (async () => {
+ await waitForCompositorCommit();
+ await touchScrollInTarget(100, scroller, 'down');
+ // Wait for the scroll snap animation to finish.
+ await waitForAnimationEnd(scrollTop);
+ await waitFor(() => { return scroll_end_arrived; });
+ // Verify that scroll snap animation has finished before firing scrollend event.
+ assert_false(scroll_arrived_after_scroll_end);
+ }, "Tests that scrollend is fired after scroll snap animation completion.");
+
+ promise_test (async () => {
+ // Reset scroll state.
+ scroller.scrollTo(0, 0);
+ await waitForCompositorCommit();
+ scroll_end_arrived = false;
+ scroll_arrived_after_scroll_end = false;
+
+ await touchFlingInTarget(50, scroller, 'down');
+ // Wait for the scroll snap animation to finish.
+ await waitForAnimationEnd(scrollTop);
+ await waitFor(() => { return scroll_end_arrived; });
+ // Verify that scroll snap animation has finished before firing scrollend event.
+ assert_false(scroll_arrived_after_scroll_end);
+ }, "Tests that scrollend is fired after fling snap animation completion.");
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-mandatory-snap-point-after-load.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-mandatory-snap-point-after-load.html
new file mode 100644
index 0000000000..f379113420
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-mandatory-snap-point-after-load.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="scroll_support.js"></script>
+ <title>scrollend + mandatory scroll snap test</title>
+
+ <style>
+ #root {
+ width: 400px;
+ height: 400px;
+ overflow: auto;
+ scroll-snap-type: y mandatory;
+ border: 1px solid black;
+ --page-height: 400px;
+ }
+
+ #scroller {
+ height: 200px;
+ width: 200px;
+ overflow: auto;
+ border: 1px solid black;
+ --page-height: 200px;
+ }
+
+ .page {
+ height: var(--page-height);
+ scroll-snap-align: start;
+ }
+
+ .hidden {
+ display: none;
+ }
+ </style>
+</head>
+
+<body onload="runTests()">
+<div id="root" class="hidden">
+ <h1>scrollend + mandatory scroll snap test</h1>
+ <div id="scroller">
+ <div class="page">
+ <p>Page 1</p>
+ </div>
+ <div class="page">
+ <p>Page 2</p>
+ </div>
+ <div class="page">
+ <p>Page 3</p>
+ </div>
+ </div>
+
+ <div class="page">
+ <p>Page A</p>
+ </div>
+ <div class="page">
+ <p>Page B</p>
+ </div>
+ <div class="page">
+ <p>Page C</p>
+ </div>
+</div>
+
+<script>
+ function runTests() {
+ const root_div = document.getElementById("root");
+
+ promise_test(async (t) => {
+ const targetScrollendPromise = createScrollendPromiseForTarget(t, root_div);
+
+ await waitForNextFrame();
+ root_div.classList.remove("hidden");
+ await waitForNextFrame();
+
+ await targetScrollendPromise;
+ await verifyScrollStopped(t, root_div);
+ }, "scrollend event fired after load for mandatory snap point");
+ }
+</script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-programmatic-scroll.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-programmatic-scroll.html
new file mode 100644
index 0000000000..c6569e0beb
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-programmatic-scroll.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<meta name="timeout" content="long">
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+html {
+ height: 3000px;
+ width: 3000px;
+}
+#targetDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+}
+
+#innerDiv {
+ width: 400px;
+ height: 400px;
+}
+</style>
+
+<body style="margin:0" onload=runTest()>
+<div id="targetDiv">
+ <div id="innerDiv">
+ </div>
+</div>
+</body>
+<script>
+var element_scrollend_arrived = false;
+var document_scrollend_arrived = false;
+
+function onElementScrollEnd(event) {
+ assert_false(event.cancelable);
+ assert_false(event.bubbles);
+ element_scrollend_arrived = true;
+}
+
+function onDocumentScrollEnd(event) {
+ assert_false(event.cancelable);
+ // scrollend events are bubbled when the target node is document.
+ assert_true(event.bubbles);
+ document_scrollend_arrived = true;
+}
+
+function callScrollFunction([scrollTarget, scrollFunction, args]) {
+ scrollTarget[scrollFunction](args);
+}
+
+function runTest() {
+ let root_element = document.scrollingElement;
+ let target_div = document.getElementById("targetDiv");
+
+ promise_test (async (t) => {
+ await waitForCompositorCommit();
+ target_div.addEventListener("scrollend", onElementScrollEnd);
+ document.addEventListener("scrollend", onDocumentScrollEnd);
+
+ let test_cases = [
+ [target_div, 200, 200, [target_div, "scrollTo", { top: 200, left: 200, behavior: "auto" }]],
+ [target_div, 0, 0, [target_div, "scrollTo", { top: 0, left: 0, behavior: "smooth" }]],
+ [root_element, 200, 200, [root_element, "scrollTo", { top: 200, left: 200, behavior: "auto" }]],
+ [root_element, 0, 0, [root_element, "scrollTo", { top: 0, left: 0, behavior: "smooth" }]],
+ [target_div, 200, 200, [target_div, "scrollBy", { top: 200, left: 200, behavior: "auto" }]],
+ [target_div, 0, 0, [target_div, "scrollBy", { top: -200, left: -200, behavior: "smooth" }]],
+ [root_element, 200, 200, [root_element, "scrollBy", { top: 200, left: 200, behavior: "auto" }]],
+ [root_element, 0, 0, [root_element, "scrollBy", { top: -200, left: -200, behavior: "smooth" }]]
+ ];
+
+ for(i = 0; i < test_cases.length; i++) {
+ let t = test_cases[i];
+ let target = t[0];
+ let expected_x = t[1];
+ let expected_y = t[2];
+ let scroll_datas = t[3];
+
+ callScrollFunction(scroll_datas);
+ await waitFor(() => { return element_scrollend_arrived || document_scrollend_arrived; }, target.tagName + "." + scroll_datas[1] + " did not receive scrollend event.");
+ if (target == root_element)
+ assert_false(element_scrollend_arrived);
+ else
+ assert_false(document_scrollend_arrived);
+ assert_equals(target.scrollLeft, expected_x, target.tagName + "." + scroll_datas[1] + " scrollLeft");
+ assert_equals(target.scrollTop, expected_y, target.tagName + "." + scroll_datas[1] + " scrollTop");
+
+ element_scrollend_arrived = false;
+ document_scrollend_arrived = false;
+ }
+ }, "Tests scrollend event for calling scroll functions.");
+
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+
+ let test_cases = [
+ [target_div, "scrollTop"],
+ [target_div, "scrollLeft"],
+ [root_element, "scrollTop"],
+ [root_element, "scrollLeft"]
+ ];
+ for (i = 0; i < test_cases.length; i++) {
+ let t = test_cases[i];
+ let target = t[0];
+ let attribute = t[1];
+ let position = 200;
+
+ target.style.scrollBehavior = "smooth";
+ target[attribute] = position;
+ await waitFor(() => { return element_scrollend_arrived || document_scrollend_arrived; }, target.tagName + "." + attribute + " did not receive scrollend event.");
+ if (target == root_element)
+ assert_false(element_scrollend_arrived);
+ else
+ assert_false(document_scrollend_arrived);
+ assert_equals(target[attribute], position, target.tagName + "." + attribute + " ");
+ element_scrollend_arrived = false;
+ document_scrollend_arrived = false;
+
+ await waitForCompositorCommit();
+ target.style.scrollBehavior = "auto";
+ target[attribute] = 0;
+ await waitFor(() => { return element_scrollend_arrived || document_scrollend_arrived; }, target.tagName + "." + attribute + " did not receive scrollend event.");
+ if (target == root_element)
+ assert_false(element_scrollend_arrived);
+ else
+ assert_false(document_scrollend_arrived);
+ assert_equals(target[attribute], 0, target.tagName + "." + attribute + " ");
+ element_scrollend_arrived = false;
+ document_scrollend_arrived = false;
+ }
+ }, "Tests scrollend event for changing scroll attributes.");
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-scrollIntoView.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-scrollIntoView.html
new file mode 100644
index 0000000000..8782b1dfee
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-scrollIntoView.html
@@ -0,0 +1,124 @@
+<!DOCTYPE HTML>
+<meta name="timeout" content="long">
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+html {
+ height: 3000px;
+ width: 3000px;
+}
+#targetDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+}
+
+#innerDiv {
+ width: 400px;
+ height: 400px;
+}
+</style>
+
+<body style="margin:0" onload=runTest()>
+<div id="targetDiv">
+ <div id="innerDiv">
+ </div>
+</div>
+</body>
+<script>
+var element_scrollend_arrived = false;
+var document_scrollend_arrived = false;
+
+function onElementScrollEnd(event) {
+ assert_false(event.cancelable);
+ assert_false(event.bubbles);
+ element_scrollend_arrived = true;
+}
+
+function onDocumentScrollEnd(event) {
+ assert_false(event.cancelable);
+ // scrollend events are bubbled when the target node is document.
+ assert_true(event.bubbles);
+ document_scrollend_arrived = true;
+}
+
+function callScrollFunction([scrollTarget, scrollFunction, args]) {
+ scrollTarget[scrollFunction](args);
+}
+
+function runTest() {
+ let root_element = document.scrollingElement;
+ let target_div = document.getElementById("targetDiv");
+ let inner_div = document.getElementById("innerDiv");
+
+ // Get expected position for root_element scrollIntoView.
+ root_element.scrollTo(10000, 10000);
+ let max_root_x = root_element.scrollLeft;
+ let max_root_y = root_element.scrollTop;
+ root_element.scrollTo(0, 0);
+
+ target_div.scrollTo(10000, 10000);
+ let max_element_x = target_div.scrollLeft;
+ let max_element_y = target_div.scrollTop;
+ target_div.scrollTo(0, 0);
+
+ promise_test (async (t) => {
+ await waitForCompositorCommit();
+ target_div.addEventListener("scrollend", onElementScrollEnd);
+ document.addEventListener("scrollend", onDocumentScrollEnd);
+
+ let test_cases = [
+ [target_div, max_element_x, max_element_y, [inner_div, "scrollIntoView", { inline: "end", block: "end", behavior: "auto" }]],
+ [target_div, 0, 0, [inner_div, "scrollIntoView", { inline: "start", block: "start", behavior: "smooth" }]],
+ [root_element, max_root_x, max_root_y, [root_element, "scrollIntoView", { inline: "end", block: "end", behavior: "smooth" }]],
+ [root_element, 0, 0, [root_element, "scrollIntoView", { inline: "start", block: "start", behavior: "smooth" }]]
+ ];
+
+ for(i = 0; i < test_cases.length; i++) {
+ let t = test_cases[i];
+ let target = t[0];
+ let expected_x = t[1];
+ let expected_y = t[2];
+ let scroll_datas = t[3];
+
+ callScrollFunction(scroll_datas);
+ await waitFor(() => { return element_scrollend_arrived || document_scrollend_arrived; }, target.tagName + ".scrollIntoView did not receive scrollend event.");
+ if (target == root_element)
+ assert_false(element_scrollend_arrived);
+ else
+ assert_false(document_scrollend_arrived);
+ assert_equals(target.scrollLeft, expected_x, target.tagName + ".scrollIntoView scrollLeft");
+ assert_equals(target.scrollTop, expected_y, target.tagName + ".scrollIntoView scrollTop");
+
+ element_scrollend_arrived = false;
+ document_scrollend_arrived = false;
+ }
+ }, "Tests scrollend event for scrollIntoView.");
+
+ promise_test(async (t) => {
+ document.body.removeChild(target_div);
+ let out_div = document.createElement("div");
+ out_div.style = "width: 100px; height:100px; overflow:scroll; scroll-behavior:smooth;";
+ out_div.appendChild(target_div);
+ document.body.appendChild(out_div);
+ await waitForCompositorCommit();
+
+ element_scrollend_arrived = false;
+ document_scrollend_arrived = false;
+ inner_div.scrollIntoView({ inline: "end", block: "end", behavior: "auto" });
+ await waitFor(() => { return element_scrollend_arrived || document_scrollend_arrived; }, "Nested scrollIntoView did not receive scrollend event.");
+ assert_equals(root_element.scrollLeft, 0, "Nested scrollIntoView root_element scrollLeft");
+ assert_equals(root_element.scrollTop, 0, "Nested scrollIntoView root_element scrollTop");
+ assert_equals(out_div.scrollLeft, 100, "Nested scrollIntoView out_div scrollLeft");
+ assert_equals(out_div.scrollTop, 100, "Nested scrollIntoView out_div scrollTop");
+ assert_equals(target_div.scrollLeft, max_element_x, "Nested scrollIntoView target_div scrollLeft");
+ assert_equals(target_div.scrollTop, max_element_y, "Nested scrollIntoView target_div scrollTop");
+ assert_false(document_scrollend_arrived);
+ }, "Tests scrollend event for nested scrollIntoView.");
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-document.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-document.html
new file mode 100644
index 0000000000..797c2eb53d
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-document.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+ #hspacer {
+ height: 100px;
+ width: 100vw;
+ top: 0;
+ left: 200px;
+ /* on the right edge od targetDiv */
+ position: absolute;
+ }
+
+ #vspacer {
+ height: 100vh;
+ width: 100px;
+ position: absolute;
+ }
+
+ #targetDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+ }
+
+ #innerDiv {
+ width: 400px;
+ height: 400px;
+ }
+</style>
+
+<body style="margin:0" onload=runTest()>
+ <div id="targetDiv">
+ <div id="innerDiv"></div>
+ </div>
+ <div id="hspacer"></div>
+ <div id="vspacer"></div>
+</body>
+
+<script>
+ var target_div = document.getElementById('targetDiv');
+ async function resetScrollers(test) {
+ await waitForScrollReset(test, target_div);
+ await waitForScrollReset(test, document.scrollingElement);
+ }
+
+ function fail() {
+ assert_true(false);
+ }
+
+ function runTest() {
+ promise_test(async (t) => {
+ await resetScrollers(t);
+ await waitForCompositorCommit();
+
+ assert_equals(document.scrollingElement.scrollTop, 0,
+ "document should not be scrolled");
+
+ let scrollend_promise = waitForScrollendEvent(t, target_div);
+ let max_target_div_scroll_top = target_div.scrollHeight - target_div.clientHeight;
+ target_div.scrollTo({ top: target_div.scrollHeight, left: 0 });
+ await scrollend_promise;
+ assert_approx_equals(target_div.scrollTop, max_target_div_scroll_top, 1,
+ "target_div should be fully scrolled down");
+
+ scrollend_promise = waitForScrollendEvent(t, document, 2000);
+ target_div.addEventListener("scrollend", fail);
+ // Scroll up on target div and wait for the doc to get scrollend event.
+ await scrollElementDown(target_div, target_div.clientHeight + 25);
+ await scrollend_promise;
+
+ target_div.removeEventListener("scrollend", fail);
+ assert_greater_than(document.scrollingElement.scrollTop, target_div.clientHeight - 1,
+ "document is scrolled by the height of target_div");
+ }, "testing, vertical");
+
+ promise_test(async (t) => {
+ await resetScrollers(t);
+ await waitForCompositorCommit();
+
+ assert_equals(document.scrollingElement.scrollLeft, 0,
+ "document should not be scrolled");
+
+ let scrollend_promise = waitForScrollendEvent(t, target_div);
+ let max_target_div_scroll_left = target_div.scrollWidth - target_div.clientWidth;
+ target_div.scrollTo({ left: target_div.scrollWidth, top: 0 });
+ await scrollend_promise;
+ assert_approx_equals(target_div.scrollLeft, max_target_div_scroll_left, 1,
+ "target_div should be fully scrolled right");
+
+ scrollend_promise = waitForScrollendEvent(t, document, 2000);
+ target_div.addEventListener("scrollend", fail);
+ await scrollElementLeft(target_div, target_div.clientWidth + 25);
+ await scrollend_promise;
+
+ target_div.removeEventListener("scrollend", fail);
+ assert_greater_than(document.scrollingElement.scrollLeft, target_div.clientWidth - 1,
+ "document is scrolled by the height of target_div");
+ }, "testing, horizontal");
+ }
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-element-with-overscroll-behavior.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-element-with-overscroll-behavior.html
new file mode 100644
index 0000000000..edda88e7cb
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-element-with-overscroll-behavior.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+ #overscrollXDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+ overscroll-behavior-x: contain;
+ border: solid 1px black;
+ display: grid;
+ /* Places content and targetXDiv beside each other. */
+ grid-template-columns: 500px 100px;
+ }
+
+ #overscrollYDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+ overscroll-behavior-y: none;
+ border: solid 1px black;
+ }
+
+ #targetXDiv {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ border: solid 1px black;
+ }
+
+ #targetYDiv {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ border: solid 1px black;
+ }
+
+ .content {
+ width: 500px;
+ height: 500px;
+ }
+
+ #spacer {
+ height: 200vh;
+ width: 200vw;
+ border: solid 1px black;
+ }
+</style>
+
+<body style="margin:0" onload=runTest()>
+ <div id="overscrollXDiv">
+ <div class="content"></div>
+ <div id="targetXDiv">
+ <div class="content">
+ </div>
+ </div>
+ </div>
+ <div id="overscrollYDiv">
+ <div class="content"></div>
+ <!-- Place targetYDiv below content so that is in view when
+ overscrollYDiv is scrolled all the way down -->
+ <div id="targetYDiv">
+ <div class="content">
+ </div>
+ </div>
+ </div>
+ <div id="spacer"></div>
+</body>
+
+<script>
+ var horizontal_scrollend_arrived = false;
+ var vertical_scrollend_arrived = false;
+ let scrollers = [document.scrollingElement, targetXDiv, targetYDiv,
+ overscrollXDiv, overscrollYDiv];
+
+ async function resetScrollers(test) {
+ for (const scroller of scrollers) {
+ await resetTargetScrollState(test, scroller);
+ }
+ }
+ function onHorizontalScrollEnd(event) {
+ assert_false(event.cancelable);
+ assert_false(event.bubbles);
+ horizontal_scrollend_arrived = true;
+ }
+ function onVerticalScrollEnd(event) {
+ assert_false(event.cancelable);
+ assert_false(event.bubbles);
+ vertical_scrollend_arrived = true;
+ }
+ // Test that both "onscrollend" and addEventListener("scrollend"... work.
+ document.getElementById('overscrollXDiv').onscrollend = onHorizontalScrollEnd;
+ document.getElementById('overscrollYDiv').
+ addEventListener("scrollend", onVerticalScrollEnd);
+
+ function runTest() {
+ promise_test(async (t) => {
+ await resetScrollers(t);
+ await waitForCompositorCommit();
+
+ // Make sure that no scrollend event is sent to document.
+ document.addEventListener("scrollend",
+ t.unreached_func("Document got unexpected scrollend event."));
+ let scrollend_promise;
+
+ scrollend_promise = waitForScrollendEvent(t, targetXDiv, 2000);
+ targetXDiv.scrollLeft = targetXDiv.scrollWidth;
+ await scrollend_promise;
+
+ scrollend_promise = waitForScrollendEvent(t, overscrollXDiv, 2000);
+ overscrollXDiv.scrollLeft = overscrollXDiv.scrollWidth;
+ await scrollend_promise;
+ horizontal_scrollend_arrived = false;
+
+ assert_equals(targetXDiv.scrollLeft,
+ targetXDiv.scrollWidth - targetXDiv.clientWidth);
+ assert_equals(overscrollXDiv.scrollLeft,
+ overscrollXDiv.scrollWidth - overscrollXDiv.clientWidth);
+ // Attempt to scroll targetXDiv further to the right.
+ // targetXDiv and overscrollXDiv are already fully scrolled right but the
+ // scroll should not propagate to the document because of
+ // overscroll-behavior-x: contain on overscrollXDiv.
+ let touchEndPromise = new Promise((resolve) => {
+ targetXDiv.addEventListener("touchend", resolve);
+ });
+ await touchScrollInTarget(100, targetXDiv, 'right');
+ // The scrollend event should never be fired before the gesture has
+ // completed.
+ await touchEndPromise;
+
+ scrollend_promise = waitForScrollendEvent(t, targetYDiv, 2000);
+ targetYDiv.scrollTop = targetXDiv.scrollHeight;
+ await scrollend_promise;
+
+ scrollend_promise = waitForScrollendEvent(t, overscrollYDiv, 2000);
+ overscrollYDiv.scrollTop = overscrollYDiv.scrollHeight;
+ await scrollend_promise;
+ vertical_scrollend_arrived = false;
+
+ assert_equals(targetYDiv.scrollTop,
+ targetYDiv.scrollHeight - targetYDiv.clientHeight);
+ assert_equals(overscrollYDiv.scrollTop,
+ overscrollYDiv.scrollHeight - overscrollYDiv.clientHeight);
+ // Attempt to scroll targetYDiv further down.
+ // targetYDiv and overscrollYDiv are already fully scrolled down but the
+ // scroll should not propagate to the document because of
+ // overscroll-behavior-y: none on overscrollYDiv.
+ touchEndPromise = new Promise((resolve) => {
+ targetYDiv.addEventListener("touchend", resolve);
+ });
+ await touchScrollInTarget(50, targetYDiv, 'down');
+ // The scrollend event should never be fired before the gesture has
+ // completed.
+ await touchEndPromise;
+
+ // Ensure we wait at least a tick after the touch end.
+ await waitForCompositorCommit();
+
+ // We should not trigger a scrollend event for a scroll that did not
+ // change the scroll position.
+ assert_equals(horizontal_scrollend_arrived, false,
+ "overscrollXDiv should not receive scrollend");
+ assert_equals(vertical_scrollend_arrived, false,
+ "overscrollYDiv should not receive scrollend");
+ }, "Tests that the scroll is not propagated beyond div with non-auto " +
+ "overscroll-behavior.");
+ }
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-window.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-window.html
new file mode 100644
index 0000000000..faacf7e572
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-window.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+ #spacer {
+ height: 100vh;
+ width: 100px;
+ }
+ #targetDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+ }
+ #innerDiv {
+ width: 400px;
+ height: 400px;
+ }
+</style>
+
+<body style="margin:0" onload=runTest()>
+ <div id="targetDiv">
+ <div id="innerDiv"></div>
+ </div>
+ <div id="spacer"></div>
+</body>
+
+<script>
+ var target_div = document.getElementById('targetDiv');
+ async function resetScrollers(test) {
+ await waitForScrollReset(test, target_div);
+ await waitForScrollReset(test, document.scrollingElement);
+ }
+
+ function fail() {
+ assert_true(false);
+ }
+
+ function runTest() {
+ promise_test(async (t) => {
+ await resetScrollers(t);
+ await waitForCompositorCommit();
+
+ assert_equals(document.scrollingElement.scrollTop, 0,
+ "document should not be scrolled");
+
+ let scrollend_promise = waitForScrollendEvent(t, target_div);
+ let max_target_div_scroll_top = target_div.scrollHeight - target_div.clientHeight;
+ target_div.scrollTo({ top: target_div.scrollHeight, left: 0 });
+ await scrollend_promise;
+ assert_approx_equals(target_div.scrollTop, max_target_div_scroll_top, 1,
+ "target_div should be fully scrolled down");
+
+ scrollend_promise = waitForScrollendEvent(t, window, 2000);
+ target_div.addEventListener("scrollend", fail);
+ // Scroll up on target div and wait for the doc to get scrollend event.
+ await scrollElementDown(target_div, target_div.clientHeight + 25);
+ await scrollend_promise;
+
+ target_div.removeEventListener("scrollend", fail);
+ assert_greater_than(document.scrollingElement.scrollTop, target_div.clientHeight - 1,
+ "document is scrolled by the height of target_div");
+ }, "testing, vertical");
+ }
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-for-user-scroll.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-for-user-scroll.html
new file mode 100644
index 0000000000..561c90ca94
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-for-user-scroll.html
@@ -0,0 +1,187 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+ #targetDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+ }
+
+ #innerDiv {
+ width: 400px;
+ height: 400px;
+ }
+</style>
+</head>
+<body style="margin:0" onload=runTest()>
+ <div id="targetDiv">
+ <div id="innerDiv">
+ </div>
+ </div>
+</body>
+
+<script>
+var target_div = document.getElementById('targetDiv');
+
+function runTest() {
+ promise_test(async (t) => {
+ // Skip the test on a Mac as they do not support touch screens.
+ const isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0;
+ if (isMac)
+ return;
+
+ await resetTargetScrollState(t, target_div);
+ await waitForCompositorReady();
+
+ const timeout = 3000; // Because we have two pauses we need longer timeout
+ const targetScrollendPromise = createScrollendPromiseForTarget(t, target_div, timeout);
+ verifyNoScrollendOnDocument(t);
+
+ let scrollend_count = 0;
+ const scrollend_listener = () => {
+ scrollend_count += 1;
+ };
+ target_div.addEventListener("scrollend", scrollend_listener);
+ t.add_cleanup(() => {
+ target_div.removeEventListener('scrollend', scrollend_listener);
+ });
+
+ // Perform a touch drag on target div and wait for target_div to get
+ // a scrollend event.
+ await new test_driver.Actions()
+ .addPointer('TestPointer', 'touch')
+ .pointerMove(0, 0, {origin: target_div}) // 0, 0 is center of element.
+ .pointerDown()
+ .addTick()
+ .pointerMove(0, -40, {origin: target_div}) // Drag up to move down.
+ .addTick()
+ .pause(200) // Prevent inertial scroll.
+ .pointerMove(0, -60, {origin: target_div})
+ .addTick()
+ .pause(200) // Prevent inertial scroll.
+ .pointerUp()
+ .send();
+
+ await targetScrollendPromise;
+
+ assert_true(target_div.scrollTop > 0);
+ await verifyScrollStopped(t, target_div);
+ assert_equals(scrollend_count, 1);
+ }, 'Tests that the target_div gets scrollend event when touch dragging.');
+
+ promise_test(async (t) => {
+ // Skip test on platforms that do not have a visible scrollbar (e.g.
+ // overlay scrollbar).
+ const scrollbar_width = target_div.offsetWidth - target_div.clientWidth;
+ if (scrollbar_width == 0)
+ return;
+
+ await resetTargetScrollState(t, target_div);
+ await waitForCompositorReady();
+
+ const targetScrollendPromise = createScrollendPromiseForTarget(t, target_div);
+ verifyNoScrollendOnDocument(t);
+
+ const bounds = target_div.getBoundingClientRect();
+ // Some versions of webdriver have been known to frown at non-int arguments
+ // to pointerMove.
+ const x = bounds.right - Math.round(scrollbar_width / 2);
+ const y = bounds.bottom - 20;
+ await new test_driver.Actions()
+ .addPointer('TestPointer', 'mouse')
+ .pointerMove(x, y, {origin: 'viewport'})
+ .pointerDown()
+ .addTick()
+ .pointerUp()
+ .send();
+
+ await targetScrollendPromise;
+ assert_true(target_div.scrollTop > 0);
+ await verifyScrollStopped(t, target_div);
+ }, 'Tests that the target_div gets scrollend event when clicking ' +
+ 'scrollbar.');
+
+ // Same issue as previous test.
+ promise_test(async (t) => {
+ // Skip test on platforms that do not have a visible scrollbar (e.g.
+ // overlay scrollbar).
+ const scrollbar_width = target_div.offsetWidth - target_div.clientWidth;
+ if (scrollbar_width == 0)
+ return;
+
+ await resetTargetScrollState(t, target_div);
+ await waitForCompositorReady();
+
+ const targetScrollendPromise = createScrollendPromiseForTarget(t, target_div, 1000);
+ verifyNoScrollendOnDocument(t);
+
+ const bounds = target_div.getBoundingClientRect();
+ // Some versions of webdriver have been known to frown at non-int arguments
+ // to pointerMove.
+ const x = bounds.right - Math.round(scrollbar_width / 2);
+ const y = bounds.top + 30;
+ const dy = 30;
+ await new test_driver.Actions()
+ .addPointer('TestPointer', 'mouse')
+ .pointerMove(x, y, { origin: 'viewport' })
+ .pointerDown()
+ .pointerMove(x, y + dy, { origin: 'viewport' })
+ .addTick()
+ .pointerUp()
+ .send();
+
+ await targetScrollendPromise;
+ assert_true(target_div.scrollTop > 0);
+ await verifyScrollStopped(t, target_div);
+ }, 'Tests that the target_div gets scrollend event when dragging the ' +
+ 'scrollbar thumb.');
+
+ promise_test(async (t) => {
+ await resetTargetScrollState(t, target_div);
+ await waitForCompositorReady();
+
+ const targetScrollendPromise = createScrollendPromiseForTarget(t, target_div);
+ verifyNoScrollendOnDocument(t);
+
+ const x = 0;
+ const y = 0;
+ const dx = 0;
+ const dy = 40;
+ const duration_ms = 10;
+ await new test_driver.Actions()
+ .scroll(x, y, dx, dy, { origin: target_div }, duration_ms)
+ .send();
+
+ await targetScrollendPromise;
+ assert_true(target_div.scrollTop > 0);
+ await verifyScrollStopped(t, target_div);
+ }, 'Tests that the target_div gets scrollend event when mouse wheel ' +
+ 'scrolling.');
+
+ promise_test(async (t) => {
+ await resetTargetScrollState(t, target_div);
+ await waitForCompositorReady();
+
+ verifyNoScrollendOnDocument(t);
+ const targetScrollendPromise = createScrollendPromiseForTarget(t, target_div);
+
+ target_div.focus();
+ window.test_driver.send_keys(target_div, '\ue015');
+
+ await targetScrollendPromise;
+ assert_true(target_div.scrollTop > 0);
+ await verifyScrollStopped(t, target_div);
+ }, 'Tests that the target_div gets scrollend event when sending DOWN key ' +
+ 'to the target.');
+}
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-handler-content-attributes.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-handler-content-attributes.html
new file mode 100644
index 0000000000..47f563c39b
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-handler-content-attributes.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+html, body {
+ margin: 0
+}
+
+body {
+ height: 3000px;
+ width: 3000px;
+}
+
+#targetDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+}
+
+#innerDiv {
+ width: 400px;
+ height: 400px;
+}
+</style>
+
+<body onload=runTest() onscrollend="failOnScrollEnd(event)">
+<div id="targetDiv" onscrollend="onElementScrollEnd(event)">
+ <div id="innerDiv">
+ </div>
+</div>
+</body>
+<script>
+let element_scrollend_arrived = false;
+
+function onElementScrollEnd(event) {
+ assert_false(event.cancelable);
+ assert_false(event.bubbles);
+ element_scrollend_arrived = true;
+}
+
+function failOnScrollEnd(event) {
+ assert_true(false, "Scrollend should not be called on: " + event.target);
+}
+
+function runTest() {
+ let target_div = document.getElementById("targetDiv");
+
+ promise_test (async (t) => {
+ await waitForCompositorCommit();
+
+ target_div.scrollTo({top: 200, left: 200});
+ await waitFor(() => { return element_scrollend_arrived; },
+ target_div.tagName + " did not receive scrollend event.");
+ assert_equals(target_div.scrollLeft, 200, target_div.tagName + " scrollLeft");
+ assert_equals(target_div.scrollTop, 200, target_div.tagName + " scrollTop");
+ }, "Tests scrollend event is handled by event handler content attribute.");
+
+ promise_test (async (t) => {
+ await waitForCompositorCommit();
+
+ document.scrollingElement.scrollTo({top: 200, left: 200});
+ // The document body onscrollend event handler content attribute will fail
+ // here, if it is fired.
+ await waitForCompositorCommit();
+ assert_equals(document.scrollingElement.scrollLeft, 200,
+ "Document scrolled on horizontal axis");
+ assert_equals(document.scrollingElement.scrollTop, 200,
+ "Document scrolled on vertical axis");
+ }, "Tests scrollend event is not fired to document body event handler content attribute.");
+
+ promise_test (async (t) => {
+ await waitForCompositorCommit();
+
+ // Reset the scroll position.
+ document.scrollingElement.scrollTo({top: 0, left: 0});
+
+ let scrollend_event = new Promise(resolve => document.onscrollend = resolve);
+ document.scrollingElement.scrollTo({top: 200, left: 200});
+ await scrollend_event;
+
+ assert_equals(document.scrollingElement.scrollTop, 200,
+ "Document scrolled on horizontal axis");
+ assert_equals(document.scrollingElement.scrollLeft, 200,
+ "Document scrolled on vertical axis");
+ }, "Tests scrollend event is fired to document event handler property");
+
+ promise_test (async (t) => {
+ await waitForCompositorCommit();
+
+ // Reset the scroll position.
+ target_div.scrollTo({top: 0, left: 0});
+
+ let scrollend_event = new Promise(resolve => target_div.onscrollend = resolve);
+ target_div.scrollTo({top: 200, left: 200});
+ await scrollend_event;
+
+ assert_equals(target_div.scrollLeft, 200,
+ target_div.tagName + " scrolled on horizontal axis");
+ assert_equals(target_div.scrollLeft, 200,
+ target_div.tagName + " scrolled on vertical axis");
+ }, "Tests scrollend event is fired to element event handler property");
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-after-removing-scroller.tentative.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-after-removing-scroller.tentative.html
new file mode 100644
index 0000000000..95447fbd12
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-after-removing-scroller.tentative.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+#rootDiv {
+ width: 500px;
+ height: 500px;
+}
+
+#targetDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+}
+
+#innerDiv {
+ width: 500px;
+ height: 4000px;
+}
+</style>
+
+<body style="margin:0" onload=runTest()>
+</body>
+
+<script>
+let scrollend_arrived = false;
+
+async function setupHtmlAndScrollAndRemoveElement(element_to_remove_id) {
+ document.body.innerHTML=`
+ <div id="rootDiv">
+ <div id="targetDiv">
+ <div id="innerDiv">
+ </div>
+ </div>
+ </div>
+ `;
+ await waitForCompositorCommit();
+
+ const target_div = document.getElementById('targetDiv');
+ const element_to_remove = document.getElementById(element_to_remove_id);
+ let reached_half_scroll = false;
+ scrollend_arrived = false;
+
+ target_div.addEventListener("scrollend", () => {
+ scrollend_arrived = true;
+ });
+
+ target_div.onscroll = () => {
+ // Remove the element after reached half of the scroll offset,
+ if(target_div.scrollTop >= 1000) {
+ reached_half_scroll = true;
+ element_to_remove.remove();
+ }
+ };
+
+ target_div.scrollTo({top:2000, left:0, behavior:"smooth"});
+ await waitFor(() => {return reached_half_scroll; },
+ "target_div never reached scroll offset of 1000");
+ await waitForCompositorCommit();
+}
+
+function runTest() {
+ promise_test (async (t) => {
+ await setupHtmlAndScrollAndRemoveElement("rootDiv");
+ await conditionHolds(() => { return !scrollend_arrived; });
+ }, "No scrollend is received after removing parent div");
+
+ promise_test (async (t) => {
+ await setupHtmlAndScrollAndRemoveElement("targetDiv");
+ await conditionHolds(() => { return !scrollend_arrived; });
+ }, "No scrollend is received after removing scrolling element");
+
+ promise_test (async (t) => {
+ await setupHtmlAndScrollAndRemoveElement("innerDiv");
+ await waitFor(() => { return scrollend_arrived; },
+ 'target_div did not receive scrollend event after vertical scroll.');
+ }, "scrollend is received after removing descendant div");
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-on-no-scroll.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-on-no-scroll.html
new file mode 100644
index 0000000000..eaa345aee9
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-on-no-scroll.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="scroll_support.js"></script>
+<style>
+ #spacer {
+ height: 100vh;
+ width: 100px;
+ position: relative;
+ }
+
+ #targetDiv {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+ }
+
+ #innerDiv {
+ width: 400px;
+ height: 400px;
+ }
+</style>
+
+<body style="margin:0" onload=runTest()>
+ <div id="targetDiv">
+ <!-- This test uses button elements as a consistent mechanism for
+ ensuring that focus is on the correct scrolling element when
+ scrolling via keys -->
+ <button id="targetButton">target</button>
+ <div id="innerDiv"></div>
+ </div>
+ <button id="docButton">doc</button>
+ <div id="spacer"></div>
+</body>
+
+<script>
+ var target_div = document.getElementById('targetDiv');
+
+ async function resetScrollers(test) {
+ await waitForScrollReset(test, target_div);
+ await waitForScrollReset(test, document.scrollingElement);
+ }
+
+ function getBoundingClientRect(element) {
+ if (element == document) {
+ return document.documentElement.getBoundingClientRect();
+ }
+ return element.getBoundingClientRect();
+ }
+
+ async function upwardScroll(scrolling_element, button_element, scroll_type) {
+ if (scroll_type == "wheel") {
+ let x = 0;
+ let y = 0;
+ let delta_x = 0;
+ let delta_y = -50;
+ let actions = new test_driver.Actions()
+ .scroll(x, y, delta_x, delta_y, {origin: scrolling_element});
+ await actions.send();
+ } else if (scroll_type == "keys") {
+ const num_keydowns = 5;
+ const arrowUp = '\uE013';
+ for (let i = 0; i < num_keydowns; i++) {
+ await test_driver.send_keys(button_element, arrowUp);
+ }
+ }
+ }
+
+ async function testScrollendNotFiredOnNoScroll(test, scrolling_element,
+ listening_element,
+ button_element, scroll_type) {
+ await resetScrollers(test);
+ await waitForCompositorCommit();
+
+ assert_greater_than(scrolling_element.scrollHeight,
+ scrolling_element.clientHeight);
+ assert_equals(scrolling_element.scrollTop, 0);
+
+ let scrollend_promise = waitForScrollendEvent(test, listening_element);
+ await upwardScroll(scrolling_element, button_element, scroll_type);
+ await scrollend_promise.then(
+ (/*resolve*/) => {
+ assert_true(false, "no scroll, so no scrollend expected");
+ },
+ (/*reject*/) => { /* Did not see scrollend, which is okay. */ }
+ );
+ }
+
+ function runTest() {
+ promise_test(async (t) => {
+ await testScrollendNotFiredOnNoScroll(t, target_div, target_div,
+ targetButton, "wheel");
+ }, "No scroll via wheel on div shouldn't fire scrollend.");
+
+ promise_test(async (t) => {
+ await testScrollendNotFiredOnNoScroll(t, target_div, target_div,
+ targetButton, "keys");
+ }, "No scroll via keys on div shouldn't fire scrollend.");
+
+ promise_test(async (t) => {
+ await testScrollendNotFiredOnNoScroll(t, document.scrollingElement,
+ document, docButton, "wheel");
+ }, "No scroll via wheel on document shouldn't fire scrollend.");
+
+ promise_test(async (t) => {
+ await testScrollendNotFiredOnNoScroll(t, document.scrollingElement,
+ document, docButton, "keys");
+ }, "No scroll via keys on document shouldn't fire scrollend.")
+ }
+</script>
diff --git a/testing/web-platform/tests/dom/events/shadow-relatedTarget.html b/testing/web-platform/tests/dom/events/shadow-relatedTarget.html
new file mode 100644
index 0000000000..713555b7d8
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/shadow-relatedTarget.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!--
+ This test is adopted from Olli Pettay's test case at
+ http://mozilla.pettay.fi/shadow_focus.html
+-->
+<div id="host"></div>
+<input id="lightInput">
+<script>
+const root = host.attachShadow({ mode: "closed" });
+root.innerHTML = "<input id='shadowInput'>";
+
+async_test((test) => {
+ root.getElementById("shadowInput").focus();
+ window.addEventListener("focus", test.step_func_done((e) => {
+ assert_equals(e.relatedTarget, host);
+ }, "relatedTarget should be pointing to shadow host."), true);
+ lightInput.focus();
+}, "relatedTarget should not leak at capturing phase, at window object.");
+
+async_test((test) => {
+ root.getElementById("shadowInput").focus();
+ lightInput.addEventListener("focus", test.step_func_done((e) => {
+ assert_equals(e.relatedTarget, host);
+ }, "relatedTarget should be pointing to shadow host."), true);
+ lightInput.focus();
+}, "relatedTarget should not leak at target.");
+
+</script>
diff --git a/testing/web-platform/tests/dom/events/webkit-animation-end-event.html b/testing/web-platform/tests/dom/events/webkit-animation-end-event.html
new file mode 100644
index 0000000000..4186f6b7a9
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/webkit-animation-end-event.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Prefixed CSS Animation end events</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-listener-invoke">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+
+<script src="resources/prefixed-animation-event-tests.js"></script>
+<script>
+'use strict';
+
+runAnimationEventTests({
+ unprefixedType: 'animationend',
+ prefixedType: 'webkitAnimationEnd',
+ animationCssStyle: '1ms',
+});
+</script>
diff --git a/testing/web-platform/tests/dom/events/webkit-animation-iteration-event.html b/testing/web-platform/tests/dom/events/webkit-animation-iteration-event.html
new file mode 100644
index 0000000000..fb251972a3
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/webkit-animation-iteration-event.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Prefixed CSS Animation iteration events</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-listener-invoke">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+
+<script src="resources/prefixed-animation-event-tests.js"></script>
+<script>
+'use strict';
+
+runAnimationEventTests({
+ unprefixedType: 'animationiteration',
+ prefixedType: 'webkitAnimationIteration',
+ // Use a long duration to avoid missing the animation due to slow machines,
+ // but set a negative delay so that the iteration boundary happens shortly
+ // after the animation starts.
+ animationCssStyle: '100s -99.9s 2',
+});
+</script>
diff --git a/testing/web-platform/tests/dom/events/webkit-animation-start-event.html b/testing/web-platform/tests/dom/events/webkit-animation-start-event.html
new file mode 100644
index 0000000000..ad1036644a
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/webkit-animation-start-event.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Prefixed CSS Animation start events</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-listener-invoke">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+
+<script src="resources/prefixed-animation-event-tests.js"></script>
+<script>
+'use strict';
+
+runAnimationEventTests({
+ unprefixedType: 'animationstart',
+ prefixedType: 'webkitAnimationStart',
+ animationCssStyle: '1ms',
+});
+</script>
diff --git a/testing/web-platform/tests/dom/events/webkit-transition-end-event.html b/testing/web-platform/tests/dom/events/webkit-transition-end-event.html
new file mode 100644
index 0000000000..2741824e30
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/webkit-transition-end-event.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Prefixed CSS Transition End event</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-listener-invoke">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+
+<script src="resources/prefixed-animation-event-tests.js"></script>
+<script>
+'use strict';
+
+runAnimationEventTests({
+ isTransition: true,
+ unprefixedType: 'transitionend',
+ prefixedType: 'webkitTransitionEnd',
+ animationCssStyle: '1ms',
+});
+</script>
diff --git a/testing/web-platform/tests/dom/historical.html b/testing/web-platform/tests/dom/historical.html
new file mode 100644
index 0000000000..1bc209ec0e
--- /dev/null
+++ b/testing/web-platform/tests/dom/historical.html
@@ -0,0 +1,222 @@
+<!DOCTYPE html>
+<title>Historical DOM features must be removed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+function isInterfaceRemoved(name) {
+ test(function() {
+ assert_false(name in window)
+ assert_equals(window[name], undefined)
+ }, "Historical DOM features must be removed: " + name)
+}
+var removedInterfaces = [
+ "DOMConfiguration",
+ "DOMCursor",
+ "DOMError",
+ "DOMErrorHandler",
+ "DOMImplementationList",
+ "DOMImplementationSource",
+ "DOMLocator",
+ "DOMObject",
+ "DOMRequest",
+ "DOMSettableTokenList",
+ "DOMUserData",
+ "Entity",
+ "EntityReference",
+ "EventException", // DOM Events
+ "NameList",
+ "Notation",
+ "TypeInfo",
+ "UserDataHandler",
+ "RangeException", // DOM Range
+ "SVGPathSegList"
+]
+removedInterfaces.forEach(isInterfaceRemoved)
+
+function isRemovedFromDocument(name) {
+ test(function() {
+ var doc = document.implementation.createDocument(null,null,null)
+ assert_false(name in document)
+ assert_equals(document[name], undefined)
+ assert_false(name in doc)
+ assert_equals(doc[name], undefined)
+ }, "Historical DOM features must be removed: " + name)
+}
+var documentRemoved = [
+ "createEntityReference",
+ "xmlEncoding",
+ "xmlStandalone",
+ "xmlVersion",
+ "strictErrorChecking",
+ "domConfig",
+ "normalizeDocument",
+ "renameNode",
+ "defaultCharset",
+ "height",
+ "width",
+ // https://github.com/whatwg/html/commit/a64aea7fdb221bba027d95dc3cabda09e0b3e5dc
+ "commands",
+ // https://github.com/whatwg/html/commit/797b4d273955a0fe3cc2e2d0ca5d578f37c0f126
+ "cssElementMap",
+ // https://github.com/whatwg/html/commit/e236f46820b93d6fe2e2caae0363331075c6c4fb
+ "async",
+ // https://github.com/whatwg/dom/pull/815
+ "origin",
+]
+documentRemoved.forEach(isRemovedFromDocument)
+
+test(function() {
+ // https://github.com/whatwg/html/commit/e236f46820b93d6fe2e2caae0363331075c6c4fb
+ assert_false("load" in document);
+ assert_equals(document["load"], undefined)
+}, "document.load");
+
+test(function() {
+ // https://github.com/whatwg/html/commit/523f7a8773d2ab8a1eb0da6510651e8c5d2a7531
+ var doc = document.implementation.createDocument(null, null, null);
+ assert_false("load" in doc);
+ assert_equals(doc["load"], undefined)
+}, "XMLDocument.load");
+
+test(function() {
+ assert_false("getFeature" in document.implementation)
+ assert_equals(document.implementation["getFeature"], undefined)
+}, "DOMImplementation.getFeature() must be removed.")
+
+function isRemovedFromElement(name) {
+ test(function() {
+ var ele = document.createElementNS("test", "test")
+ assert_false(name in document.body)
+ assert_equals(document.body[name], undefined)
+ assert_false(name in ele)
+ assert_equals(ele[name], undefined)
+ }, "Historical DOM features must be removed: " + name)
+}
+var elementRemoved = [
+ "schemaTypeInfo",
+ "setIdAttribute",
+ "setIdAttributeNS",
+ "setIdAttributeNode"
+]
+elementRemoved.forEach(isRemovedFromElement)
+
+function isRemovedFromAttr(name) {
+ test(function() {
+ var attr = document.createAttribute("test")
+ assert_false(name in attr)
+ assert_equals(attr[name], undefined)
+ }, "Attr member must be removed: " + name)
+}
+var attrRemoved = [
+ "schemaTypeInfo",
+ "isId"
+]
+attrRemoved.forEach(isRemovedFromAttr)
+
+function isRemovedFromDoctype(name) {
+ test(function() {
+ var doctype = document.implementation.createDocumentType("test", "", "")
+ assert_false(name in doctype)
+ assert_equals(doctype[name], undefined)
+ }, "DocumentType member must be removed: " + name)
+}
+var doctypeRemoved = [
+ "entities",
+ "notations",
+ "internalSubset"
+]
+doctypeRemoved.forEach(isRemovedFromDoctype)
+
+function isRemovedFromText(name) {
+ test(function() {
+ var text = document.createTextNode("test")
+ assert_false(name in text)
+ assert_equals(text[name], undefined)
+ }, "Text member must be removed: " + name)
+}
+var textRemoved = [
+ "isElementContentWhitespace",
+ "replaceWholeText"
+]
+textRemoved.forEach(isRemovedFromText)
+
+function isRemovedFromNode(name) {
+ test(function() {
+ var doc = document.implementation.createDocument(null,null,null)
+ var doctype = document.implementation.createDocumentType("test", "", "")
+ var text = document.createTextNode("test")
+ assert_false(name in doc)
+ assert_equals(doc[name], undefined)
+ assert_false(name in doctype)
+ assert_equals(doctype[name], undefined)
+ assert_false(name in text)
+ assert_equals(text[name], undefined)
+ }, "Node member must be removed: " + name)
+}
+var nodeRemoved = [
+ "hasAttributes",
+ "attributes",
+ "namespaceURI",
+ "prefix",
+ "localName",
+ "isSupported",
+ "getFeature",
+ "getUserData",
+ "setUserData",
+ "rootNode",
+]
+nodeRemoved.forEach(isRemovedFromNode)
+
+function isRemovedFromWindow(name) {
+ test(function() {
+ assert_false(name in window)
+ assert_equals(window[name], undefined)
+ }, "Window member must be removed: " + name)
+}
+var windowRemoved = [
+ "attachEvent",
+ "content",
+ "sidebar",
+]
+windowRemoved.forEach(isRemovedFromWindow)
+
+function isRemovedFromEvent(name) {
+ test(() => {
+ assert_false(name in Event)
+ assert_equals(Event[name], undefined)
+ }, "Event should not have this constant: " + name)
+}
+var EventRemoved = [
+ "MOUSEDOWN",
+ "MOUSEUP",
+ "MOUSEOVER",
+ "MOUSEOUT",
+ "MOUSEMOVE",
+ "MOUSEDRAG",
+ "CLICK",
+ "DBLCLICK",
+ "KEYDOWN",
+ "KEYUP",
+ "KEYPRESS",
+ "DRAGDROP",
+ "FOCUS",
+ "BLUR",
+ "SELECT",
+ "CHANGE"
+]
+EventRemoved.forEach(isRemovedFromEvent)
+
+var EventPrototypeRemoved = [
+ "getPreventDefault",
+ "path"
+]
+EventPrototypeRemoved.forEach(name => {
+ test(() => {
+ assert_false(name in Event.prototype)
+ assert_equals(Event.prototype[name], undefined)
+ assert_false(name in new Event("test"))
+ assert_equals((new Event("test"))[name], undefined)
+ }, "Event.prototype should not have this property: " + name)
+})
+</script>
diff --git a/testing/web-platform/tests/dom/idlharness-shadowrealm.window.js b/testing/web-platform/tests/dom/idlharness-shadowrealm.window.js
new file mode 100644
index 0000000000..cb03c07c9b
--- /dev/null
+++ b/testing/web-platform/tests/dom/idlharness-shadowrealm.window.js
@@ -0,0 +1,2 @@
+// META: script=/resources/idlharness-shadowrealm.js
+idl_test_shadowrealm(["dom"], ["html"]);
diff --git a/testing/web-platform/tests/dom/idlharness.any.js b/testing/web-platform/tests/dom/idlharness.any.js
new file mode 100644
index 0000000000..26da3ab3bf
--- /dev/null
+++ b/testing/web-platform/tests/dom/idlharness.any.js
@@ -0,0 +1,25 @@
+// META: global=worker
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: timeout=long
+
+// Note: This test doesn't cover the Window context, see idlharness.window.js
+// for that coverage and why it can't be merged into this test.
+
+'use strict';
+
+idl_test(
+ ['dom'],
+ ['html'],
+ idl_array => {
+ idl_array.add_objects({
+ EventTarget: ['new EventTarget()'],
+ Event: ['new Event("foo")'],
+ CustomEvent: ['new CustomEvent("foo")'],
+ AbortController: ['new AbortController()'],
+ AbortSignal: ['new AbortController().signal'],
+ });
+ }
+);
+
+done();
diff --git a/testing/web-platform/tests/dom/idlharness.window.js b/testing/web-platform/tests/dom/idlharness.window.js
new file mode 100644
index 0000000000..2c7bfd727e
--- /dev/null
+++ b/testing/web-platform/tests/dom/idlharness.window.js
@@ -0,0 +1,52 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: script=/common/subset-tests-by-key.js
+// META: variant=?include=Node
+// META: variant=?exclude=Node
+// META: timeout=long
+
+// Note: This isn't merged into idlharness.any.js because of the use of variants,
+// i.e., include=Node wouldn't make sense for workers.
+
+'use strict';
+
+idl_test(
+ ['dom', 'fullscreen'],
+ ['html'],
+ idl_array => {
+ self.xmlDoc = document.implementation.createDocument(null, '', null);
+ self.detachedRange = document.createRange();
+ detachedRange.detach();
+ self.element = xmlDoc.createElementNS(null, 'test');
+ element.setAttribute('bar', 'baz');
+
+ idl_array.add_objects({
+ EventTarget: ['new EventTarget()'],
+ Event: ['document.createEvent("Event")', 'new Event("foo")'],
+ CustomEvent: ['new CustomEvent("foo")'],
+ AbortController: ['new AbortController()'],
+ AbortSignal: ['new AbortController().signal'],
+ Document: ['new Document()'],
+ XMLDocument: ['xmlDoc'],
+ DOMImplementation: ['document.implementation'],
+ DocumentFragment: ['document.createDocumentFragment()'],
+ DocumentType: ['document.doctype'],
+ Element: ['element'],
+ Attr: ['document.querySelector("[id]").attributes[0]'],
+ Text: ['document.createTextNode("abc")'],
+ ProcessingInstruction: ['xmlDoc.createProcessingInstruction("abc", "def")'],
+ Comment: ['document.createComment("abc")'],
+ Range: ['document.createRange()', 'detachedRange'],
+ NodeIterator: ['document.createNodeIterator(document.body, NodeFilter.SHOW_ALL, null, false)'],
+ TreeWalker: ['document.createTreeWalker(document.body, NodeFilter.SHOW_ALL, null, false)'],
+ NodeList: ['document.querySelectorAll("script")'],
+ HTMLCollection: ['document.body.children'],
+ DOMTokenList: ['document.body.classList'],
+ XPathEvaluator: ['new XPathEvaluator()'],
+ XPathExpression: ['document.createExpression("//*")'],
+ XPathNSResolver: ['document.createNSResolver(document.body)'],
+ XPathResult: ['document.evaluate("//*", document.body)'],
+ XSLTProcessor: ['new XSLTProcessor()'],
+ });
+ }
+);
diff --git a/testing/web-platform/tests/dom/interface-objects.html b/testing/web-platform/tests/dom/interface-objects.html
new file mode 100644
index 0000000000..936d63517e
--- /dev/null
+++ b/testing/web-platform/tests/dom/interface-objects.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<title>Interfaces</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function testInterfaceDeletable(iface) {
+ test(function() {
+ assert_true(!!window[iface], "Interface should exist.")
+ assert_true(delete window[iface], "The delete operator should return true.")
+ assert_equals(window[iface], undefined, "Interface should be gone.")
+ }, "Should be able to delete " + iface + ".")
+}
+var interfaces = [
+ "Event",
+ "CustomEvent",
+ "EventTarget",
+ "AbortController",
+ "AbortSignal",
+ "Node",
+ "Document",
+ "DOMImplementation",
+ "DocumentFragment",
+ "ProcessingInstruction",
+ "DocumentType",
+ "Element",
+ "Attr",
+ "CharacterData",
+ "Text",
+ "Comment",
+ "NodeIterator",
+ "TreeWalker",
+ "NodeFilter",
+ "NodeList",
+ "HTMLCollection",
+ "DOMTokenList"
+];
+test(function() {
+ for (var p in window) {
+ interfaces.forEach(function(i) {
+ assert_not_equals(p, i)
+ })
+ }
+}, "Interface objects properties should not be Enumerable")
+interfaces.forEach(testInterfaceDeletable);
+</script>
diff --git a/testing/web-platform/tests/dom/lists/DOMTokenList-Iterable.html b/testing/web-platform/tests/dom/lists/DOMTokenList-Iterable.html
new file mode 100644
index 0000000000..4cf84b12a2
--- /dev/null
+++ b/testing/web-platform/tests/dom/lists/DOMTokenList-Iterable.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>DOMTokenList Iterable Test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<span class="foo Foo foo "></span>
+<script>
+ var elementClasses;
+ setup(function() {
+ elementClasses = document.querySelector("span").classList;
+ })
+ test(function() {
+ assert_true('length' in elementClasses);
+ }, 'DOMTokenList has length method.');
+ test(function() {
+ assert_true('values' in elementClasses);
+ }, 'DOMTokenList has values method.');
+ test(function() {
+ assert_true('entries' in elementClasses);
+ }, 'DOMTokenList has entries method.');
+ test(function() {
+ assert_true('forEach' in elementClasses);
+ }, 'DOMTokenList has forEach method.');
+ test(function() {
+ assert_true(Symbol.iterator in elementClasses);
+ }, 'DOMTokenList has Symbol.iterator.');
+ test(function() {
+ var classList = [];
+ for (var className of elementClasses){
+ classList.push(className);
+ }
+ assert_array_equals(classList, ['foo', 'Foo']);
+ }, 'DOMTokenList is iterable via for-of loop.');
+</script>
diff --git a/testing/web-platform/tests/dom/lists/DOMTokenList-coverage-for-attributes.html b/testing/web-platform/tests/dom/lists/DOMTokenList-coverage-for-attributes.html
new file mode 100644
index 0000000000..e5f060b8ac
--- /dev/null
+++ b/testing/web-platform/tests/dom/lists/DOMTokenList-coverage-for-attributes.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>DOMTokenList coverage for attributes</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+"use strict";
+
+var pairs = [
+ // Defined in DOM
+ {attr: "classList", sup: ["anyElement"]},
+ // Defined in HTML except for a which is also SVG
+ {attr: "relList", sup: ["a", "area", "link"]},
+ // Defined in HTML
+ {attr: "htmlFor", sup: ["output"]},
+ {attr: "sandbox", sup: ["iframe"]},
+ {attr: "sizes", sup: ["link"]}
+];
+var namespaces = [
+ "http://www.w3.org/1999/xhtml",
+ "http://www.w3.org/2000/svg",
+ "http://www.w3.org/1998/Math/MathML",
+ "http://example.com/",
+ ""
+];
+
+var elements = ["a", "area", "link", "iframe", "output", "td", "th"];
+function testAttr(pair, new_el){
+ return (pair.attr === "classList" ||
+ (pair.attr === "relList" && new_el.localName === "a" &&
+ new_el.namespaceURI === "http://www.w3.org/2000/svg") ||
+ (new_el.namespaceURI === "http://www.w3.org/1999/xhtml" &&
+ pair.sup.indexOf(new_el.localName) != -1));
+}
+
+pairs.forEach(function(pair) {
+ namespaces.forEach(function(ns) {
+ elements.forEach(function(el) {
+ var new_el = document.createElementNS(ns, el);
+ if (testAttr(pair, new_el)) {
+ test(function() {
+ assert_class_string(new_el[pair.attr], "DOMTokenList");
+ }, new_el.localName + "." + pair.attr + " in " + new_el.namespaceURI + " namespace should be DOMTokenList.");
+ }
+ else {
+ test(function() {
+ assert_equals(new_el[pair.attr], undefined);
+ }, new_el.localName + "." + pair.attr + " in " + new_el.namespaceURI + " namespace should be undefined.");
+ }
+ });
+ });
+});
+
+</script>
diff --git a/testing/web-platform/tests/dom/lists/DOMTokenList-iteration.html b/testing/web-platform/tests/dom/lists/DOMTokenList-iteration.html
new file mode 100644
index 0000000000..f713ad4aa0
--- /dev/null
+++ b/testing/web-platform/tests/dom/lists/DOMTokenList-iteration.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>DOMTokenList iteration: keys, values, etc.</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<span class=" a a b "></span>
+<script>
+ test(() => {
+ var list = document.querySelector("span").classList;
+ assert_array_equals([...list], ["a", "b"]);
+ }, "classList");
+
+ test(() => {
+ var keys = document.querySelector("span").classList.keys();
+ assert_false(keys instanceof Array, "must not be Array");
+ keys = [...keys];
+ assert_array_equals(keys, [0, 1]);
+ }, "classList.keys");
+
+ test(() => {
+ var values = document.querySelector("span").classList.values();
+ assert_false(values instanceof Array, "must not be Array");
+ values = [...values];
+ assert_array_equals(values, ["a", "b"]);
+ }, "classList.values");
+
+ test(() => {
+ var entries = document.querySelector("span").classList.entries();
+ assert_false(entries instanceof Array, "must not be Array");
+ entries = [...entries];
+ var keys = [...document.querySelector("span").classList.keys()];
+ var values = [...document.querySelector("span").classList.values()];
+ assert_equals(entries.length, keys.length, "entries.length == keys.length");
+ assert_equals(entries.length, values.length,
+ "entries.length == values.length");
+ for (var i = 0; i < entries.length; ++i) {
+ assert_array_equals(entries[i], [keys[i], values[i]],
+ "entries[" + i + "]");
+ }
+ }, "classList.entries");
+
+ test(() => {
+ var list = document.querySelector("span").classList;
+ var values = [...list.values()];
+ var keys = [...list.keys()];
+ var entries = [...list.entries()];
+
+ var cur = 0;
+ var thisObj = {};
+ list.forEach(function(value, key, listObj) {
+ assert_equals(listObj, list, "Entry " + cur + " listObj");
+ assert_equals(this, thisObj, "Entry " + cur + " this");
+ assert_equals(value, values[cur], "Entry " + cur + " value");
+ assert_equals(key, keys[cur], "Entry " + cur + " key");
+ cur++;
+ }, thisObj);
+ assert_equals(cur, entries.length, "length");
+ }, "classList.forEach");
+
+ test(() => {
+ var list = document.querySelector("span").classList;
+ assert_equals(list[Symbol.iterator], Array.prototype[Symbol.iterator],
+ "[Symbol.iterator]");
+ assert_equals(list.keys, Array.prototype.keys, ".keys");
+ if (Array.prototype.values) {
+ assert_equals(list.values, Array.prototype.values, ".values");
+ }
+ assert_equals(list.entries, Array.prototype.entries, ".entries");
+ assert_equals(list.forEach, Array.prototype.forEach, ".forEach");
+ }, "classList inheritance from Array.prototype");
+</script>
diff --git a/testing/web-platform/tests/dom/lists/DOMTokenList-stringifier.html b/testing/web-platform/tests/dom/lists/DOMTokenList-stringifier.html
new file mode 100644
index 0000000000..b125388e02
--- /dev/null
+++ b/testing/web-platform/tests/dom/lists/DOMTokenList-stringifier.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>DOMTokenList stringifier</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-domtokenlist-stringifier">
+<link rel=author title=Ms2ger href="mailto:Ms2ger@gmail.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<span class=" a a b "></span>
+<script>
+test(function() {
+ assert_equals(String(document.createElement("span").classList), "",
+ "String(classList) should return the empty list for an undefined class attribute");
+ var span = document.querySelector("span");
+ assert_equals(span.getAttribute("class"), " a a b ",
+ "getAttribute should return the literal value");
+ assert_equals(span.className, " a a b ",
+ "className should return the literal value");
+ assert_equals(String(span.classList), " a a b ",
+ "String(classList) should return the literal value");
+ assert_equals(span.classList.toString(), " a a b ",
+ "classList.toString() should return the literal value");
+ assert_class_string(span.classList, "DOMTokenList");
+});
+</script>
diff --git a/testing/web-platform/tests/dom/lists/DOMTokenList-value.html b/testing/web-platform/tests/dom/lists/DOMTokenList-value.html
new file mode 100644
index 0000000000..b0e39111d9
--- /dev/null
+++ b/testing/web-platform/tests/dom/lists/DOMTokenList-value.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>DOMTokenList value</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-domtokenlist-value">
+<link rel=author title=Tangresh href="mailto:dmenzi@tangresh.ch">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<span class=" a a b "></span>
+<script>
+test(function() {
+ assert_equals(String(document.createElement("span").classList.value), "",
+ "classList.value should return the empty list for an undefined class attribute");
+ var span = document.querySelector("span");
+ assert_equals(span.classList.value, " a a b ",
+ "value should return the literal value");
+ span.classList.value = " foo bar foo ";
+ assert_equals(span.classList.value, " foo bar foo ",
+ "assigning value should set the literal value");
+ assert_equals(span.classList.length, 2,
+ "length should be the number of tokens");
+ assert_class_string(span.classList, "DOMTokenList");
+ assert_class_string(span.classList.value, "String");
+});
+</script>
diff --git a/testing/web-platform/tests/dom/lists/README.md b/testing/web-platform/tests/dom/lists/README.md
new file mode 100644
index 0000000000..59c821a7da
--- /dev/null
+++ b/testing/web-platform/tests/dom/lists/README.md
@@ -0,0 +1 @@
+See `../nodes/Element-classlist.html` for more DOMTokenList tests.
diff --git a/testing/web-platform/tests/dom/nodes/CharacterData-appendChild.html b/testing/web-platform/tests/dom/nodes/CharacterData-appendChild.html
new file mode 100644
index 0000000000..eb4f36c681
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/CharacterData-appendChild.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Node.appendChild applied to CharacterData</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-appendchild">
+<link rel=help href="https://dom.spec.whatwg.org/#introduction-to-the-dom">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function create(type) {
+ switch (type) {
+ case "Text": return document.createTextNode("test"); break;
+ case "Comment": return document.createComment("test"); break;
+ case "ProcessingInstruction": return document.createProcessingInstruction("target", "test"); break;
+ }
+}
+
+function testNode(type1, type2) {
+ test(function() {
+ var node1 = create(type1);
+ var node2 = create(type2);
+ assert_throws_dom("HierarchyRequestError", function () {
+ node1.appendChild(node2);
+ }, "CharacterData type " + type1 + " must not have children");
+ }, type1 + ".appendChild(" + type2 + ")");
+}
+
+var types = ["Text", "Comment", "ProcessingInstruction"];
+types.forEach(function(type1) {
+ types.forEach(function(type2) {
+ testNode(type1, type2);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/CharacterData-appendData.html b/testing/web-platform/tests/dom/nodes/CharacterData-appendData.html
new file mode 100644
index 0000000000..a5d4072244
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/CharacterData-appendData.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CharacterData.appendData</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-appenddata">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-data">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function testNode(create, type) {
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.appendData("bar")
+ assert_equals(node.data, "testbar")
+ }, type + ".appendData('bar')")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.appendData("")
+ assert_equals(node.data, "test")
+ }, type + ".appendData('')")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+ node.appendData(", append more 資料,測試資料");
+ assert_equals(node.data, "test, append more 資料,測試資料");
+ assert_equals(node.length, 25);
+ }, type + ".appendData(non-ASCII)")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.appendData(null)
+ assert_equals(node.data, "testnull")
+ }, type + ".appendData(null)")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.appendData(undefined)
+ assert_equals(node.data, "testundefined")
+ }, type + ".appendData(undefined)")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.appendData("", "bar")
+ assert_equals(node.data, "test")
+ }, type + ".appendData('', 'bar')")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_throws_js(TypeError, function() { node.appendData() });
+ assert_equals(node.data, "test")
+ }, type + ".appendData()")
+}
+
+testNode(function() { return document.createTextNode("test") }, "Text")
+testNode(function() { return document.createComment("test") }, "Comment")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/CharacterData-data.html b/testing/web-platform/tests/dom/nodes/CharacterData-data.html
new file mode 100644
index 0000000000..b3b29ea4d7
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/CharacterData-data.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CharacterData.data</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-data">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function testNode(create, type) {
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+ assert_equals(node.length, 4)
+ }, type + ".data initial value")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = null;
+ assert_equals(node.data, "")
+ assert_equals(node.length, 0)
+ }, type + ".data = null")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = undefined;
+ assert_equals(node.data, "undefined")
+ assert_equals(node.length, 9)
+ }, type + ".data = undefined")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = 0;
+ assert_equals(node.data, "0")
+ assert_equals(node.length, 1)
+ }, type + ".data = 0")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "";
+ assert_equals(node.data, "")
+ assert_equals(node.length, 0)
+ }, type + ".data = ''")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "--";
+ assert_equals(node.data, "--")
+ assert_equals(node.length, 2)
+ }, type + ".data = '--'")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "資料";
+ assert_equals(node.data, "資料")
+ assert_equals(node.length, 2)
+ }, type + ".data = '資料'")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "🌠 test 🌠 TEST";
+ assert_equals(node.data, "🌠 test 🌠 TEST")
+ assert_equals(node.length, 15) // Counting UTF-16 code units
+ }, type + ".data = '🌠 test 🌠 TEST'")
+}
+
+testNode(function() { return document.createTextNode("test") }, "Text")
+testNode(function() { return document.createComment("test") }, "Comment")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/CharacterData-deleteData.html b/testing/web-platform/tests/dom/nodes/CharacterData-deleteData.html
new file mode 100644
index 0000000000..84f4d5b415
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/CharacterData-deleteData.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CharacterData.deleteData</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-deletedata">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-data">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function testNode(create, type) {
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_throws_dom("INDEX_SIZE_ERR", function() { node.deleteData(5, 10) })
+ assert_throws_dom("INDEX_SIZE_ERR", function() { node.deleteData(5, 0) })
+ assert_throws_dom("INDEX_SIZE_ERR", function() { node.deleteData(-1, 10) })
+ assert_throws_dom("INDEX_SIZE_ERR", function() { node.deleteData(-1, 0) })
+ }, type + ".deleteData() out of bounds")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.deleteData(0, 2)
+ assert_equals(node.data, "st")
+ }, type + ".deleteData() at the start")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.deleteData(2, 10)
+ assert_equals(node.data, "te")
+ }, type + ".deleteData() at the end")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.deleteData(1, 1)
+ assert_equals(node.data, "tst")
+ }, type + ".deleteData() in the middle")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.deleteData(2, 0)
+ assert_equals(node.data, "test")
+
+ node.deleteData(0, 0)
+ assert_equals(node.data, "test")
+ }, type + ".deleteData() with zero count")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.deleteData(2, -1)
+ assert_equals(node.data, "te")
+ }, type + ".deleteData() with small negative count")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.deleteData(1, -0x100000000 + 2)
+ assert_equals(node.data, "tt")
+ }, type + ".deleteData() with large negative count")
+
+ test(function() {
+ var node = create()
+ node.data = "This is the character data test, append more 資料,更多測試資料";
+
+ node.deleteData(40, 5);
+ assert_equals(node.data, "This is the character data test, append 資料,更多測試資料");
+ node.deleteData(45, 2);
+ assert_equals(node.data, "This is the character data test, append 資料,更多資料");
+ }, type + ".deleteData() with non-ascii data")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "🌠 test 🌠 TEST"
+
+ node.deleteData(5, 8); // Counting UTF-16 code units
+ assert_equals(node.data, "🌠 teST");
+ }, type + ".deleteData() with non-BMP data")
+}
+
+testNode(function() { return document.createTextNode("test") }, "Text")
+testNode(function() { return document.createComment("test") }, "Comment")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/CharacterData-insertData.html b/testing/web-platform/tests/dom/nodes/CharacterData-insertData.html
new file mode 100644
index 0000000000..62c0ca1018
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/CharacterData-insertData.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CharacterData.insertData</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-insertdata">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-data">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function testNode(create, type) {
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_throws_dom("INDEX_SIZE_ERR", function() { node.insertData(5, "x") })
+ assert_throws_dom("INDEX_SIZE_ERR", function() { node.insertData(5, "") })
+ }, type + ".insertData() out of bounds")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_throws_dom("INDEX_SIZE_ERR", function() { node.insertData(-1, "x") })
+ assert_throws_dom("INDEX_SIZE_ERR", function() { node.insertData(-0x100000000 + 5, "x") })
+ }, type + ".insertData() negative out of bounds")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.insertData(-0x100000000 + 2, "X")
+ assert_equals(node.data, "teXst")
+ }, type + ".insertData() negative in bounds")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.insertData(0, "")
+ assert_equals(node.data, "test")
+ }, type + ".insertData('')")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.insertData(0, "X")
+ assert_equals(node.data, "Xtest")
+ }, type + ".insertData() at the start")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.insertData(2, "X")
+ assert_equals(node.data, "teXst")
+ }, type + ".insertData() in the middle")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.insertData(4, "ing")
+ assert_equals(node.data, "testing")
+ }, type + ".insertData() at the end")
+
+ test(function() {
+ var node = create()
+ node.data = "This is the character data, append more 資料,測試資料";
+
+ node.insertData(26, " test");
+ assert_equals(node.data, "This is the character data test, append more 資料,測試資料");
+ node.insertData(48, "更多");
+ assert_equals(node.data, "This is the character data test, append more 資料,更多測試資料");
+ }, type + ".insertData() with non-ascii data")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "🌠 test 🌠 TEST"
+
+ node.insertData(5, "--"); // Counting UTF-16 code units
+ assert_equals(node.data, "🌠 te--st 🌠 TEST");
+ }, type + ".insertData() with non-BMP data")
+}
+
+testNode(function() { return document.createTextNode("test") }, "Text")
+testNode(function() { return document.createComment("test") }, "Comment")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/CharacterData-remove.html b/testing/web-platform/tests/dom/nodes/CharacterData-remove.html
new file mode 100644
index 0000000000..aef9d56bfa
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/CharacterData-remove.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CharacterData.remove</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-childnode-remove">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="ChildNode-remove.js"></script>
+<div id=log></div>
+<script>
+var text, text_parent,
+ comment, comment_parent,
+ pi, pi_parent;
+setup(function() {
+ text = document.createTextNode("text");
+ text_parent = document.createElement("div");
+ comment = document.createComment("comment");
+ comment_parent = document.createElement("div");
+ pi = document.createProcessingInstruction("foo", "bar");
+ pi_parent = document.createElement("div");
+});
+testRemove(text, text_parent, "text");
+testRemove(comment, comment_parent, "comment");
+testRemove(pi, pi_parent, "PI");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/CharacterData-replaceData.html b/testing/web-platform/tests/dom/nodes/CharacterData-replaceData.html
new file mode 100644
index 0000000000..537751d832
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/CharacterData-replaceData.html
@@ -0,0 +1,163 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CharacterData.replaceData</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-replacedata">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-data">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function testNode(create, type) {
+ // Step 2.
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_throws_dom("IndexSizeError", function() { node.replaceData(5, 1, "x") })
+ assert_throws_dom("IndexSizeError", function() { node.replaceData(5, 0, "") })
+ assert_throws_dom("IndexSizeError", function() { node.replaceData(-1, 1, "x") })
+ assert_throws_dom("IndexSizeError", function() { node.replaceData(-1, 0, "") })
+ assert_equals(node.data, "test")
+ }, type + ".replaceData() with invalid offset")
+
+ // Step 3.
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(2, 10, "yo")
+ assert_equals(node.data, "teyo")
+ }, type + ".replaceData() with clamped count")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(2, -1, "yo")
+ assert_equals(node.data, "teyo")
+ }, type + ".replaceData() with negative clamped count")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(0, 0, "yo")
+ assert_equals(node.data, "yotest")
+ }, type + ".replaceData() before the start")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(0, 2, "y")
+ assert_equals(node.data, "yst")
+ }, type + ".replaceData() at the start (shorter)")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(0, 2, "yo")
+ assert_equals(node.data, "yost")
+ }, type + ".replaceData() at the start (equal length)")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(0, 2, "yoa")
+ assert_equals(node.data, "yoast")
+ }, type + ".replaceData() at the start (longer)")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(1, 2, "o")
+ assert_equals(node.data, "tot")
+ }, type + ".replaceData() in the middle (shorter)")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(1, 2, "yo")
+ assert_equals(node.data, "tyot")
+ }, type + ".replaceData() in the middle (equal length)")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(1, 1, "waddup")
+ assert_equals(node.data, "twaddupst")
+ node.replaceData(1, 1, "yup")
+ assert_equals(node.data, "tyupaddupst")
+ }, type + ".replaceData() in the middle (longer)")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(1, 20, "yo")
+ assert_equals(node.data, "tyo")
+ }, type + ".replaceData() at the end (shorter)")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(2, 20, "yo")
+ assert_equals(node.data, "teyo")
+ }, type + ".replaceData() at the end (same length)")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(4, 20, "yo")
+ assert_equals(node.data, "testyo")
+ }, type + ".replaceData() at the end (longer)")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(0, 4, "quux")
+ assert_equals(node.data, "quux")
+ }, type + ".replaceData() the whole string")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.replaceData(0, 4, "")
+ assert_equals(node.data, "")
+ }, type + ".replaceData() with the empty string")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "This is the character data test, append 資料,更多資料";
+
+ node.replaceData(33, 6, "other");
+ assert_equals(node.data, "This is the character data test, other 資料,更多資料");
+ node.replaceData(44, 2, "文字");
+ assert_equals(node.data, "This is the character data test, other 資料,更多文字");
+ }, type + ".replaceData() with non-ASCII data")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "🌠 test 🌠 TEST"
+
+ node.replaceData(5, 8, "--"); // Counting UTF-16 code units
+ assert_equals(node.data, "🌠 te--ST");
+ }, type + ".replaceData() with non-BMP data")
+}
+
+testNode(function() { return document.createTextNode("test") }, "Text")
+testNode(function() { return document.createComment("test") }, "Comment")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/CharacterData-substringData.html b/testing/web-platform/tests/dom/nodes/CharacterData-substringData.html
new file mode 100644
index 0000000000..b353526480
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/CharacterData-substringData.html
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CharacterData.substringData</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-substringdata">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-data">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function testNode(create, type) {
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_throws_js(TypeError, function() { node.substringData() })
+ assert_throws_js(TypeError, function() { node.substringData(0) })
+ }, type + ".substringData() with too few arguments")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_equals(node.substringData(0, 1, "test"), "t")
+ }, type + ".substringData() with too many arguments")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_throws_dom("IndexSizeError", function() { node.substringData(5, 0) })
+ assert_throws_dom("IndexSizeError", function() { node.substringData(6, 0) })
+ assert_throws_dom("IndexSizeError", function() { node.substringData(-1, 0) })
+ }, type + ".substringData() with invalid offset")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_equals(node.substringData(0, 1), "t")
+ assert_equals(node.substringData(1, 1), "e")
+ assert_equals(node.substringData(2, 1), "s")
+ assert_equals(node.substringData(3, 1), "t")
+ assert_equals(node.substringData(4, 1), "")
+ }, type + ".substringData() with in-bounds offset")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_equals(node.substringData(0, 0), "")
+ assert_equals(node.substringData(1, 0), "")
+ assert_equals(node.substringData(2, 0), "")
+ assert_equals(node.substringData(3, 0), "")
+ assert_equals(node.substringData(4, 0), "")
+ }, type + ".substringData() with zero count")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_equals(node.substringData(0x100000000 + 0, 1), "t")
+ assert_equals(node.substringData(0x100000000 + 1, 1), "e")
+ assert_equals(node.substringData(0x100000000 + 2, 1), "s")
+ assert_equals(node.substringData(0x100000000 + 3, 1), "t")
+ assert_equals(node.substringData(0x100000000 + 4, 1), "")
+ }, type + ".substringData() with very large offset")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_equals(node.substringData(-0x100000000 + 2, 1), "s")
+ }, type + ".substringData() with negative offset")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_equals(node.substringData("test", 3), "tes")
+ }, type + ".substringData() with string offset")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_equals(node.substringData(0, 1), "t")
+ assert_equals(node.substringData(0, 2), "te")
+ assert_equals(node.substringData(0, 3), "tes")
+ assert_equals(node.substringData(0, 4), "test")
+ }, type + ".substringData() with in-bounds count")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_equals(node.substringData(0, 5), "test")
+ assert_equals(node.substringData(2, 20), "st")
+ }, type + ".substringData() with large count")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_equals(node.substringData(2, 0x100000000 + 1), "s")
+ }, type + ".substringData() with very large count")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ assert_equals(node.substringData(0, -1), "test")
+ assert_equals(node.substringData(0, -0x100000000 + 2), "te")
+ }, type + ".substringData() with negative count")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "This is the character data test, other 資料,更多文字"
+
+ assert_equals(node.substringData(12, 4), "char")
+ assert_equals(node.substringData(39, 2), "資料")
+ }, type + ".substringData() with non-ASCII data")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "🌠 test 🌠 TEST"
+
+ assert_equals(node.substringData(5, 8), "st 🌠 TE") // Counting UTF-16 code units
+ }, type + ".substringData() with non-BMP data")
+}
+
+testNode(function() { return document.createTextNode("test") }, "Text")
+testNode(function() { return document.createComment("test") }, "Comment")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/CharacterData-surrogates.html b/testing/web-platform/tests/dom/nodes/CharacterData-surrogates.html
new file mode 100644
index 0000000000..ff1014c5fd
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/CharacterData-surrogates.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Splitting and joining surrogate pairs in CharacterData methods</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-substringdata">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-replacedata">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-insertdata">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-deletedata">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-data">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+function testNode(create, type) {
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "🌠 test 🌠 TEST"
+
+ assert_equals(node.substringData(1, 8), "\uDF20 test \uD83C")
+ }, type + ".substringData() splitting surrogate pairs")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "🌠 test 🌠 TEST"
+
+ node.replaceData(1, 4, "--");
+ assert_equals(node.data, "\uD83C--st 🌠 TEST");
+
+ node.replaceData(1, 2, "\uDF1F ");
+ assert_equals(node.data, "🌟 st 🌠 TEST");
+
+ node.replaceData(5, 2, "---");
+ assert_equals(node.data, "🌟 st---\uDF20 TEST");
+
+ node.replaceData(6, 2, " \uD83D");
+ assert_equals(node.data, "🌟 st- 🜠 TEST");
+ }, type + ".replaceData() splitting and creating surrogate pairs")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "🌠 test 🌠 TEST"
+
+ node.deleteData(1, 4);
+ assert_equals(node.data, "\uD83Cst 🌠 TEST");
+
+ node.deleteData(1, 4);
+ assert_equals(node.data, "🌠 TEST");
+ }, type + ".deleteData() splitting and creating surrogate pairs")
+
+ test(function() {
+ var node = create()
+ assert_equals(node.data, "test")
+
+ node.data = "🌠 test 🌠 TEST"
+
+ node.insertData(1, "--");
+ assert_equals(node.data, "\uD83C--\uDF20 test 🌠 TEST");
+
+ node.insertData(1, "\uDF1F ");
+ assert_equals(node.data, "🌟 --\uDF20 test 🌠 TEST");
+
+ node.insertData(5, " \uD83D");
+ assert_equals(node.data, "🌟 -- 🜠 test 🌠 TEST");
+ }, type + ".insertData() splitting and creating surrogate pairs")
+}
+
+testNode(function() { return document.createTextNode("test") }, "Text")
+testNode(function() { return document.createComment("test") }, "Comment")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/ChildNode-after.html b/testing/web-platform/tests/dom/nodes/ChildNode-after.html
new file mode 100644
index 0000000000..b5bf7ab5c2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ChildNode-after.html
@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>ChildNode.after</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-childnode-after">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+function test_after(child, nodeName, innerHTML) {
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.after();
+ assert_equals(parent.innerHTML, innerHTML);
+ }, nodeName + '.after() without any argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.after(null);
+ var expected = innerHTML + 'null';
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.after() with null as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.after(undefined);
+ var expected = innerHTML + 'undefined';
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.after() with undefined as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.after('');
+ assert_equals(parent.lastChild.data, '');
+ }, nodeName + '.after() with the empty string as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.after('text');
+ var expected = innerHTML + 'text';
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.after() with only text as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ parent.appendChild(child);
+ child.after(x);
+ var expected = innerHTML + '<x></x>';
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.after() with only one element as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ parent.appendChild(child);
+ child.after(x, 'text');
+ var expected = innerHTML + '<x></x>text';
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.after() with one element and text as arguments.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.after('text', child);
+ var expected = 'text' + innerHTML;
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.after() with context object itself as the argument.');
+
+ test(function() {
+ var parent = document.createElement('div')
+ var x = document.createElement('x');
+ parent.appendChild(x);
+ parent.appendChild(child);
+ child.after(child, x);
+ var expected = innerHTML + '<x></x>';
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.after() with context object itself and node as the arguments, switching positions.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ var z = document.createElement('z');
+ parent.appendChild(y);
+ parent.appendChild(child);
+ parent.appendChild(x);
+ child.after(x, y, z);
+ var expected = innerHTML + '<x></x><y></y><z></z>';
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.after() with all siblings of child as arguments.');
+
+ test(function() {
+ var parent = document.createElement('div')
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ var z = document.createElement('z');
+ parent.appendChild(child);
+ parent.appendChild(x);
+ parent.appendChild(y);
+ parent.appendChild(z);
+ child.after(x, y);
+ var expected = innerHTML + '<x></x><y></y><z></z>';
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.before() with some siblings of child as arguments; no changes in tree; viable sibling is first child.');
+
+ test(function() {
+ var parent = document.createElement('div')
+ var v = document.createElement('v');
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ var z = document.createElement('z');
+ parent.appendChild(child);
+ parent.appendChild(v);
+ parent.appendChild(x);
+ parent.appendChild(y);
+ parent.appendChild(z);
+ child.after(v, x);
+ var expected = innerHTML + '<v></v><x></x><y></y><z></z>';
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.after() with some siblings of child as arguments; no changes in tree.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ parent.appendChild(child);
+ parent.appendChild(x);
+ parent.appendChild(y);
+ child.after(y, x);
+ var expected = innerHTML + '<y></y><x></x>';
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.after() when pre-insert behaves like append.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ parent.appendChild(child);
+ parent.appendChild(x);
+ parent.appendChild(document.createTextNode('1'));
+ parent.appendChild(y);
+ child.after(x, '2');
+ var expected = innerHTML + '<x></x>21<y></y>';
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.after() with one sibling of child and text as arguments.');
+
+ test(function() {
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ x.after(y);
+ assert_equals(x.nextSibling, null);
+ }, nodeName + '.after() on a child without any parent.');
+}
+
+test_after(document.createComment('test'), 'Comment', '<!--test-->');
+test_after(document.createElement('test'), 'Element', '<test></test>');
+test_after(document.createTextNode('test'), 'Text', 'test');
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/ChildNode-before.html b/testing/web-platform/tests/dom/nodes/ChildNode-before.html
new file mode 100644
index 0000000000..8659424465
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ChildNode-before.html
@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>ChildNode.before</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-childnode-before">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+function test_before(child, nodeName, innerHTML) {
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.before();
+ assert_equals(parent.innerHTML, innerHTML);
+ }, nodeName + '.before() without any argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.before(null);
+ var expected = 'null' + innerHTML;
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.before() with null as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.before(undefined);
+ var expected = 'undefined' + innerHTML;
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.before() with undefined as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.before('');
+ assert_equals(parent.firstChild.data, '');
+ }, nodeName + '.before() with the empty string as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.before('text');
+ var expected = 'text' + innerHTML;
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.before() with only text as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ parent.appendChild(child);
+ child.before(x);
+ var expected = '<x></x>' + innerHTML;
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.before() with only one element as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ parent.appendChild(child);
+ child.before(x, 'text');
+ var expected = '<x></x>text' + innerHTML;
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.before() with one element and text as arguments.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.before('text', child);
+ var expected = 'text' + innerHTML;
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.before() with context object itself as the argument.');
+
+ test(function() {
+ var parent = document.createElement('div')
+ var x = document.createElement('x');
+ parent.appendChild(child);
+ parent.appendChild(x);
+ child.before(x, child);
+ var expected = '<x></x>' + innerHTML;
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.before() with context object itself and node as the arguments, switching positions.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ var z = document.createElement('z');
+ parent.appendChild(y);
+ parent.appendChild(child);
+ parent.appendChild(x);
+ child.before(x, y, z);
+ var expected = '<x></x><y></y><z></z>' + innerHTML;
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.before() with all siblings of child as arguments.');
+
+ test(function() {
+ var parent = document.createElement('div')
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ var z = document.createElement('z');
+ parent.appendChild(x);
+ parent.appendChild(y);
+ parent.appendChild(z);
+ parent.appendChild(child);
+ child.before(y, z);
+ var expected = '<x></x><y></y><z></z>' + innerHTML;
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.before() with some siblings of child as arguments; no changes in tree; viable sibling is first child.');
+
+ test(function() {
+ var parent = document.createElement('div')
+ var v = document.createElement('v');
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ var z = document.createElement('z');
+ parent.appendChild(v);
+ parent.appendChild(x);
+ parent.appendChild(y);
+ parent.appendChild(z);
+ parent.appendChild(child);
+ child.before(y, z);
+ var expected = '<v></v><x></x><y></y><z></z>' + innerHTML;
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.before() with some siblings of child as arguments; no changes in tree.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ parent.appendChild(x);
+ parent.appendChild(y);
+ parent.appendChild(child);
+ child.before(y, x);
+ var expected = '<y></y><x></x>' + innerHTML;
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.before() when pre-insert behaves like prepend.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ parent.appendChild(x);
+ parent.appendChild(document.createTextNode('1'));
+ var y = document.createElement('y');
+ parent.appendChild(y);
+ parent.appendChild(child);
+ child.before(x, '2');
+ var expected = '1<y></y><x></x>2' + innerHTML;
+ assert_equals(parent.innerHTML, expected);
+ }, nodeName + '.before() with one sibling of child and text as arguments.');
+
+ test(function() {
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ x.before(y);
+ assert_equals(x.previousSibling, null);
+ }, nodeName + '.before() on a child without any parent.');
+}
+
+test_before(document.createComment('test'), 'Comment', '<!--test-->');
+test_before(document.createElement('test'), 'Element', '<test></test>');
+test_before(document.createTextNode('test'), 'Text', 'test');
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/ChildNode-remove.js b/testing/web-platform/tests/dom/nodes/ChildNode-remove.js
new file mode 100644
index 0000000000..c36ba0d117
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ChildNode-remove.js
@@ -0,0 +1,30 @@
+function testRemove(node, parent, type) {
+ test(function() {
+ assert_true("remove" in node);
+ assert_equals(typeof node.remove, "function");
+ assert_equals(node.remove.length, 0);
+ }, type + " should support remove()");
+ test(function() {
+ assert_equals(node.parentNode, null, "Node should not have a parent");
+ assert_equals(node.remove(), undefined);
+ assert_equals(node.parentNode, null, "Removed new node should not have a parent");
+ }, "remove() should work if " + type + " doesn't have a parent");
+ test(function() {
+ assert_equals(node.parentNode, null, "Node should not have a parent");
+ parent.appendChild(node);
+ assert_equals(node.parentNode, parent, "Appended node should have a parent");
+ assert_equals(node.remove(), undefined);
+ assert_equals(node.parentNode, null, "Removed node should not have a parent");
+ assert_array_equals(parent.childNodes, [], "Parent should not have children");
+ }, "remove() should work if " + type + " does have a parent");
+ test(function() {
+ assert_equals(node.parentNode, null, "Node should not have a parent");
+ var before = parent.appendChild(document.createComment("before"));
+ parent.appendChild(node);
+ var after = parent.appendChild(document.createComment("after"));
+ assert_equals(node.parentNode, parent, "Appended node should have a parent");
+ assert_equals(node.remove(), undefined);
+ assert_equals(node.parentNode, null, "Removed node should not have a parent");
+ assert_array_equals(parent.childNodes, [before, after], "Parent should have two children left");
+ }, "remove() should work if " + type + " does have a parent and siblings");
+}
diff --git a/testing/web-platform/tests/dom/nodes/ChildNode-replaceWith.html b/testing/web-platform/tests/dom/nodes/ChildNode-replaceWith.html
new file mode 100644
index 0000000000..aab8b17f2a
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ChildNode-replaceWith.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>ChildNode.replaceWith</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-childnode-replaceWith">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+function test_replaceWith(child, nodeName, innerHTML) {
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.replaceWith();
+ assert_equals(parent.innerHTML, '');
+ }, nodeName + '.replaceWith() without any argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.replaceWith(null);
+ assert_equals(parent.innerHTML, 'null');
+ }, nodeName + '.replaceWith() with null as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.replaceWith(undefined);
+ assert_equals(parent.innerHTML, 'undefined');
+ }, nodeName + '.replaceWith() with undefined as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.replaceWith('');
+ assert_equals(parent.innerHTML, '');
+ }, nodeName + '.replaceWith() with empty string as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ parent.appendChild(child);
+ child.replaceWith('text');
+ assert_equals(parent.innerHTML, 'text');
+ }, nodeName + '.replaceWith() with only text as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ parent.appendChild(child);
+ child.replaceWith(x);
+ assert_equals(parent.innerHTML, '<x></x>');
+ }, nodeName + '.replaceWith() with only one element as an argument.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ var z = document.createElement('z');
+ parent.appendChild(y);
+ parent.appendChild(child);
+ parent.appendChild(x);
+ child.replaceWith(x, y, z);
+ assert_equals(parent.innerHTML, '<x></x><y></y><z></z>');
+ }, nodeName + '.replaceWith() with sibling of child as arguments.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ parent.appendChild(child);
+ parent.appendChild(x);
+ parent.appendChild(document.createTextNode('1'));
+ child.replaceWith(x, '2');
+ assert_equals(parent.innerHTML, '<x></x>21');
+ }, nodeName + '.replaceWith() with one sibling of child and text as arguments.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ parent.appendChild(child);
+ parent.appendChild(x);
+ parent.appendChild(document.createTextNode('text'));
+ child.replaceWith(x, child);
+ assert_equals(parent.innerHTML, '<x></x>' + innerHTML + 'text');
+ }, nodeName + '.replaceWith() with one sibling of child and child itself as arguments.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ parent.appendChild(child);
+ child.replaceWith(x, 'text');
+ assert_equals(parent.innerHTML, '<x></x>text');
+ }, nodeName + '.replaceWith() with one element and text as arguments.');
+
+ test(function() {
+ var parent = document.createElement('div');
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ parent.appendChild(x);
+ parent.appendChild(y);
+ child.replaceWith(x, y);
+ assert_equals(parent.innerHTML, '<x></x><y></y>');
+ }, nodeName + '.replaceWith() on a parentless child with two elements as arguments.');
+}
+
+test_replaceWith(document.createComment('test'), 'Comment', '<!--test-->');
+test_replaceWith(document.createElement('test'), 'Element', '<test></test>');
+test_replaceWith(document.createTextNode('test'), 'Text', 'test');
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Comment-Text-constructor.js b/testing/web-platform/tests/dom/nodes/Comment-Text-constructor.js
new file mode 100644
index 0000000000..24b4425f4b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Comment-Text-constructor.js
@@ -0,0 +1,77 @@
+function test_constructor(ctor) {
+ test(function() {
+ var object = new window[ctor]();
+ assert_equals(Object.getPrototypeOf(object),
+ window[ctor].prototype, "Prototype chain: " + ctor);
+ assert_equals(Object.getPrototypeOf(Object.getPrototypeOf(object)),
+ CharacterData.prototype, "Prototype chain: CharacterData");
+ assert_equals(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(object))),
+ Node.prototype, "Prototype chain: Node");
+ }, "new " + ctor + "(): prototype chain");
+
+ test(function() {
+ var object = new window[ctor]();
+ assert_true(object instanceof Node, "Should be a Node");
+ assert_true(object instanceof CharacterData, "Should be a CharacterData");
+ assert_true(object instanceof window[ctor], "Should be a " + ctor);
+ }, "new " + ctor + "(): instanceof");
+
+ test(function() {
+ var object = new window[ctor]();
+ assert_equals(object.data, "");
+ assert_equals(object.nodeValue, "");
+ assert_equals(object.ownerDocument, document);
+ }, "new " + ctor + "(): no arguments");
+
+ var testArgs = [
+ [undefined, ""],
+ [null, "null"],
+ [42, "42"],
+ ["", ""],
+ ["-", "-"],
+ ["--", "--"],
+ ["-->", "-->"],
+ ["<!--", "<!--"],
+ ["\u0000", "\u0000"],
+ ["\u0000test", "\u0000test"],
+ ["&amp;", "&amp;"],
+ ];
+
+ testArgs.forEach(function(a) {
+ var argument = a[0], expected = a[1];
+ test(function() {
+ var object = new window[ctor](argument);
+ assert_equals(object.data, expected);
+ assert_equals(object.nodeValue, expected);
+ assert_equals(object.ownerDocument, document);
+ }, "new " + ctor + "(): " + format_value(argument));
+ });
+
+ test(function() {
+ var called = [];
+ var object = new window[ctor]({
+ toString: function() {
+ called.push("first");
+ return "text";
+ }
+ }, {
+ toString: function() {
+ called.push("second");
+ assert_unreached("Should not look at the second argument.");
+ }
+ });
+ assert_equals(object.data, "text");
+ assert_equals(object.nodeValue, "text");
+ assert_equals(object.ownerDocument, document);
+ assert_array_equals(called, ["first"]);
+ }, "new " + ctor + "(): two arguments")
+
+ async_test("new " + ctor + "() should get the correct ownerDocument across globals").step(function() {
+ var iframe = document.createElement("iframe");
+ iframe.onload = this.step_func_done(function() {
+ var object = new iframe.contentWindow[ctor]();
+ assert_equals(object.ownerDocument, iframe.contentDocument);
+ });
+ document.body.appendChild(iframe);
+ });
+}
diff --git a/testing/web-platform/tests/dom/nodes/Comment-constructor.html b/testing/web-platform/tests/dom/nodes/Comment-constructor.html
new file mode 100644
index 0000000000..5091316bd3
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Comment-constructor.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Comment constructor</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-comment">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="Comment-Text-constructor.js"></script>
+<div id="log"></div>
+<script>
+test_constructor("Comment");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Comment-in-doctype.xhtml b/testing/web-platform/tests/dom/nodes/Comment-in-doctype.xhtml
new file mode 100644
index 0000000000..c0817d353d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Comment-in-doctype.xhtml
@@ -0,0 +1,15 @@
+<!DOCTYPE html [<!--x-->]><html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>XML: Comment in doctype internal subset</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ test(() => {
+ assert_equals(document.documentElement.previousSibling, document.firstChild);
+ assert_equals(document.firstChild.nodeType, Node.DOCUMENT_TYPE_NODE);
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/DOMImplementation-createDocument-with-null-browsing-context-crash.html b/testing/web-platform/tests/dom/nodes/DOMImplementation-createDocument-with-null-browsing-context-crash.html
new file mode 100644
index 0000000000..c9393d0a09
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DOMImplementation-createDocument-with-null-browsing-context-crash.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>DOMImplementation.createDocument()</title>
+<link rel="author" title="Nate Chapin" href="mailto:japhet@chromium.org">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-domimplementation-createdocument">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1086801">
+<meta name="assert" content="Calling on createDocument() on a DOMImplementation from a document with a null browsing context should not crash"/>
+<iframe id="i"></iframe>
+<script>
+var doc = i.contentDocument;
+i.remove();
+doc.implementation.createDocument("", "");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/DOMImplementation-createDocument.html b/testing/web-platform/tests/dom/nodes/DOMImplementation-createDocument.html
new file mode 100644
index 0000000000..835002b470
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DOMImplementation-createDocument.html
@@ -0,0 +1,170 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>DOMImplementation.createDocument(namespace, qualifiedName, doctype)</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-domimplementation-createdocument">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-createelementns">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-nodetype">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-documentelement">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-doctype">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="Document-createElementNS.js"></script>
+<div id="log"></div>
+<script>
+var htmlNamespace = "http://www.w3.org/1999/xhtml"
+var svgNamespace = "http://www.w3.org/2000/svg"
+var mathMLNamespace = "http://www.w3.org/1998/Math/MathML"
+
+// Make DocumentTypes distinct
+function my_format_value(val) {
+ if (val instanceof DocumentType) {
+ return "DocumentType node <!DOCTYPE " + val.name
+ + (val.publicId ? " " + val.publicId : "")
+ + (val.systemId ? " " + val.systemId : "")
+ + ">";
+ }
+ return format_value(val);
+}
+
+test(function() {
+ var tests = createElementNS_tests.map(function(t) {
+ return [t[0], t[1], null, t[2]]
+ }).concat([
+ /* Arrays with four elements:
+ * the namespace argument
+ * the qualifiedName argument
+ * the doctype argument
+ * the expected exception, or null if none
+ */
+ [null, null, false, TypeError],
+ [null, "", null, null],
+ [undefined, null, undefined, null],
+ [undefined, undefined, undefined, null],
+ [undefined, "", undefined, null],
+ ["http://example.com/", null, null, null],
+ ["http://example.com/", "", null, null],
+ ["/", null, null, null],
+ ["/", "", null, null],
+ ["http://www.w3.org/XML/1998/namespace", null, null, null],
+ ["http://www.w3.org/XML/1998/namespace", "", null, null],
+ ["http://www.w3.org/2000/xmlns/", null, null, null],
+ ["http://www.w3.org/2000/xmlns/", "", null, null],
+ ["foo:", null, null, null],
+ ["foo:", "", null, null],
+ [null, null, document.implementation.createDocumentType("foo", "", ""), null],
+ [null, null, document.doctype, null], // This causes a horrible WebKit bug (now fixed in trunk).
+ [null, null, function() {
+ var foo = document.implementation.createDocumentType("bar", "", "");
+ document.implementation.createDocument(null, null, foo);
+ return foo;
+ }(), null], // DOCTYPE already associated with a document.
+ [null, null, function() {
+ var bar = document.implementation.createDocument(null, null, null);
+ return bar.implementation.createDocumentType("baz", "", "");
+ }(), null], // DOCTYPE created by a different implementation.
+ [null, null, function() {
+ var bar = document.implementation.createDocument(null, null, null);
+ var magic = bar.implementation.createDocumentType("quz", "", "");
+ bar.implementation.createDocument(null, null, magic);
+ return magic;
+ }(), null], // DOCTYPE created by a different implementation and already associated with a document.
+ [null, "foo", document.implementation.createDocumentType("foo", "", ""), null],
+ ["foo", null, document.implementation.createDocumentType("foo", "", ""), null],
+ ["foo", "bar", document.implementation.createDocumentType("foo", "", ""), null],
+ [htmlNamespace, "", null, null],
+ [svgNamespace, "", null, null],
+ [mathMLNamespace, "", null, null],
+ [null, "html", null, null],
+ [null, "svg", null, null],
+ [null, "math", null, null],
+ [null, "", document.implementation.createDocumentType("html",
+ "-//W3C//DTD XHTML 1.0 Transitional//EN",
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd")],
+ [null, "", document.implementation.createDocumentType("svg",
+ "-//W3C//DTD SVG 1.1//EN",
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd")],
+ [null, "", document.implementation.createDocumentType("math",
+ "-//W3C//DTD MathML 2.0//EN",
+ "http://www.w3.org/Math/DTD/mathml2/mathml2.dtd")],
+ ])
+
+ tests.forEach(function(t, i) {
+ var namespace = t[0], qualifiedName = t[1], doctype = t[2], expected = t[3]
+ test(function() {
+ if (expected != null) {
+ if (typeof expected == "string") {
+ assert_throws_dom(expected, function() { document.implementation.createDocument(namespace, qualifiedName, doctype) });
+ } else {
+ assert_throws_js(expected, function() { document.implementation.createDocument(namespace, qualifiedName, doctype) });
+ }
+ } else {
+ var doc = document.implementation.createDocument(namespace, qualifiedName, doctype)
+ assert_equals(doc.nodeType, Node.DOCUMENT_NODE)
+ assert_equals(doc.nodeType, doc.DOCUMENT_NODE)
+ assert_equals(doc.nodeName, "#document")
+ assert_equals(doc.nodeValue, null)
+ assert_equals(Object.getPrototypeOf(doc), XMLDocument.prototype)
+ var omitRootElement = qualifiedName === null || String(qualifiedName) === ""
+ if (omitRootElement) {
+ assert_equals(doc.documentElement, null)
+ } else {
+ var element = doc.documentElement
+ assert_not_equals(element, null)
+ assert_equals(element.nodeType, Node.ELEMENT_NODE)
+ assert_equals(element.ownerDocument, doc)
+ var qualified = String(qualifiedName), names = []
+ if (qualified.indexOf(":") >= 0) {
+ names = qualified.split(":", 2)
+ } else {
+ names = [null, qualified]
+ }
+ assert_equals(element.prefix, names[0])
+ assert_equals(element.localName, names[1])
+ assert_equals(element.namespaceURI, namespace === undefined ? null : namespace)
+ }
+ if (!doctype) {
+ assert_equals(doc.doctype, null)
+ } else {
+ assert_equals(doc.doctype, doctype)
+ assert_equals(doc.doctype.ownerDocument, doc)
+ }
+ assert_equals(doc.childNodes.length, !omitRootElement + !!doctype)
+ }
+ }, "createDocument test: " + t.map(my_format_value))
+
+ if (expected === null) {
+ test(function() {
+ var doc = document.implementation.createDocument(namespace, qualifiedName, doctype)
+ assert_equals(doc.location, null)
+ assert_equals(doc.compatMode, "CSS1Compat")
+ assert_equals(doc.characterSet, "UTF-8")
+ assert_equals(doc.contentType, namespace == htmlNamespace ? "application/xhtml+xml"
+ : namespace == svgNamespace ? "image/svg+xml"
+ : "application/xml")
+ assert_equals(doc.URL, "about:blank")
+ assert_equals(doc.documentURI, "about:blank")
+ assert_equals(doc.createElement("DIV").localName, "DIV");
+ }, "createDocument test: metadata for " +
+ [namespace, qualifiedName, doctype].map(my_format_value))
+
+ test(function() {
+ var doc = document.implementation.createDocument(namespace, qualifiedName, doctype)
+ assert_equals(doc.characterSet, "UTF-8", "characterSet");
+ assert_equals(doc.charset, "UTF-8", "charset");
+ assert_equals(doc.inputEncoding, "UTF-8", "inputEncoding");
+ }, "createDocument test: characterSet aliases for " +
+ [namespace, qualifiedName, doctype].map(my_format_value))
+ }
+ })
+})
+
+test(function() {
+ assert_throws_js(TypeError, function() {
+ document.implementation.createDocument()
+ }, "createDocument() should throw")
+
+ assert_throws_js(TypeError, function() {
+ document.implementation.createDocument('')
+ }, "createDocument('') should throw")
+}, "createDocument with missing arguments");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/DOMImplementation-createDocumentType.html b/testing/web-platform/tests/dom/nodes/DOMImplementation-createDocumentType.html
new file mode 100644
index 0000000000..8d23e66a2b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DOMImplementation-createDocumentType.html
@@ -0,0 +1,123 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>DOMImplementation.createDocumentType(qualifiedName, publicId, systemId)</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-documenttype-name">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-documenttype-publicid">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-documenttype-systemid">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-ownerdocument">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var tests = [
+ ["", "", "", "INVALID_CHARACTER_ERR"],
+ ["test:root", "1234", "", null],
+ ["test:root", "1234", "test", null],
+ ["test:root", "test", "", null],
+ ["test:root", "test", "test", null],
+ ["_:_", "", "", null],
+ ["_:h0", "", "", null],
+ ["_:test", "", "", null],
+ ["_:_.", "", "", null],
+ ["_:a-", "", "", null],
+ ["l_:_", "", "", null],
+ ["ns:_0", "", "", null],
+ ["ns:a0", "", "", null],
+ ["ns0:test", "", "", null],
+ ["ns:EEE.", "", "", null],
+ ["ns:_-", "", "", null],
+ ["a.b:c", "", "", null],
+ ["a-b:c.j", "", "", null],
+ ["a-b:c", "", "", null],
+ ["foo", "", "", null],
+ ["1foo", "", "", "INVALID_CHARACTER_ERR"],
+ ["foo1", "", "", null],
+ ["f1oo", "", "", null],
+ ["@foo", "", "", "INVALID_CHARACTER_ERR"],
+ ["foo@", "", "", "INVALID_CHARACTER_ERR"],
+ ["f@oo", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:{", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:}", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:~", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:'", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:!", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:@", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:#", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:$", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:%", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:^", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:&", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:*", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:(", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:)", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:+", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:=", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:[", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:]", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:\\", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:/", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:;", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:`", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:<", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:>", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:,", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:a ", "", "", "INVALID_CHARACTER_ERR"],
+ ["edi:\"", "", "", "INVALID_CHARACTER_ERR"],
+ ["{", "", "", "INVALID_CHARACTER_ERR"],
+ ["}", "", "", "INVALID_CHARACTER_ERR"],
+ ["'", "", "", "INVALID_CHARACTER_ERR"],
+ ["~", "", "", "INVALID_CHARACTER_ERR"],
+ ["`", "", "", "INVALID_CHARACTER_ERR"],
+ ["@", "", "", "INVALID_CHARACTER_ERR"],
+ ["#", "", "", "INVALID_CHARACTER_ERR"],
+ ["$", "", "", "INVALID_CHARACTER_ERR"],
+ ["%", "", "", "INVALID_CHARACTER_ERR"],
+ ["^", "", "", "INVALID_CHARACTER_ERR"],
+ ["&", "", "", "INVALID_CHARACTER_ERR"],
+ ["*", "", "", "INVALID_CHARACTER_ERR"],
+ ["(", "", "", "INVALID_CHARACTER_ERR"],
+ [")", "", "", "INVALID_CHARACTER_ERR"],
+ ["f:oo", "", "", null],
+ [":foo", "", "", "INVALID_CHARACTER_ERR"],
+ ["foo:", "", "", "INVALID_CHARACTER_ERR"],
+ ["prefix::local", "", "", "INVALID_CHARACTER_ERR"],
+ ["foo", "foo", "", null],
+ ["foo", "", "foo", null],
+ ["foo", "f'oo", "", null],
+ ["foo", "", "f'oo", null],
+ ["foo", 'f"oo', "", null],
+ ["foo", "", 'f"oo', null],
+ ["foo", "f'o\"o", "", null],
+ ["foo", "", "f'o\"o", null],
+ ["foo", "foo>", "", null],
+ ["foo", "", "foo>", null]
+ ]
+
+ var doc = document.implementation.createHTMLDocument("title");
+ var doTest = function(aDocument, aQualifiedName, aPublicId, aSystemId) {
+ var doctype = aDocument.implementation.createDocumentType(aQualifiedName, aPublicId, aSystemId);
+ assert_equals(doctype.name, aQualifiedName, "name")
+ assert_equals(doctype.nodeName, aQualifiedName, "nodeName")
+ assert_equals(doctype.publicId, aPublicId, "publicId")
+ assert_equals(doctype.systemId, aSystemId, "systemId")
+ assert_equals(doctype.ownerDocument, aDocument, "ownerDocument")
+ assert_equals(doctype.nodeValue, null, "nodeValue")
+ }
+ tests.forEach(function(t) {
+ var qualifiedName = t[0], publicId = t[1], systemId = t[2], expected = t[3]
+ test(function() {
+ if (expected) {
+ assert_throws_dom(expected, function() {
+ document.implementation.createDocumentType(qualifiedName, publicId, systemId)
+ })
+ } else {
+ doTest(document, qualifiedName, publicId, systemId);
+ doTest(doc, qualifiedName, publicId, systemId);
+ }
+ }, "createDocumentType(" + format_value(qualifiedName) + ", " + format_value(publicId) + ", " + format_value(systemId) + ") should " +
+ (expected ? "throw " + expected : "work"));
+ });
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument-with-null-browsing-context-crash.html b/testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument-with-null-browsing-context-crash.html
new file mode 100644
index 0000000000..d0cd6f1f74
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument-with-null-browsing-context-crash.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>DOMImplementation.createHTMLDocument()</title>
+<link rel="author" title="Nate Chapin" href="mailto:japhet@chromium.org">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1086800">
+<meta name="assert" content="Calling on createHTMLDocument() on a DOMImplementation from a document with a null browsing context should not crash"/>
+<iframe id="i"></iframe>
+<script>
+var doc = i.contentDocument;
+i.remove();
+doc.implementation.createHTMLDocument();
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument-with-saved-implementation.html b/testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument-with-saved-implementation.html
new file mode 100644
index 0000000000..bae22660bf
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument-with-saved-implementation.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>DOMImplementation.createHTMLDocument</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+// Test the document location getter is null outside of browser context
+test(function() {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ var implementation = iframe.contentDocument.implementation;
+ iframe.remove();
+ assert_not_equals(implementation.createHTMLDocument(), null);
+}, "createHTMLDocument(): from a saved and detached implementation does not return null")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument.html b/testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument.html
new file mode 100644
index 0000000000..c6e0beaf75
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset=windows-1252>
+<!-- Using windows-1252 to ensure that DOMImplementation.createHTMLDocument()
+ doesn't inherit utf-8 from the parent document. -->
+<title>DOMImplementation.createHTMLDocument</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-documenttype-name">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-documenttype-publicid">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-documenttype-systemid">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-documentelement">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="DOMImplementation-createHTMLDocument.js"></script>
+<div id="log"></div>
+<script>
+createHTMLDocuments(function(doc, expectedtitle, normalizedtitle) {
+ assert_true(doc instanceof Document, "Should be a Document")
+ assert_true(doc instanceof Node, "Should be a Node")
+ assert_equals(doc.childNodes.length, 2,
+ "Document should have two child nodes")
+
+ var doctype = doc.doctype
+ assert_true(doctype instanceof DocumentType,
+ "Doctype should be a DocumentType")
+ assert_true(doctype instanceof Node, "Doctype should be a Node")
+ assert_equals(doctype.name, "html")
+ assert_equals(doctype.publicId, "")
+ assert_equals(doctype.systemId, "")
+
+ var documentElement = doc.documentElement
+ assert_true(documentElement instanceof HTMLHtmlElement,
+ "Document element should be a HTMLHtmlElement")
+ assert_equals(documentElement.childNodes.length, 2,
+ "Document element should have two child nodes")
+ assert_equals(documentElement.localName, "html")
+ assert_equals(documentElement.tagName, "HTML")
+
+ var head = documentElement.firstChild
+ assert_true(head instanceof HTMLHeadElement,
+ "Head should be a HTMLHeadElement")
+ assert_equals(head.localName, "head")
+ assert_equals(head.tagName, "HEAD")
+
+ if (expectedtitle !== undefined) {
+ assert_equals(head.childNodes.length, 1)
+
+ var title = head.firstChild
+ assert_true(title instanceof HTMLTitleElement,
+ "Title should be a HTMLTitleElement")
+ assert_equals(title.localName, "title")
+ assert_equals(title.tagName, "TITLE")
+ assert_equals(title.childNodes.length, 1)
+ assert_equals(title.firstChild.data, expectedtitle)
+ } else {
+ assert_equals(head.childNodes.length, 0)
+ }
+
+ var body = documentElement.lastChild
+ assert_true(body instanceof HTMLBodyElement,
+ "Body should be a HTMLBodyElement")
+ assert_equals(body.localName, "body")
+ assert_equals(body.tagName, "BODY")
+ assert_equals(body.childNodes.length, 0)
+})
+
+test(function() {
+ var doc = document.implementation.createHTMLDocument("test");
+ assert_equals(doc.URL, "about:blank");
+ assert_equals(doc.documentURI, "about:blank");
+ assert_equals(doc.compatMode, "CSS1Compat");
+ assert_equals(doc.characterSet, "UTF-8");
+ assert_equals(doc.contentType, "text/html");
+ assert_equals(doc.createElement("DIV").localName, "div");
+}, "createHTMLDocument(): metadata")
+
+test(function() {
+ var doc = document.implementation.createHTMLDocument("test");
+ assert_equals(doc.characterSet, "UTF-8", "characterSet");
+ assert_equals(doc.charset, "UTF-8", "charset");
+ assert_equals(doc.inputEncoding, "UTF-8", "inputEncoding");
+}, "createHTMLDocument(): characterSet aliases")
+
+test(function() {
+ var doc = document.implementation.createHTMLDocument("test");
+ var a = doc.createElement("a");
+ // In UTF-8: 0xC3 0xA4
+ a.href = "http://example.org/?\u00E4";
+ assert_equals(a.href, "http://example.org/?%C3%A4");
+}, "createHTMLDocument(): URL parsing")
+
+// Test the document location getter is null outside of browser context
+test(function() {
+ var doc = document.implementation.createHTMLDocument();
+ assert_equals(doc.location, null);
+}, "createHTMLDocument(): document location getter is null")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument.js b/testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument.js
new file mode 100644
index 0000000000..3e7e9aa9b7
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DOMImplementation-createHTMLDocument.js
@@ -0,0 +1,25 @@
+function createHTMLDocuments(checkDoc) {
+ var tests = [
+ ["", "", ""],
+ [null, "null", "null"],
+ [undefined, undefined, ""],
+ ["foo bar baz", "foo bar baz", "foo bar baz"],
+ ["foo\t\tbar baz", "foo\t\tbar baz", "foo bar baz"],
+ ["foo\n\nbar baz", "foo\n\nbar baz", "foo bar baz"],
+ ["foo\f\fbar baz", "foo\f\fbar baz", "foo bar baz"],
+ ["foo\r\rbar baz", "foo\r\rbar baz", "foo bar baz"],
+ ]
+
+ tests.forEach(function(t, i) {
+ var title = t[0], expectedtitle = t[1], normalizedtitle = t[2]
+ test(function() {
+ var doc = document.implementation.createHTMLDocument(title);
+ checkDoc(doc, expectedtitle, normalizedtitle)
+ }, "createHTMLDocument test " + i + ": " + t.map(function(el) { return format_value(el) }))
+ })
+
+ test(function() {
+ var doc = document.implementation.createHTMLDocument();
+ checkDoc(doc, undefined, "")
+ }, "Missing title argument");
+}
diff --git a/testing/web-platform/tests/dom/nodes/DOMImplementation-hasFeature.html b/testing/web-platform/tests/dom/nodes/DOMImplementation-hasFeature.html
new file mode 100644
index 0000000000..637565a60f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DOMImplementation-hasFeature.html
@@ -0,0 +1,155 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>DOMImplementation.hasFeature(feature, version)</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var tests = [
+ [],
+ ["Core"],
+ ["XML"],
+ ["org.w3c.svg"],
+ ["org.w3c.dom.svg"],
+ ["http://www.w3.org/TR/SVG11/feature#Script"],
+ ["Core", "1.0"],
+ ["Core", "2.0"],
+ ["Core", "3.0"],
+ ["Core", "100.0"],
+ ["XML", "1.0"],
+ ["XML", "2.0"],
+ ["XML", "3.0"],
+ ["XML", "100.0"],
+ ["Core", "1"],
+ ["Core", "2"],
+ ["Core", "3"],
+ ["Core", "100"],
+ ["XML", "1"],
+ ["XML", "2"],
+ ["XML", "3"],
+ ["XML", "100"],
+ ["Core", "1.1"],
+ ["Core", "2.1"],
+ ["Core", "3.1"],
+ ["Core", "100.1"],
+ ["XML", "1.1"],
+ ["XML", "2.1"],
+ ["XML", "3.1"],
+ ["XML", "100.1"],
+ ["Core", ""],
+ ["XML", ""],
+ ["core", ""],
+ ["xml", ""],
+ ["CoRe", ""],
+ ["XmL", ""],
+ [" Core", ""],
+ [" XML", ""],
+ ["Core ", ""],
+ ["XML ", ""],
+ ["Co re", ""],
+ ["XM L", ""],
+ ["aCore", ""],
+ ["aXML", ""],
+ ["Corea", ""],
+ ["XMLa", ""],
+ ["Coare", ""],
+ ["XMaL", ""],
+ ["Core", " "],
+ ["XML", " "],
+ ["Core", " 1.0"],
+ ["Core", " 2.0"],
+ ["Core", " 3.0"],
+ ["Core", " 100.0"],
+ ["XML", " 1.0"],
+ ["XML", " 2.0"],
+ ["XML", " 3.0"],
+ ["XML", " 100.0"],
+ ["Core", "1.0 "],
+ ["Core", "2.0 "],
+ ["Core", "3.0 "],
+ ["Core", "100.0 "],
+ ["XML", "1.0 "],
+ ["XML", "2.0 "],
+ ["XML", "3.0 "],
+ ["XML", "100.0 "],
+ ["Core", "1. 0"],
+ ["Core", "2. 0"],
+ ["Core", "3. 0"],
+ ["Core", "100. 0"],
+ ["XML", "1. 0"],
+ ["XML", "2. 0"],
+ ["XML", "3. 0"],
+ ["XML", "100. 0"],
+ ["Core", "a1.0"],
+ ["Core", "a2.0"],
+ ["Core", "a3.0"],
+ ["Core", "a100.0"],
+ ["XML", "a1.0"],
+ ["XML", "a2.0"],
+ ["XML", "a3.0"],
+ ["XML", "a100.0"],
+ ["Core", "1.0a"],
+ ["Core", "2.0a"],
+ ["Core", "3.0a"],
+ ["Core", "100.0a"],
+ ["XML", "1.0a"],
+ ["XML", "2.0a"],
+ ["XML", "3.0a"],
+ ["XML", "100.0a"],
+ ["Core", "1.a0"],
+ ["Core", "2.a0"],
+ ["Core", "3.a0"],
+ ["Core", "100.a0"],
+ ["XML", "1.a0"],
+ ["XML", "2.a0"],
+ ["XML", "3.a0"],
+ ["XML", "100.a0"],
+ ["Core", 1],
+ ["Core", 2],
+ ["Core", 3],
+ ["Core", 100],
+ ["XML", 1],
+ ["XML", 2],
+ ["XML", 3],
+ ["XML", 100],
+ ["Core", null],
+ ["XML", null],
+ ["core", null],
+ ["xml", null],
+ ["CoRe", null],
+ ["XmL", null],
+ [" Core", null],
+ [" XML", null],
+ ["Core ", null],
+ ["XML ", null],
+ ["Co re", null],
+ ["XM L", null],
+ ["aCore", null],
+ ["aXML", null],
+ ["Corea", null],
+ ["XMLa", null],
+ ["Coare", null],
+ ["XMaL", null],
+ ["Core", undefined],
+ ["XML", undefined],
+ ["This is filler text.", ""],
+ [null, ""],
+ [undefined, ""],
+ ["org.w3c.svg", ""],
+ ["org.w3c.svg", "1.0"],
+ ["org.w3c.svg", "1.1"],
+ ["org.w3c.dom.svg", ""],
+ ["org.w3c.dom.svg", "1.0"],
+ ["org.w3c.dom.svg", "1.1"],
+ ["http://www.w3.org/TR/SVG11/feature#Script", "7.5"],
+ ];
+ tests.forEach(function(data) {
+ test(function() {
+ assert_equals(document.implementation.hasFeature
+ .apply(document.implementation, data), true)
+ }, "hasFeature(" + data.map(format_value).join(", ") + ")")
+ })
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-Element-getElementsByTagName.js b/testing/web-platform/tests/dom/nodes/Document-Element-getElementsByTagName.js
new file mode 100644
index 0000000000..dbbe667ff5
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-Element-getElementsByTagName.js
@@ -0,0 +1,208 @@
+function test_getElementsByTagName(context, element) {
+ // TODO: getElementsByTagName("*")
+ test(function() {
+ assert_false(context.getElementsByTagName("html") instanceof NodeList,
+ "Should not return a NodeList")
+ assert_true(context.getElementsByTagName("html") instanceof HTMLCollection,
+ "Should return an HTMLCollection")
+ }, "Interfaces")
+
+ test(function() {
+ var firstCollection = context.getElementsByTagName("html"),
+ secondCollection = context.getElementsByTagName("html")
+ assert_true(firstCollection !== secondCollection ||
+ firstCollection === secondCollection)
+ }, "Caching is allowed")
+
+ test(function() {
+ var l = context.getElementsByTagName("nosuchtag")
+ l[5] = "foopy"
+ assert_equals(l[5], undefined)
+ assert_equals(l.item(5), null)
+ }, "Shouldn't be able to set unsigned properties on a HTMLCollection (non-strict mode)")
+
+ test(function() {
+ var l = context.getElementsByTagName("nosuchtag")
+ assert_throws_js(TypeError, function() {
+ "use strict";
+ l[5] = "foopy"
+ })
+ assert_equals(l[5], undefined)
+ assert_equals(l.item(5), null)
+ }, "Shouldn't be able to set unsigned properties on a HTMLCollection (strict mode)")
+
+ test(function() {
+ var l = context.getElementsByTagName("nosuchtag")
+ var fn = l.item;
+ assert_equals(fn, HTMLCollection.prototype.item);
+ l.item = "pass"
+ assert_equals(l.item, "pass")
+ assert_equals(HTMLCollection.prototype.item, fn);
+ }, "Should be able to set expando shadowing a proto prop (item)")
+
+ test(function() {
+ var l = context.getElementsByTagName("nosuchtag")
+ var fn = l.namedItem;
+ assert_equals(fn, HTMLCollection.prototype.namedItem);
+ l.namedItem = "pass"
+ assert_equals(l.namedItem, "pass")
+ assert_equals(HTMLCollection.prototype.namedItem, fn);
+ }, "Should be able to set expando shadowing a proto prop (namedItem)")
+
+ test(function() {
+ var t1 = element.appendChild(document.createElement("pre"));
+ t1.id = "x";
+ var t2 = element.appendChild(document.createElement("pre"));
+ t2.setAttribute("name", "y");
+ var t3 = element.appendChild(document.createElementNS("", "pre"));
+ t3.setAttribute("id", "z");
+ var t4 = element.appendChild(document.createElementNS("", "pre"));
+ t4.setAttribute("name", "w");
+ this.add_cleanup(function() {
+ element.removeChild(t1)
+ element.removeChild(t2)
+ element.removeChild(t3)
+ element.removeChild(t4)
+ });
+
+ var list = context.getElementsByTagName('pre');
+ var pre = list[0];
+ assert_equals(pre.id, "x");
+
+ var exposedNames = { 'x': 0, 'y': 1, 'z': 2 };
+ for (var exposedName in exposedNames) {
+ assert_equals(list[exposedName], list[exposedNames[exposedName]]);
+ assert_equals(list[exposedName], list.namedItem(exposedName));
+ assert_true(exposedName in list, "'" + exposedName + "' in list");
+ assert_true(list.hasOwnProperty(exposedName),
+ "list.hasOwnProperty('" + exposedName + "')");
+ }
+
+ var unexposedNames = ["w"];
+ for (var unexposedName of unexposedNames) {
+ assert_false(unexposedName in list);
+ assert_false(list.hasOwnProperty(unexposedName));
+ assert_equals(list[unexposedName], undefined);
+ assert_equals(list.namedItem(unexposedName), null);
+ }
+
+ assert_array_equals(Object.getOwnPropertyNames(list).sort(),
+ ["0", "1", "2", "3", "x", "y", "z"]);
+
+ var desc = Object.getOwnPropertyDescriptor(list, '0');
+ assert_equals(typeof desc, "object", "descriptor should be an object");
+ assert_true(desc.enumerable, "desc.enumerable");
+ assert_true(desc.configurable, "desc.configurable");
+
+ desc = Object.getOwnPropertyDescriptor(list, 'x');
+ assert_equals(typeof desc, "object", "descriptor should be an object");
+ assert_false(desc.enumerable, "desc.enumerable");
+ assert_true(desc.configurable, "desc.configurable");
+ }, "hasOwnProperty, getOwnPropertyDescriptor, getOwnPropertyNames")
+
+ test(function() {
+ assert_equals(document.createElementNS("http://www.w3.org/1999/xhtml", "i").localName, "i") // Sanity
+ var t = element.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "I"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_equals(t.localName, "I")
+ assert_equals(t.tagName, "I")
+ assert_equals(context.getElementsByTagName("I").length, 0)
+ assert_equals(context.getElementsByTagName("i").length, 0)
+ }, "HTML element with uppercase tagName never matches in HTML Documents")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("test", "st"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagName("st"), [t])
+ assert_array_equals(context.getElementsByTagName("ST"), [])
+ }, "Element in non-HTML namespace, no prefix, lowercase name")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("test", "ST"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagName("ST"), [t])
+ assert_array_equals(context.getElementsByTagName("st"), [])
+ }, "Element in non-HTML namespace, no prefix, uppercase name")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("test", "te:st"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagName("st"), [])
+ assert_array_equals(context.getElementsByTagName("ST"), [])
+ assert_array_equals(context.getElementsByTagName("te:st"), [t])
+ assert_array_equals(context.getElementsByTagName("te:ST"), [])
+ }, "Element in non-HTML namespace, prefix, lowercase name")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("test", "te:ST"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagName("st"), [])
+ assert_array_equals(context.getElementsByTagName("ST"), [])
+ assert_array_equals(context.getElementsByTagName("te:st"), [])
+ assert_array_equals(context.getElementsByTagName("te:ST"), [t])
+ }, "Element in non-HTML namespace, prefix, uppercase name")
+
+ test(function() {
+ var t = element.appendChild(document.createElement("aÇ"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_equals(t.localName, "aÇ")
+ assert_array_equals(context.getElementsByTagName("AÇ"), [t], "All uppercase input")
+ assert_array_equals(context.getElementsByTagName("aÇ"), [t], "Ascii lowercase input")
+ assert_array_equals(context.getElementsByTagName("aç"), [], "All lowercase input")
+ }, "Element in HTML namespace, no prefix, non-ascii characters in name")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("test", "AÇ"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagName("AÇ"), [t])
+ assert_array_equals(context.getElementsByTagName("aÇ"), [])
+ assert_array_equals(context.getElementsByTagName("aç"), [])
+ }, "Element in non-HTML namespace, non-ascii characters in name")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "test:aÇ"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagName("TEST:AÇ"), [t], "All uppercase input")
+ assert_array_equals(context.getElementsByTagName("test:aÇ"), [t], "Ascii lowercase input")
+ assert_array_equals(context.getElementsByTagName("test:aç"), [], "All lowercase input")
+ }, "Element in HTML namespace, prefix, non-ascii characters in name")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("test", "TEST:AÇ"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagName("TEST:AÇ"), [t], "All uppercase input")
+ assert_array_equals(context.getElementsByTagName("test:aÇ"), [], "Ascii lowercase input")
+ assert_array_equals(context.getElementsByTagName("test:aç"), [], "All lowercase input")
+ }, "Element in non-HTML namespace, prefix, non-ascii characters in name")
+
+ test(function() {
+ var actual = context.getElementsByTagName("*");
+ var expected = [];
+ var get_elements = function(node) {
+ for (var i = 0; i < node.childNodes.length; i++) {
+ var child = node.childNodes[i];
+ if (child.nodeType === child.ELEMENT_NODE) {
+ expected.push(child);
+ get_elements(child);
+ }
+ }
+ }
+ get_elements(context);
+ assert_array_equals(actual, expected);
+ }, "getElementsByTagName('*')")
+
+ test(function() {
+ var t1 = element.appendChild(document.createElement("abc"));
+ this.add_cleanup(function() {element.removeChild(t1)});
+
+ var l = context.getElementsByTagName("abc");
+ assert_true(l instanceof HTMLCollection);
+ assert_equals(l.length, 1);
+
+ var t2 = element.appendChild(document.createElement("abc"));
+ assert_equals(l.length, 2);
+
+ element.removeChild(t2);
+ assert_equals(l.length, 1);
+ }, "getElementsByTagName() should be a live collection");
+}
diff --git a/testing/web-platform/tests/dom/nodes/Document-Element-getElementsByTagNameNS.js b/testing/web-platform/tests/dom/nodes/Document-Element-getElementsByTagNameNS.js
new file mode 100644
index 0000000000..b610c49d86
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-Element-getElementsByTagNameNS.js
@@ -0,0 +1,143 @@
+function test_getElementsByTagNameNS(context, element) {
+ test(function() {
+ assert_false(context.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "html") instanceof NodeList, "NodeList")
+ assert_true(context.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "html") instanceof HTMLCollection, "HTMLCollection")
+ var firstCollection = context.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "html"),
+ secondCollection = context.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "html")
+ assert_true(firstCollection !== secondCollection || firstCollection === secondCollection,
+ "Caching is allowed.")
+ })
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("test", "body"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ var actual = context.getElementsByTagNameNS("*", "body");
+ var expected = [];
+ var get_elements = function(node) {
+ for (var i = 0; i < node.childNodes.length; i++) {
+ var child = node.childNodes[i];
+ if (child.nodeType === child.ELEMENT_NODE) {
+ if (child.localName == "body") {
+ expected.push(child);
+ }
+ get_elements(child);
+ }
+ }
+ }
+ get_elements(context);
+ assert_array_equals(actual, expected);
+ }, "getElementsByTagNameNS('*', 'body')")
+
+ test(function() {
+ assert_array_equals(context.getElementsByTagNameNS("", "*"), []);
+ var t = element.appendChild(document.createElementNS("", "body"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagNameNS("", "*"), [t]);
+ }, "Empty string namespace")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("test", "body"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagNameNS("test", "body"), [t]);
+ }, "body element in test namespace, no prefix")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("test", "test:body"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagNameNS("test", "body"), [t]);
+ }, "body element in test namespace, prefix")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("test", "BODY"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagNameNS("test", "BODY"), [t]);
+ assert_array_equals(context.getElementsByTagNameNS("test", "body"), []);
+ }, "BODY element in test namespace, no prefix")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "abc"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "abc"), [t]);
+ assert_array_equals(context.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "ABC"), []);
+ assert_array_equals(context.getElementsByTagNameNS("test", "ABC"), []);
+ }, "abc element in html namespace")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "ABC"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "abc"), []);
+ assert_array_equals(context.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "ABC"), [t]);
+ }, "ABC element in html namespace")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "AÇ"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "AÇ"), [t]);
+ assert_array_equals(context.getElementsByTagNameNS("test", "aÇ"), []);
+ assert_array_equals(context.getElementsByTagNameNS("test", "aç"), []);
+ }, "AÇ, case sensitivity")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("test", "test:BODY"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ assert_array_equals(context.getElementsByTagNameNS("test", "BODY"), [t]);
+ assert_array_equals(context.getElementsByTagNameNS("test", "body"), []);
+ }, "BODY element in test namespace, prefix")
+
+ test(function() {
+ var t = element.appendChild(document.createElementNS("test", "test:test"))
+ this.add_cleanup(function() {element.removeChild(t)})
+ var actual = context.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "*");
+ var expected = [];
+ var get_elements = function(node) {
+ for (var i = 0; i < node.childNodes.length; i++) {
+ var child = node.childNodes[i];
+ if (child.nodeType === child.ELEMENT_NODE) {
+ if (child !== t) {
+ expected.push(child);
+ }
+ get_elements(child);
+ }
+ }
+ }
+ get_elements(context);
+ assert_array_equals(actual, expected);
+ }, "getElementsByTagNameNS('http://www.w3.org/1999/xhtml', '*')")
+
+ test(function() {
+ var actual = context.getElementsByTagNameNS("*", "*");
+ var expected = [];
+ var get_elements = function(node) {
+ for (var i = 0; i < node.childNodes.length; i++) {
+ var child = node.childNodes[i];
+ if (child.nodeType === child.ELEMENT_NODE) {
+ expected.push(child);
+ get_elements(child);
+ }
+ }
+ }
+ get_elements(context);
+ assert_array_equals(actual, expected);
+ }, "getElementsByTagNameNS('*', '*')")
+
+ test(function() {
+ assert_array_equals(context.getElementsByTagNameNS("**", "*"), []);
+ assert_array_equals(context.getElementsByTagNameNS(null, "0"), []);
+ assert_array_equals(context.getElementsByTagNameNS(null, "div"), []);
+ }, "Empty lists")
+
+ test(function() {
+ var t1 = element.appendChild(document.createElementNS("test", "abc"));
+ this.add_cleanup(function() {element.removeChild(t1)});
+
+ var l = context.getElementsByTagNameNS("test", "abc");
+ assert_true(l instanceof HTMLCollection);
+ assert_equals(l.length, 1);
+
+ var t2 = element.appendChild(document.createElementNS("test", "abc"));
+ assert_equals(l.length, 2);
+
+ element.removeChild(t2);
+ assert_equals(l.length, 1);
+ }, "getElementsByTagNameNS() should be a live collection");
+}
diff --git a/testing/web-platform/tests/dom/nodes/Document-URL.html b/testing/web-platform/tests/dom/nodes/Document-URL.html
new file mode 100644
index 0000000000..242def1fb3
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-URL.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Document.URL with redirect</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.src = "/common/redirect.py?location=/common/blank.html";
+ document.body.appendChild(iframe);
+ this.add_cleanup(function() { document.body.removeChild(iframe); });
+ iframe.onload = this.step_func_done(function() {
+ assert_equals(iframe.contentDocument.URL,
+ location.origin + "/common/blank.html");
+ });
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-adoptNode.html b/testing/web-platform/tests/dom/nodes/Document-adoptNode.html
new file mode 100644
index 0000000000..60a4e6772a
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-adoptNode.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Document.adoptNode</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-adoptnode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<!--creates an element with local name "x<": --><x<>x</x<>
+<script>
+test(function() {
+ var y = document.getElementsByTagName("x<")[0]
+ var child = y.firstChild
+ assert_equals(y.parentNode, document.body)
+ assert_equals(y.ownerDocument, document)
+ assert_equals(document.adoptNode(y), y)
+ assert_equals(y.parentNode, null)
+ assert_equals(y.firstChild, child)
+ assert_equals(y.ownerDocument, document)
+ assert_equals(child.ownerDocument, document)
+ var doc = document.implementation.createDocument(null, null, null)
+ assert_equals(doc.adoptNode(y), y)
+ assert_equals(y.parentNode, null)
+ assert_equals(y.firstChild, child)
+ assert_equals(y.ownerDocument, doc)
+ assert_equals(child.ownerDocument, doc)
+}, "Adopting an Element called 'x<' should work.")
+
+test(function() {
+ var x = document.createElement(":good:times:")
+ assert_equals(document.adoptNode(x), x);
+ var doc = document.implementation.createDocument(null, null, null)
+ assert_equals(doc.adoptNode(x), x)
+ assert_equals(x.parentNode, null)
+ assert_equals(x.ownerDocument, doc)
+}, "Adopting an Element called ':good:times:' should work.")
+
+test(function() {
+ var doctype = document.doctype;
+ assert_equals(doctype.parentNode, document)
+ assert_equals(doctype.ownerDocument, document)
+ assert_equals(document.adoptNode(doctype), doctype)
+ assert_equals(doctype.parentNode, null)
+ assert_equals(doctype.ownerDocument, document)
+}, "Explicitly adopting a DocumentType should work.")
+
+test(function() {
+ var doc = document.implementation.createDocument(null, null, null)
+ assert_throws_dom("NOT_SUPPORTED_ERR", function() { document.adoptNode(doc) })
+}, "Adopting a Document should throw.")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-characterSet-normalization-1.html b/testing/web-platform/tests/dom/nodes/Document-characterSet-normalization-1.html
new file mode 100644
index 0000000000..0facd50e49
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-characterSet-normalization-1.html
@@ -0,0 +1,157 @@
+<!doctype html>
+<title>document.characterSet (inputEncoding and charset as aliases) normalization tests</title>
+<link rel=author title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="./characterset-helper.js"></script>
+<style>iframe { display: none }</style>
+<script>
+"use strict";
+
+// Taken straight from https://encoding.spec.whatwg.org/
+var encodingMap = {
+ "UTF-8": [
+ "unicode-1-1-utf-8",
+ "utf-8",
+ "utf8",
+ // As we use <meta>, utf-16 will map to utf-8 per
+ // https://html.spec.whatwg.org/multipage/#documentEncoding
+ "utf-16",
+ "utf-16le",
+ "utf-16be",
+ ],
+ "IBM866": [
+ "866",
+ "cp866",
+ "csibm866",
+ "ibm866",
+ ],
+ "ISO-8859-2": [
+ "csisolatin2",
+ "iso-8859-2",
+ "iso-ir-101",
+ "iso8859-2",
+ "iso88592",
+ "iso_8859-2",
+ "iso_8859-2:1987",
+ "l2",
+ "latin2",
+ ],
+ "ISO-8859-3": [
+ "csisolatin3",
+ "iso-8859-3",
+ "iso-ir-109",
+ "iso8859-3",
+ "iso88593",
+ "iso_8859-3",
+ "iso_8859-3:1988",
+ "l3",
+ "latin3",
+ ],
+ "ISO-8859-4": [
+ "csisolatin4",
+ "iso-8859-4",
+ "iso-ir-110",
+ "iso8859-4",
+ "iso88594",
+ "iso_8859-4",
+ "iso_8859-4:1988",
+ "l4",
+ "latin4",
+ ],
+ "ISO-8859-5": [
+ "csisolatincyrillic",
+ "cyrillic",
+ "iso-8859-5",
+ "iso-ir-144",
+ "iso8859-5",
+ "iso88595",
+ "iso_8859-5",
+ "iso_8859-5:1988",
+ ],
+ "ISO-8859-6": [
+ "arabic",
+ "asmo-708",
+ "csiso88596e",
+ "csiso88596i",
+ "csisolatinarabic",
+ "ecma-114",
+ "iso-8859-6",
+ "iso-8859-6-e",
+ "iso-8859-6-i",
+ "iso-ir-127",
+ "iso8859-6",
+ "iso88596",
+ "iso_8859-6",
+ "iso_8859-6:1987",
+ ],
+ "ISO-8859-7": [
+ "csisolatingreek",
+ "ecma-118",
+ "elot_928",
+ "greek",
+ "greek8",
+ "iso-8859-7",
+ "iso-ir-126",
+ "iso8859-7",
+ "iso88597",
+ "iso_8859-7",
+ "iso_8859-7:1987",
+ "sun_eu_greek",
+ ],
+ "ISO-8859-8": [
+ "csiso88598e",
+ "csisolatinhebrew",
+ "hebrew",
+ "iso-8859-8",
+ "iso-8859-8-e",
+ "iso-ir-138",
+ "iso8859-8",
+ "iso88598",
+ "iso_8859-8",
+ "iso_8859-8:1988",
+ "visual",
+ ],
+ "ISO-8859-8-I": [
+ "csiso88598i",
+ "iso-8859-8-i",
+ "logical",
+ ],
+ "ISO-8859-10": [
+ "csisolatin6",
+ "iso-8859-10",
+ "iso-ir-157",
+ "iso8859-10",
+ "iso885910",
+ "l6",
+ "latin6",
+ ],
+ "ISO-8859-13": [
+ "iso-8859-13",
+ "iso8859-13",
+ "iso885913",
+ ],
+ "ISO-8859-14": [
+ "iso-8859-14",
+ "iso8859-14",
+ "iso885914",
+ ],
+ "ISO-8859-15": [
+ "csisolatin9",
+ "iso-8859-15",
+ "iso8859-15",
+ "iso885915",
+ "iso_8859-15",
+ "l9",
+ ],
+ "ISO-8859-16": [
+ "iso-8859-16",
+ ],
+};
+
+runCharacterSetTests(encodingMap);
+
+</script>
+<!-- vim: set expandtab tabstop=2 shiftwidth=2: -->
diff --git a/testing/web-platform/tests/dom/nodes/Document-characterSet-normalization-2.html b/testing/web-platform/tests/dom/nodes/Document-characterSet-normalization-2.html
new file mode 100644
index 0000000000..7c7691bdc1
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-characterSet-normalization-2.html
@@ -0,0 +1,179 @@
+<!doctype html>
+<title>document.characterSet (inputEncoding and charset as aliases) normalization tests</title>
+<link rel=author title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="./characterset-helper.js"></script>
+<style>iframe { display: none }</style>
+<script>
+"use strict";
+
+// Taken straight from https://encoding.spec.whatwg.org/
+var encodingMap = {
+ "KOI8-R": [
+ "cskoi8r",
+ "koi",
+ "koi8",
+ "koi8-r",
+ "koi8_r",
+ ],
+ "KOI8-U": [
+ "koi8-ru",
+ "koi8-u",
+ ],
+ "macintosh": [
+ "csmacintosh",
+ "mac",
+ "macintosh",
+ "x-mac-roman",
+ ],
+ "windows-874": [
+ "dos-874",
+ "iso-8859-11",
+ "iso8859-11",
+ "iso885911",
+ "tis-620",
+ "windows-874",
+ ],
+ "windows-1250": [
+ "cp1250",
+ "windows-1250",
+ "x-cp1250",
+ ],
+ "windows-1251": [
+ "cp1251",
+ "windows-1251",
+ "x-cp1251",
+ ],
+ "windows-1252": [
+ "ansi_x3.4-1968",
+ "ascii",
+ "cp1252",
+ "cp819",
+ "csisolatin1",
+ "ibm819",
+ "iso-8859-1",
+ "iso-ir-100",
+ "iso8859-1",
+ "iso88591",
+ "iso_8859-1",
+ "iso_8859-1:1987",
+ "l1",
+ "latin1",
+ "us-ascii",
+ "windows-1252",
+ "x-cp1252",
+ // As we use <meta>, x-user-defined will map to windows-1252 per
+ // https://html.spec.whatwg.org/multipage/#documentEncoding
+ "x-user-defined"
+ ],
+ "windows-1253": [
+ "cp1253",
+ "windows-1253",
+ "x-cp1253",
+ ],
+ "windows-1254": [
+ "cp1254",
+ "csisolatin5",
+ "iso-8859-9",
+ "iso-ir-148",
+ "iso8859-9",
+ "iso88599",
+ "iso_8859-9",
+ "iso_8859-9:1989",
+ "l5",
+ "latin5",
+ "windows-1254",
+ "x-cp1254",
+ ],
+ "windows-1255": [
+ "cp1255",
+ "windows-1255",
+ "x-cp1255",
+ ],
+ "windows-1256": [
+ "cp1256",
+ "windows-1256",
+ "x-cp1256",
+ ],
+ "windows-1257": [
+ "cp1257",
+ "windows-1257",
+ "x-cp1257",
+ ],
+ "windows-1258": [
+ "cp1258",
+ "windows-1258",
+ "x-cp1258",
+ ],
+ "x-mac-cyrillic": [
+ "x-mac-cyrillic",
+ "x-mac-ukrainian",
+ ],
+ "GBK": [
+ "chinese",
+ "csgb2312",
+ "csiso58gb231280",
+ "gb2312",
+ "gb_2312",
+ "gb_2312-80",
+ "gbk",
+ "iso-ir-58",
+ "x-gbk",
+ ],
+ "gb18030": [
+ "gb18030",
+ ],
+ "Big5": [
+ "big5",
+ "big5-hkscs",
+ "cn-big5",
+ "csbig5",
+ "x-x-big5",
+ ],
+ "EUC-JP": [
+ "cseucpkdfmtjapanese",
+ "euc-jp",
+ "x-euc-jp",
+ ],
+ "ISO-2022-JP": [
+ "csiso2022jp",
+ "iso-2022-jp",
+ ],
+ "Shift_JIS": [
+ "csshiftjis",
+ "ms932",
+ "ms_kanji",
+ "shift-jis",
+ "shift_jis",
+ "sjis",
+ "windows-31j",
+ "x-sjis",
+ ],
+ "EUC-KR": [
+ "cseuckr",
+ "csksc56011987",
+ "euc-kr",
+ "iso-ir-149",
+ "korean",
+ "ks_c_5601-1987",
+ "ks_c_5601-1989",
+ "ksc5601",
+ "ksc_5601",
+ "windows-949",
+ ],
+ "replacement": [
+ "csiso2022kr",
+ "hz-gb-2312",
+ "iso-2022-cn",
+ "iso-2022-cn-ext",
+ "iso-2022-kr",
+ ],
+};
+
+runCharacterSetTests(encodingMap);
+
+</script>
+<!-- vim: set expandtab tabstop=2 shiftwidth=2: -->
diff --git a/testing/web-platform/tests/dom/nodes/Document-constructor-svg.svg b/testing/web-platform/tests/dom/nodes/Document-constructor-svg.svg
new file mode 100644
index 0000000000..77e3d8996e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-constructor-svg.svg
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="windows-1252"?>
+<!-- Using windows-1252 to ensure that new Document() doesn't inherit utf-8
+ from the parent document. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+ width="100%" height="100%" viewBox="0 0 800 600">
+<title>Document constructor</title>
+<html:script src="/resources/testharness.js"></html:script>
+<html:script src="/resources/testharnessreport.js"></html:script>
+<html:script><![CDATA[
+test(function() {
+ var doc = new Document();
+ assert_true(doc instanceof Node, "Should be a Node");
+ assert_true(doc instanceof Document, "Should be a Document");
+ assert_false(doc instanceof XMLDocument, "Should not be an XMLDocument");
+ assert_equals(Object.getPrototypeOf(doc), Document.prototype,
+ "Document should be the primary interface");
+}, "new Document(): interfaces")
+
+test(function() {
+ var doc = new Document();
+ assert_equals(doc.firstChild, null, "firstChild");
+ assert_equals(doc.lastChild, null, "lastChild");
+ assert_equals(doc.doctype, null, "doctype");
+ assert_equals(doc.documentElement, null, "documentElement");
+ assert_array_equals(doc.childNodes, [], "childNodes");
+}, "new Document(): children")
+
+test(function() {
+ var doc = new Document();
+ assert_equals(doc.URL, "about:blank");
+ assert_equals(doc.documentURI, "about:blank");
+ assert_equals(doc.compatMode, "CSS1Compat");
+ assert_equals(doc.characterSet, "UTF-8");
+ assert_equals(doc.contentType, "application/xml");
+ assert_equals(doc.createElement("DIV").localName, "DIV");
+}, "new Document(): metadata")
+
+test(function() {
+ var doc = new Document();
+ assert_equals(doc.characterSet, "UTF-8", "characterSet");
+ assert_equals(doc.charset, "UTF-8", "charset");
+ assert_equals(doc.inputEncoding, "UTF-8", "inputEncoding");
+}, "new Document(): characterSet aliases")
+]]></html:script>
+</svg>
+
diff --git a/testing/web-platform/tests/dom/nodes/Document-constructor-xml.xml b/testing/web-platform/tests/dom/nodes/Document-constructor-xml.xml
new file mode 100644
index 0000000000..c9fc775806
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-constructor-xml.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="windows-1252"?>
+<!-- Using windows-1252 to ensure that new Document() doesn't inherit utf-8
+ from the parent document. -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Document constructor</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-document" />
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script><![CDATA[
+test(function() {
+ var doc = new Document();
+ assert_true(doc instanceof Node, "Should be a Node");
+ assert_true(doc instanceof Document, "Should be a Document");
+ assert_false(doc instanceof XMLDocument, "Should not be an XMLDocument");
+ assert_equals(Object.getPrototypeOf(doc), Document.prototype,
+ "Document should be the primary interface");
+}, "new Document(): interfaces")
+
+test(function() {
+ var doc = new Document();
+ assert_equals(doc.firstChild, null, "firstChild");
+ assert_equals(doc.lastChild, null, "lastChild");
+ assert_equals(doc.doctype, null, "doctype");
+ assert_equals(doc.documentElement, null, "documentElement");
+ assert_array_equals(doc.childNodes, [], "childNodes");
+}, "new Document(): children")
+
+test(function() {
+ var doc = new Document();
+ assert_equals(doc.URL, "about:blank");
+ assert_equals(doc.documentURI, "about:blank");
+ assert_equals(doc.compatMode, "CSS1Compat");
+ assert_equals(doc.characterSet, "UTF-8");
+ assert_equals(doc.contentType, "application/xml");
+ assert_equals(doc.createElement("DIV").localName, "DIV");
+}, "new Document(): metadata")
+
+test(function() {
+ var doc = new Document();
+ assert_equals(doc.characterSet, "UTF-8", "characterSet");
+ assert_equals(doc.charset, "UTF-8", "charset");
+ assert_equals(doc.inputEncoding, "UTF-8", "inputEncoding");
+}, "new Document(): characterSet aliases")
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Document-constructor.html b/testing/web-platform/tests/dom/nodes/Document-constructor.html
new file mode 100644
index 0000000000..e17de28471
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-constructor.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<meta charset=windows-1252>
+<!-- Using windows-1252 to ensure that new Document() doesn't inherit utf-8
+ from the parent document. -->
+<title>Document constructor</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var doc = new Document();
+ assert_true(doc instanceof Node, "Should be a Node");
+ assert_true(doc instanceof Document, "Should be a Document");
+ assert_false(doc instanceof XMLDocument, "Should not be an XMLDocument");
+ assert_equals(Object.getPrototypeOf(doc), Document.prototype,
+ "Document should be the primary interface");
+}, "new Document(): interfaces")
+
+test(function() {
+ var doc = new Document();
+ assert_equals(doc.firstChild, null, "firstChild");
+ assert_equals(doc.lastChild, null, "lastChild");
+ assert_equals(doc.doctype, null, "doctype");
+ assert_equals(doc.documentElement, null, "documentElement");
+ assert_array_equals(doc.childNodes, [], "childNodes");
+}, "new Document(): children")
+
+test(function() {
+ var doc = new Document();
+ assert_equals(doc.location, null);
+ assert_equals(doc.URL, "about:blank");
+ assert_equals(doc.documentURI, "about:blank");
+ assert_equals(doc.compatMode, "CSS1Compat");
+ assert_equals(doc.characterSet, "UTF-8");
+ assert_equals(doc.contentType, "application/xml");
+ assert_equals(doc.createElement("DIV").localName, "DIV");
+ assert_equals(doc.createElement("a").constructor, Element);
+}, "new Document(): metadata")
+
+test(function() {
+ var doc = new Document();
+ assert_equals(doc.characterSet, "UTF-8", "characterSet");
+ assert_equals(doc.charset, "UTF-8", "charset");
+ assert_equals(doc.inputEncoding, "UTF-8", "inputEncoding");
+}, "new Document(): characterSet aliases")
+
+test(function() {
+ var doc = new Document();
+ var a = doc.createElementNS("http://www.w3.org/1999/xhtml", "a");
+ assert_equals(a.constructor, HTMLAnchorElement);
+ // In UTF-8: 0xC3 0xA4
+ a.href = "http://example.org/?\u00E4";
+ assert_equals(a.href, "http://example.org/?%C3%A4");
+}, "new Document(): URL parsing")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_bmp.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_bmp.html
new file mode 100644
index 0000000000..8286437416
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_bmp.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>BMP document.contentType === 'image/bmp'</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.addEventListener('load', this.step_func_done(function() {
+ assert_equals(iframe.contentDocument.contentType, "image/bmp");
+ }), false);
+ iframe.src = "../resources/t.bmp";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_css.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_css.html
new file mode 100644
index 0000000000..0eb35edd5e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_css.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>CSS document.contentType === 'text/css'</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.addEventListener('load', this.step_func_done(function() {
+ assert_equals(iframe.contentDocument.contentType, "text/css");
+ }), false);
+ iframe.src = "../resources/style.css";
+ document.body.appendChild(iframe);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_datauri_02.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_datauri_02.html
new file mode 100644
index 0000000000..c124cb3979
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_datauri_02.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Data URI document.contentType === 'text/html' when data URI MIME type is set</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');
+ self.onmessage = this.step_func_done(e => {
+ assert_equals(e.data, "text/html");
+ });
+ iframe.src = "data:text/html;charset=utf-8,<!DOCTYPE html><script>parent.postMessage(document.contentType,'*')<\/script>";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_gif.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_gif.html
new file mode 100644
index 0000000000..8dd66dae59
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_gif.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>GIF document.contentType === 'image/gif'</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.addEventListener('load', this.step_func_done(function() {
+ assert_equals(iframe.contentDocument.contentType, "image/gif");
+ }), false);
+ iframe.src = "../resources/t.gif";
+ document.body.appendChild(iframe);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_html.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_html.html
new file mode 100644
index 0000000000..2b2d7263eb
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_html.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>HTM document.contentType === 'text/html'</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.addEventListener('load', this.step_func_done(function() {
+ assert_equals(iframe.contentDocument.contentType, "text/html");
+ }), false);
+ iframe.src = "../resources/blob.htm";
+ document.body.appendChild(iframe);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_javascripturi.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_javascripturi.html
new file mode 100644
index 0000000000..956589615a
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_javascripturi.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Javascript URI document.contentType === 'text/html'</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.addEventListener('load', this.step_func_done(function() {
+ assert_equals(iframe.contentDocument.contentType, "text/html");
+ assert_equals(iframe.contentDocument.documentElement.textContent, "text/html");
+ }), false);
+ iframe.src = "javascript:document.contentType";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_jpg.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_jpg.html
new file mode 100644
index 0000000000..13f57ec8b9
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_jpg.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>JPG document.contentType === 'image/jpeg'</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.addEventListener('load', this.step_func_done(function() {
+ assert_equals(iframe.contentDocument.contentType, "image/jpeg");
+ }), false);
+ iframe.src = "../resources/t.jpg";
+ document.body.appendChild(iframe);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_mimeheader_01.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_mimeheader_01.html
new file mode 100644
index 0000000000..87885efba6
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_mimeheader_01.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Custom document.contentType === 'text/xml' when explicitly set to this value</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.addEventListener('load', this.step_func_done(function() {
+ assert_equals(iframe.contentDocument.contentType, "text/xml");
+ }), false);
+ iframe.src = "../support/contenttype_setter.py?type=text&subtype=xml";
+ document.body.appendChild(iframe);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_mimeheader_02.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_mimeheader_02.html
new file mode 100644
index 0000000000..33870147fc
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_mimeheader_02.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Custom document.contentType === 'text/html' when explicitly set to this value and an attempt is made to override this value in an HTML meta header</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.addEventListener('load', this.step_func_done(function() {
+ assert_equals(iframe.contentDocument.contentType, "text/html");
+ }), false);
+ iframe.src = "../support/contenttype_setter.py?type=text&subtype=html&mimeHead=text%2Fxml";
+ document.body.appendChild(iframe);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_png.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_png.html
new file mode 100644
index 0000000000..a214ad3e98
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_png.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>PNG document.contentType === 'image/png'</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.addEventListener('load', this.step_func_done(function() {
+ assert_equals(iframe.contentDocument.contentType, "image/png");
+ }), false);
+ iframe.src = "../resources/t.png";
+ document.body.appendChild(iframe);
+});
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_txt.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_txt.html
new file mode 100644
index 0000000000..f40f641fb0
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_txt.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>TXT document.contentType === 'text/plain'</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.addEventListener('load', this.step_func_done(function() {
+ assert_equals(iframe.contentDocument.contentType, "text/plain");
+ }), false);
+ iframe.src = "../resources/blob.txt";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_xml.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_xml.html
new file mode 100644
index 0000000000..c382de8490
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_xml.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>XML document.contentType === 'application/xml'</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.addEventListener('load', this.step_func_done(function() {
+ assert_equals(iframe.contentDocument.contentType, "application/xml");
+ }), false);
+ iframe.src = "../resources/blob.xml";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/createDocument.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/createDocument.html
new file mode 100644
index 0000000000..78a952de4a
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/createDocument.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>document.implementation.createDocument: document.contentType === 'application/xhtml+xml'</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var doc = document.implementation.createDocument("http://www.w3.org/1999/xhtml", "html", null);
+ assert_equals(doc.contentType, "application/xhtml+xml");
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/createHTMLDocument.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/createHTMLDocument.html
new file mode 100644
index 0000000000..185e3c8c92
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/createHTMLDocument.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>document.implementation.createHTMLDocument: document.contentType === 'text/html'</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var doc = document.implementation.createHTMLDocument("test");
+ assert_equals(doc.contentType, "text/html");
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/xhr_responseType_document.html b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/xhr_responseType_document.html
new file mode 100644
index 0000000000..c2fb6c19fd
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/contentType/xhr_responseType_document.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>XHR - retrieve HTML document: document.contentType === 'application/xml'</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+async_test(function() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "../resources/blob.xml");
+ xhr.responseType = "document";
+ xhr.onload = this.step_func_done(function(response) {
+ assert_equals(xhr.readyState, 4);
+ assert_equals(xhr.status, 200);
+ assert_equals(xhr.responseXML.contentType, "application/xml");
+ });
+ xhr.send(null);
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/resources/blob.htm b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/blob.htm
new file mode 100644
index 0000000000..9d235ed07c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/blob.htm
@@ -0,0 +1 @@
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/resources/blob.txt b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/blob.txt
new file mode 100644
index 0000000000..9d235ed07c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/blob.txt
@@ -0,0 +1 @@
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/resources/blob.xml b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/blob.xml
new file mode 100644
index 0000000000..0922ed14b8
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/blob.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<blob>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</blob> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/resources/lib.js b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/lib.js
new file mode 100644
index 0000000000..c41d336c08
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/lib.js
@@ -0,0 +1 @@
+var t; \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/resources/style.css b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/style.css
new file mode 100644
index 0000000000..bb4ff575b7
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/style.css
@@ -0,0 +1,12 @@
+.unknown
+{
+ background-color:lightblue;
+}
+.pass
+{
+ background-color:lime;
+}
+.fail
+{
+ background-color:red;
+}
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.bmp b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.bmp
new file mode 100644
index 0000000000..5697c0aef4
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.bmp
Binary files differ
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.gif b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.gif
new file mode 100644
index 0000000000..91f269207a
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.gif
Binary files differ
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.jpg b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.jpg
new file mode 100644
index 0000000000..72b51899e4
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.jpg
Binary files differ
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.png b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.png
new file mode 100644
index 0000000000..447d9e3012
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/resources/t.png
Binary files differ
diff --git a/testing/web-platform/tests/dom/nodes/Document-contentType/support/contenttype_setter.py b/testing/web-platform/tests/dom/nodes/Document-contentType/support/contenttype_setter.py
new file mode 100644
index 0000000000..c4b1f8df27
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-contentType/support/contenttype_setter.py
@@ -0,0 +1,20 @@
+def main(request, response):
+ type = request.GET.first(b"type", None)
+ subtype = request.GET.first(b"subtype", None)
+ if type and subtype:
+ response.headers[b"Content-Type"] = type + b"/" + subtype
+
+ removeContentType = request.GET.first(b"removeContentType", None)
+ if removeContentType:
+ try:
+ del response.headers[b"Content-Type"]
+ except KeyError:
+ pass
+
+ content = b'<head>'
+ mimeHead = request.GET.first(b"mime", None);
+ if mimeHead:
+ content += b'<meta http-equiv="Content-Type" content="%s; charset=utf-8"/>' % mimeHead
+ content += b"</head>"
+
+ return content
diff --git a/testing/web-platform/tests/dom/nodes/Document-createAttribute.html b/testing/web-platform/tests/dom/nodes/Document-createAttribute.html
new file mode 100644
index 0000000000..b3dc8b60b9
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createAttribute.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Document.createAttribute</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=attributes.js></script>
+<script src=productions.js></script>
+<div id=log>
+<script>
+var xml_document;
+setup(function() {
+ xml_document = document.implementation.createDocument(null, null, null);
+});
+
+invalid_names.forEach(function(name) {
+ test(function() {
+ assert_throws_dom("INVALID_CHARACTER_ERR", function() {
+ document.createAttribute(name, "test");
+ });
+ }, "HTML document.createAttribute(" + format_value(name) + ") should throw");
+
+ test(function() {
+ assert_throws_dom("INVALID_CHARACTER_ERR", function() {
+ xml_document.createAttribute(name, "test");
+ });
+ }, "XML document.createAttribute(" + format_value(name) + ") should throw");
+});
+
+valid_names.forEach(name => {
+ test(() => {
+ let attr = document.createAttribute(name);
+ attr_is(attr, "", name.toLowerCase(), null, null, name.toLowerCase());
+ }, `HTML document.createAttribute(${format_value(name)})`);
+
+ test(() => {
+ let attr = xml_document.createAttribute(name);
+ attr_is(attr, "", name, null, null, name);
+ }, `XML document.createAttribute(${format_value(name)})`);
+});
+
+var tests = ["title", "TITLE", null, undefined];
+tests.forEach(function(name) {
+ test(function() {
+ var attribute = document.createAttribute(name);
+ attr_is(attribute, "", String(name).toLowerCase(), null, null, String(name).toLowerCase());
+ assert_equals(attribute.ownerElement, null);
+ }, "HTML document.createAttribute(" + format_value(name) + ")");
+
+ test(function() {
+ var attribute = xml_document.createAttribute(name);
+ attr_is(attribute, "", String(name), null, null, String(name));
+ assert_equals(attribute.ownerElement, null);
+ }, "XML document.createAttribute(" + format_value(name) + ")");
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createCDATASection-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Document-createCDATASection-xhtml.xhtml
new file mode 100644
index 0000000000..b0a5a7f284
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createCDATASection-xhtml.xhtml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta charset="utf-8"/>
+ <title>document.createCDATASection</title>
+ <link rel="help" href="https://dom.spec.whatwg.org/#dom-document-createcdatasection"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="Document-createComment-createTextNode.js"></script>
+</head>
+
+<body>
+ <script>
+ "use strict";
+ test_create("createCDATASection", CDATASection, 4, "#cdata-section");
+
+ test(() => {
+ assert_throws_dom("InvalidCharacterError", () => document.createCDATASection(" ]" + "]> "));
+ }, "Creating a CDATA section containing the string \"]" + "]>\" must throw");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createCDATASection.html b/testing/web-platform/tests/dom/nodes/Document-createCDATASection.html
new file mode 100644
index 0000000000..72b3684c75
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createCDATASection.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>document.createCDATASection must throw in HTML documents</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-document-createcdatasection">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+setup({ single_test: true });
+
+assert_throws_dom("NotSupportedError", () => document.createCDATASection("foo"));
+
+done();
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createComment-createTextNode.js b/testing/web-platform/tests/dom/nodes/Document-createComment-createTextNode.js
new file mode 100644
index 0000000000..62a38d380d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createComment-createTextNode.js
@@ -0,0 +1,22 @@
+function test_create(method, iface, nodeType, nodeName) {
+ ["\u000b", "a -- b", "a-", "-b", null, undefined].forEach(function(value) {
+ test(function() {
+ var c = document[method](value);
+ var expected = String(value);
+ assert_true(c instanceof iface);
+ assert_true(c instanceof CharacterData);
+ assert_true(c instanceof Node);
+ assert_equals(c.ownerDocument, document);
+ assert_equals(c.data, expected, "data");
+ assert_equals(c.nodeValue, expected, "nodeValue");
+ assert_equals(c.textContent, expected, "textContent");
+ assert_equals(c.length, expected.length);
+ assert_equals(c.nodeType, nodeType);
+ assert_equals(c.nodeName, nodeName);
+ assert_equals(c.hasChildNodes(), false);
+ assert_equals(c.childNodes.length, 0);
+ assert_equals(c.firstChild, null);
+ assert_equals(c.lastChild, null);
+ }, method + "(" + format_value(value) + ")");
+ });
+}
diff --git a/testing/web-platform/tests/dom/nodes/Document-createComment.html b/testing/web-platform/tests/dom/nodes/Document-createComment.html
new file mode 100644
index 0000000000..a175c3a2f8
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createComment.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Document.createComment</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-createcomment">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-ownerdocument">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-data">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-nodevalue">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-textcontent">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-length">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-nodetype">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-haschildnodes">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-childnodes">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-firstchild">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-lastchild">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="Document-createComment-createTextNode.js"></script>
+<div id="log"></div>
+<script>
+test_create("createComment", Comment, 8, "#comment");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.html b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.html
new file mode 100644
index 0000000000..b80a99a784
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.html
@@ -0,0 +1 @@
+<math></math> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.svg b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.svg
new file mode 100644
index 0000000000..b80a99a784
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.svg
@@ -0,0 +1 @@
+<math></math> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.xhtml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.xhtml
new file mode 100644
index 0000000000..b80a99a784
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.xhtml
@@ -0,0 +1 @@
+<math></math> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.xml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.xml
new file mode 100644
index 0000000000..b80a99a784
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_mathml.xml
@@ -0,0 +1 @@
+<math></math> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.html b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.html
new file mode 100644
index 0000000000..dc1ced5b6b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.html
@@ -0,0 +1 @@
+<svg></svg> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.svg b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.svg
new file mode 100644
index 0000000000..dc1ced5b6b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.svg
@@ -0,0 +1 @@
+<svg></svg> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.xhtml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.xhtml
new file mode 100644
index 0000000000..dc1ced5b6b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.xhtml
@@ -0,0 +1 @@
+<svg></svg> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.xml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.xml
new file mode 100644
index 0000000000..dc1ced5b6b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_svg.xml
@@ -0,0 +1 @@
+<svg></svg> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.html b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.html
new file mode 100644
index 0000000000..6c70bcfe4d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.html
@@ -0,0 +1 @@
+<html></html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.svg b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.svg
new file mode 100644
index 0000000000..6c70bcfe4d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.svg
@@ -0,0 +1 @@
+<html></html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.xhtml
new file mode 100644
index 0000000000..6c70bcfe4d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.xhtml
@@ -0,0 +1 @@
+<html></html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.xml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.xml
new file mode 100644
index 0000000000..6c70bcfe4d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/bare_xhtml.xml
@@ -0,0 +1 @@
+<html></html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.html b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.html
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.svg b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.svg
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.svg
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.xhtml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.xhtml
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.xhtml
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.xml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.xml
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/empty.xml
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/generate.py b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/generate.py
new file mode 100755
index 0000000000..20c866bee8
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/generate.py
@@ -0,0 +1,78 @@
+#!/usr/bin/python
+
+import os
+import sys
+
+THIS_NAME = u"generate.py"
+
+# Note: these lists must be kept in sync with the lists in
+# Document-createElement-namespace.html, and this script must be run whenever
+# the lists are updated. (We could keep the lists in a shared JSON file, but
+# seems like too much effort.)
+FILES = (
+ (u"empty", u""),
+ (u"minimal_html", u"<!doctype html><title></title>"),
+
+ (u"xhtml", u'<html xmlns="http://www.w3.org/1999/xhtml"></html>'),
+ (u"svg", u'<svg xmlns="http://www.w3.org/2000/svg"></svg>'),
+ (u"mathml", u'<mathml xmlns="http://www.w3.org/1998/Math/MathML"></mathml>'),
+
+ (u"bare_xhtml", u"<html></html>"),
+ (u"bare_svg", u"<svg></svg>"),
+ (u"bare_mathml", u"<math></math>"),
+
+ (u"xhtml_ns_removed", u"""\
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head><script>
+ var newRoot = document.createElementNS(null, "html");
+ document.removeChild(document.documentElement);
+ document.appendChild(newRoot);
+ </script></head>
+</html>
+"""),
+ (u"xhtml_ns_changed", u"""\
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head><script>
+ var newRoot = document.createElementNS("http://www.w3.org/2000/svg", "abc");
+ document.removeChild(document.documentElement);
+ document.appendChild(newRoot);
+ </script></head>
+</html>
+"""),
+)
+
+EXTENSIONS = (
+ u"html",
+ u"xhtml",
+ u"xml",
+ u"svg",
+ # Was not able to get server MIME type working properly :(
+ #"mml",
+)
+
+def __main__():
+ if len(sys.argv) > 1:
+ print(u"No arguments expected, aborting")
+ return
+
+ if not os.access(THIS_NAME, os.F_OK):
+ print(u"Must be run from the directory of " + THIS_NAME + u", aborting")
+ return
+
+ for name in os.listdir(u"."):
+ if name == THIS_NAME:
+ continue
+ os.remove(name)
+
+ manifest = open(u"MANIFEST", u"w")
+
+ for name, contents in FILES:
+ for extension in EXTENSIONS:
+ f = open(name + u"." + extension, u"w")
+ f.write(contents)
+ f.close()
+ manifest.write(u"support " + name + u"." + extension + u"\n")
+
+ manifest.close()
+
+__main__()
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.html b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.html
new file mode 100644
index 0000000000..0bec8e99e4
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.html
@@ -0,0 +1 @@
+<mathml xmlns="http://www.w3.org/1998/Math/MathML"></mathml> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.svg b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.svg
new file mode 100644
index 0000000000..0bec8e99e4
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.svg
@@ -0,0 +1 @@
+<mathml xmlns="http://www.w3.org/1998/Math/MathML"></mathml> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.xhtml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.xhtml
new file mode 100644
index 0000000000..0bec8e99e4
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.xhtml
@@ -0,0 +1 @@
+<mathml xmlns="http://www.w3.org/1998/Math/MathML"></mathml> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.xml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.xml
new file mode 100644
index 0000000000..0bec8e99e4
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/mathml.xml
@@ -0,0 +1 @@
+<mathml xmlns="http://www.w3.org/1998/Math/MathML"></mathml> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.html b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.html
new file mode 100644
index 0000000000..a33d9859af
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.html
@@ -0,0 +1 @@
+<!doctype html><title></title> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.svg b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.svg
new file mode 100644
index 0000000000..a33d9859af
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.svg
@@ -0,0 +1 @@
+<!doctype html><title></title> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.xhtml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.xhtml
new file mode 100644
index 0000000000..a33d9859af
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.xhtml
@@ -0,0 +1 @@
+<!doctype html><title></title> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.xml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.xml
new file mode 100644
index 0000000000..a33d9859af
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/minimal_html.xml
@@ -0,0 +1 @@
+<!doctype html><title></title> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.html b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.html
new file mode 100644
index 0000000000..64def4af7f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.html
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"></svg> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.svg b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.svg
new file mode 100644
index 0000000000..64def4af7f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"></svg> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.xhtml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.xhtml
new file mode 100644
index 0000000000..64def4af7f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.xhtml
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"></svg> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.xml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.xml
new file mode 100644
index 0000000000..64def4af7f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/svg.xml
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"></svg> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.html b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.html
new file mode 100644
index 0000000000..1cba998241
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.html
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"></html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.svg b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.svg
new file mode 100644
index 0000000000..1cba998241
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.svg
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"></html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.xhtml
new file mode 100644
index 0000000000..1cba998241
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"></html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.xml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.xml
new file mode 100644
index 0000000000..1cba998241
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml.xml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"></html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.html b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.html
new file mode 100644
index 0000000000..b228c7f740
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.html
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head><script>
+ var newRoot = document.createElementNS("http://www.w3.org/2000/svg", "abc");
+ document.removeChild(document.documentElement);
+ document.appendChild(newRoot);
+ </script></head>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.svg b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.svg
new file mode 100644
index 0000000000..b228c7f740
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.svg
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head><script>
+ var newRoot = document.createElementNS("http://www.w3.org/2000/svg", "abc");
+ document.removeChild(document.documentElement);
+ document.appendChild(newRoot);
+ </script></head>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.xhtml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.xhtml
new file mode 100644
index 0000000000..b228c7f740
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head><script>
+ var newRoot = document.createElementNS("http://www.w3.org/2000/svg", "abc");
+ document.removeChild(document.documentElement);
+ document.appendChild(newRoot);
+ </script></head>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.xml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.xml
new file mode 100644
index 0000000000..b228c7f740
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_changed.xml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head><script>
+ var newRoot = document.createElementNS("http://www.w3.org/2000/svg", "abc");
+ document.removeChild(document.documentElement);
+ document.appendChild(newRoot);
+ </script></head>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.html b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.html
new file mode 100644
index 0000000000..dba395fed0
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.html
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head><script>
+ var newRoot = document.createElementNS(null, "html");
+ document.removeChild(document.documentElement);
+ document.appendChild(newRoot);
+ </script></head>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.svg b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.svg
new file mode 100644
index 0000000000..dba395fed0
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.svg
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head><script>
+ var newRoot = document.createElementNS(null, "html");
+ document.removeChild(document.documentElement);
+ document.appendChild(newRoot);
+ </script></head>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.xhtml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.xhtml
new file mode 100644
index 0000000000..dba395fed0
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head><script>
+ var newRoot = document.createElementNS(null, "html");
+ document.removeChild(document.documentElement);
+ document.appendChild(newRoot);
+ </script></head>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.xml b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.xml
new file mode 100644
index 0000000000..dba395fed0
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace-tests/xhtml_ns_removed.xml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head><script>
+ var newRoot = document.createElementNS(null, "html");
+ document.removeChild(document.documentElement);
+ document.appendChild(newRoot);
+ </script></head>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement-namespace.html b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace.html
new file mode 100644
index 0000000000..cea61f1aec
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement-namespace.html
@@ -0,0 +1,117 @@
+<!doctype html>
+<title>document.createElement() namespace tests</title>
+<link rel=author title="Aryeh Gregor" href=ayg@aryeh.name>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+"use strict";
+/**
+ * This tests the namespace of elements created by the Document interface's
+ * createElement() method. See bug:
+ * https://www.w3.org/Bugs/Public/show_bug.cgi?id=19431
+ */
+
+/**
+ * Test that an element created using the Document object doc has the namespace
+ * that would be expected for the given contentType.
+ */
+function testDoc(doc, contentType) {
+ if (doc.contentType !== undefined) {
+ // Sanity check
+ assert_equals(doc.contentType, contentType,
+ "Wrong MIME type returned from doc.contentType");
+ }
+
+ var expectedNamespace = contentType == "text/html" ||
+ contentType == "application/xhtml+xml"
+ ? "http://www.w3.org/1999/xhtml" : null;
+
+ assert_equals(doc.createElement("x").namespaceURI, expectedNamespace);
+}
+
+// First test various objects we create in JS
+test(function() {
+ testDoc(document, "text/html")
+}, "Created element's namespace in current document");
+test(function() {
+ testDoc(document.implementation.createHTMLDocument(""), "text/html");
+}, "Created element's namespace in created HTML document");
+test(function() {
+ testDoc(document.implementation.createDocument(null, "", null),
+ "application/xml");
+}, "Created element's namespace in created XML document");
+test(function() {
+ testDoc(document.implementation.createDocument("http://www.w3.org/1999/xhtml", "html", null),
+ "application/xhtml+xml");
+}, "Created element's namespace in created XHTML document");
+test(function() {
+ testDoc(document.implementation.createDocument("http://www.w3.org/2000/svg", "svg", null),
+ "image/svg+xml");
+}, "Created element's namespace in created SVG document");
+test(function() {
+ testDoc(document.implementation.createDocument("http://www.w3.org/1998/Math/MathML", "math", null),
+ "application/xml");
+}, "Created element's namespace in created MathML document");
+
+// Second also test document created by DOMParser
+test(function() {
+ testDoc(new DOMParser().parseFromString("", "text/html"), "text/html");
+}, "Created element's namespace in created HTML document by DOMParser ('text/html')");
+test(function() {
+ testDoc(new DOMParser().parseFromString("<root/>", "text/xml"), "text/xml");
+}, "Created element's namespace in created XML document by DOMParser ('text/xml')");
+test(function() {
+ testDoc(new DOMParser().parseFromString("<root/>", "application/xml"), "application/xml");
+}, "Created element's namespace in created XML document by DOMParser ('application/xml')");
+test(function() {
+ testDoc(new DOMParser().parseFromString("<html/>", "application/xhtml+xml"), "application/xhtml+xml");
+}, "Created element's namespace in created XHTML document by DOMParser ('application/xhtml+xml')");
+test(function() {
+ testDoc(new DOMParser().parseFromString("<math/>", "image/svg+xml"), "image/svg+xml");
+}, "Created element's namespace in created SVG document by DOMParser ('image/svg+xml')");
+
+// Now for various externally-loaded files. Note: these lists must be kept
+// synced with the lists in generate.py in the subdirectory, and that script
+// must be run whenever the lists are updated. (We could keep the lists in a
+// shared JSON file, but it seems like too much effort.)
+var testExtensions = {
+ html: "text/html",
+ xhtml: "application/xhtml+xml",
+ xml: "application/xml",
+ svg: "image/svg+xml",
+ // Was not able to get server MIME type working properly :(
+ //mml: "application/mathml+xml",
+};
+
+var tests = [
+ "empty",
+ "minimal_html",
+
+ "xhtml",
+ "svg",
+ "mathml",
+
+ "bare_xhtml",
+ "bare_svg",
+ "bare_mathml",
+
+ "xhtml_ns_removed",
+ "xhtml_ns_changed",
+];
+
+tests.forEach(function(testName) {
+ Object.keys(testExtensions).forEach(function(ext) {
+ async_test(function(t) {
+ var iframe = document.createElement("iframe");
+ iframe.src = "Document-createElement-namespace-tests/" +
+ testName + "." + ext;
+ iframe.onload = t.step_func_done(function() {
+ testDoc(iframe.contentDocument, testExtensions[ext]);
+ document.body.removeChild(iframe);
+ });
+ document.body.appendChild(iframe);
+ }, "Created element's namespace in " + testName + "." + ext);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElement.html b/testing/web-platform/tests/dom/nodes/Document-createElement.html
new file mode 100644
index 0000000000..93435ac82d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement.html
@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Document.createElement</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-createelement">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-localname">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-tagname">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-prefix">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-namespaceuri">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<iframe src="/common/dummy.xml"></iframe>
+<iframe src="/common/dummy.xhtml"></iframe>
+<script>
+function toASCIIUppercase(str) {
+ var diff = "a".charCodeAt(0) - "A".charCodeAt(0);
+ var res = "";
+ for (var i = 0; i < str.length; ++i) {
+ if ("a" <= str[i] && str[i] <= "z") {
+ res += String.fromCharCode(str.charCodeAt(i) - diff);
+ } else {
+ res += str[i];
+ }
+ }
+ return res;
+}
+function toASCIILowercase(str) {
+ var diff = "a".charCodeAt(0) - "A".charCodeAt(0);
+ var res = "";
+ for (var i = 0; i < str.length; ++i) {
+ if ("A" <= str[i] && str[i] <= "Z") {
+ res += String.fromCharCode(str.charCodeAt(i) + diff);
+ } else {
+ res += str[i];
+ }
+ }
+ return res;
+}
+var HTMLNS = "http://www.w3.org/1999/xhtml",
+ valid = [
+ undefined,
+ null,
+ "foo",
+ "f1oo",
+ "foo1",
+ "f\u0BC6",
+ "foo\u0BC6",
+ ":",
+ ":foo",
+ "f:oo",
+ "foo:",
+ "f:o:o",
+ "f::oo",
+ "f::oo:",
+ "foo:0",
+ "foo:_",
+ // combining char after :, invalid QName but valid Name
+ "foo:\u0BC6",
+ "foo:foo\u0BC6",
+ "foo\u0BC6:foo",
+ "xml",
+ "xmlns",
+ "xmlfoo",
+ "xml:foo",
+ "xmlns:foo",
+ "xmlfoo:bar",
+ "svg",
+ "math",
+ "FOO",
+ // Test that non-ASCII chars don't get uppercased/lowercased
+ "mar\u212a",
+ "\u0130nput",
+ "\u0131nput",
+ ],
+ invalid = [
+ "",
+ "1foo",
+ "1:foo",
+ "fo o",
+ "\u0300foo",
+ "}foo",
+ "f}oo",
+ "foo}",
+ "\ufffffoo",
+ "f\uffffoo",
+ "foo\uffff",
+ "<foo",
+ "foo>",
+ "<foo>",
+ "f<oo",
+ "-foo",
+ ".foo",
+ "\u0300",
+ ]
+
+var xmlIframe = document.querySelector('[src="/common/dummy.xml"]');
+var xhtmlIframe = document.querySelector('[src="/common/dummy.xhtml"]');
+
+function getWin(desc) {
+ if (desc == "HTML document") {
+ return window;
+ }
+ if (desc == "XML document") {
+ assert_equals(xmlIframe.contentDocument.documentElement.textContent,
+ "Dummy XML document", "XML document didn't load");
+ return xmlIframe.contentWindow;
+ }
+ if (desc == "XHTML document") {
+ assert_equals(xhtmlIframe.contentDocument.documentElement.textContent,
+ "Dummy XHTML document", "XHTML document didn't load");
+ return xhtmlIframe.contentWindow;
+ }
+}
+
+
+valid.forEach(function(t) {
+ ["HTML document", "XML document", "XHTML document"].forEach(function(desc) {
+ async_test(function(testObj) {
+ window.addEventListener("load", function() {
+ testObj.step(function() {
+ var win = getWin(desc);
+ var doc = win.document;
+ var elt = doc.createElement(t)
+ assert_true(elt instanceof win.Element, "instanceof Element")
+ assert_true(elt instanceof win.Node, "instanceof Node")
+ assert_equals(elt.localName,
+ desc == "HTML document" ? toASCIILowercase(String(t))
+ : String(t),
+ "localName")
+ assert_equals(elt.tagName,
+ desc == "HTML document" ? toASCIIUppercase(String(t))
+ : String(t),
+ "tagName")
+ assert_equals(elt.prefix, null, "prefix")
+ assert_equals(elt.namespaceURI,
+ desc == "XML document" ? null : HTMLNS, "namespaceURI")
+ });
+ testObj.done();
+ });
+ }, "createElement(" + format_value(t) + ") in " + desc);
+ });
+});
+invalid.forEach(function(arg) {
+ ["HTML document", "XML document", "XHTML document"].forEach(function(desc) {
+ async_test(function(testObj) {
+ window.addEventListener("load", function() {
+ testObj.step(function() {
+ let win = getWin(desc);
+ let doc = win.document;
+ assert_throws_dom("InvalidCharacterError", win.DOMException,
+ function() { doc.createElement(arg) })
+ });
+ testObj.done();
+ });
+ }, "createElement(" + format_value(arg) + ") in " + desc);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElementNS.html b/testing/web-platform/tests/dom/nodes/Document-createElementNS.html
new file mode 100644
index 0000000000..5ad81043de
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElementNS.html
@@ -0,0 +1,224 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Document.createElementNS</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-createelementns">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="Document-createElementNS.js"></script>
+<div id="log"></div>
+<iframe src="/common/dummy.xml"></iframe>
+<iframe src="/common/dummy.xhtml"></iframe>
+<script>
+var tests = createElementNS_tests.concat([
+ /* Arrays with three elements:
+ * the namespace argument
+ * the qualifiedName argument
+ * the expected exception, or null if none
+ */
+ ["", "", "INVALID_CHARACTER_ERR"],
+ [null, "", "INVALID_CHARACTER_ERR"],
+ [undefined, "", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", null, null],
+ ["http://example.com/", "", "INVALID_CHARACTER_ERR"],
+ ["/", null, null],
+ ["/", "", "INVALID_CHARACTER_ERR"],
+ ["http://www.w3.org/XML/1998/namespace", null, null],
+ ["http://www.w3.org/XML/1998/namespace", "", "INVALID_CHARACTER_ERR"],
+ ["http://www.w3.org/2000/xmlns/", null, "NAMESPACE_ERR"],
+ ["http://www.w3.org/2000/xmlns/", "", "INVALID_CHARACTER_ERR"],
+ ["foo:", null, null],
+ ["foo:", "", "INVALID_CHARACTER_ERR"],
+])
+
+var xmlIframe = document.querySelector('[src="/common/dummy.xml"]');
+var xhtmlIframe = document.querySelector('[src="/common/dummy.xhtml"]');
+
+function runTest(t, i, desc) {
+ async_test(function(testObj) {
+ window.addEventListener("load", function() {
+ testObj.step(function() {
+ var doc;
+ if (desc == "HTML document") {
+ doc = document;
+ } else if (desc == "XML document") {
+ doc = xmlIframe.contentDocument;
+ // Make sure we're testing the right document
+ assert_equals(doc.documentElement.textContent, "Dummy XML document");
+ } else if (desc == "XHTML document") {
+ doc = xhtmlIframe.contentDocument;
+ assert_equals(doc.documentElement.textContent, "Dummy XHTML document");
+ }
+ var namespace = t[0], qualifiedName = t[1], expected = t[2]
+ if (expected != null) {
+ assert_throws_dom(expected, doc.defaultView.DOMException, function() {doc.createElementNS(namespace, qualifiedName) });
+ } else {
+ var element = doc.createElementNS(namespace, qualifiedName)
+ assert_not_equals(element, null)
+ assert_equals(element.nodeType, Node.ELEMENT_NODE)
+ assert_equals(element.nodeType, element.ELEMENT_NODE)
+ assert_equals(element.nodeValue, null)
+ assert_equals(element.ownerDocument, doc)
+ var qualified = String(qualifiedName), names = []
+ if (qualified.indexOf(":") >= 0) {
+ names = qualified.split(":", 2)
+ } else {
+ names = [null, qualified]
+ }
+ assert_equals(element.prefix, names[0])
+ assert_equals(element.localName, names[1])
+ assert_equals(element.tagName, qualified)
+ assert_equals(element.nodeName, qualified)
+ assert_equals(element.namespaceURI,
+ namespace === undefined || namespace === "" ? null
+ : namespace)
+ }
+ });
+ testObj.done();
+ });
+ }, "createElementNS test in " + desc + ": " + t.map(format_value))
+}
+
+tests.forEach(function(t, i) {
+ runTest(t, i, "HTML document")
+ runTest(t, i, "XML document")
+ runTest(t, i, "XHTML document")
+})
+
+
+test(function() {
+ var HTMLNS = "http://www.w3.org/1999/xhtml";
+ var element = document.createElementNS(HTMLNS, "span");
+ assert_equals(element.namespaceURI, HTMLNS);
+ assert_equals(element.prefix, null);
+ assert_equals(element.localName, "span");
+ assert_equals(element.tagName, "SPAN");
+ assert_true(element instanceof Node, "Should be a Node");
+ assert_true(element instanceof Element, "Should be an Element");
+ assert_true(element instanceof HTMLElement, "Should be an HTMLElement");
+ assert_true(element instanceof HTMLSpanElement, "Should be an HTMLSpanElement");
+}, "Lower-case HTML element without a prefix");
+
+test(function() {
+ var HTMLNS = "http://www.w3.org/1999/xhtml";
+ var element = document.createElementNS(HTMLNS, "html:span");
+ assert_equals(element.namespaceURI, HTMLNS);
+ assert_equals(element.prefix, "html");
+ assert_equals(element.localName, "span");
+ assert_equals(element.tagName, "HTML:SPAN");
+ assert_true(element instanceof Node, "Should be a Node");
+ assert_true(element instanceof Element, "Should be an Element");
+ assert_true(element instanceof HTMLElement, "Should be an HTMLElement");
+ assert_true(element instanceof HTMLSpanElement, "Should be an HTMLSpanElement");
+}, "Lower-case HTML element with a prefix");
+
+test(function() {
+ var element = document.createElementNS("test", "span");
+ assert_equals(element.namespaceURI, "test");
+ assert_equals(element.prefix, null);
+ assert_equals(element.localName, "span");
+ assert_equals(element.tagName, "span");
+ assert_true(element instanceof Node, "Should be a Node");
+ assert_true(element instanceof Element, "Should be an Element");
+ assert_false(element instanceof HTMLElement, "Should not be an HTMLElement");
+ assert_false(element instanceof HTMLSpanElement, "Should not be an HTMLSpanElement");
+}, "Lower-case non-HTML element without a prefix");
+
+test(function() {
+ var element = document.createElementNS("test", "html:span");
+ assert_equals(element.namespaceURI, "test");
+ assert_equals(element.prefix, "html");
+ assert_equals(element.localName, "span");
+ assert_equals(element.tagName, "html:span");
+ assert_true(element instanceof Node, "Should be a Node");
+ assert_true(element instanceof Element, "Should be an Element");
+ assert_false(element instanceof HTMLElement, "Should not be an HTMLElement");
+ assert_false(element instanceof HTMLSpanElement, "Should not be an HTMLSpanElement");
+}, "Lower-case non-HTML element with a prefix");
+
+test(function() {
+ var HTMLNS = "http://www.w3.org/1999/xhtml";
+ var element = document.createElementNS(HTMLNS, "SPAN");
+ assert_equals(element.namespaceURI, HTMLNS);
+ assert_equals(element.prefix, null);
+ assert_equals(element.localName, "SPAN");
+ assert_equals(element.tagName, "SPAN");
+ assert_true(element instanceof Node, "Should be a Node");
+ assert_true(element instanceof Element, "Should be an Element");
+ assert_true(element instanceof HTMLElement, "Should be an HTMLElement");
+ assert_true(element instanceof HTMLUnknownElement, "Should be an HTMLUnknownElement");
+ assert_false(element instanceof HTMLSpanElement, "Should not be an HTMLSpanElement");
+}, "Upper-case HTML element without a prefix");
+
+test(function() {
+ var HTMLNS = "http://www.w3.org/1999/xhtml";
+ var element = document.createElementNS(HTMLNS, "html:SPAN");
+ assert_equals(element.namespaceURI, HTMLNS);
+ assert_equals(element.prefix, "html");
+ assert_equals(element.localName, "SPAN");
+ assert_equals(element.tagName, "HTML:SPAN");
+ assert_true(element instanceof Node, "Should be a Node");
+ assert_true(element instanceof Element, "Should be an Element");
+ assert_true(element instanceof HTMLElement, "Should be an HTMLElement");
+ assert_false(element instanceof HTMLSpanElement, "Should not be an HTMLSpanElement");
+}, "Upper-case HTML element with a prefix");
+
+test(function() {
+ var element = document.createElementNS("test", "SPAN");
+ assert_equals(element.namespaceURI, "test");
+ assert_equals(element.prefix, null);
+ assert_equals(element.localName, "SPAN");
+ assert_equals(element.tagName, "SPAN");
+ assert_true(element instanceof Node, "Should be a Node");
+ assert_true(element instanceof Element, "Should be an Element");
+ assert_false(element instanceof HTMLElement, "Should not be an HTMLElement");
+ assert_false(element instanceof HTMLSpanElement, "Should not be an HTMLSpanElement");
+}, "Upper-case non-HTML element without a prefix");
+
+test(function() {
+ var element = document.createElementNS("test", "html:SPAN");
+ assert_equals(element.namespaceURI, "test");
+ assert_equals(element.prefix, "html");
+ assert_equals(element.localName, "SPAN");
+ assert_equals(element.tagName, "html:SPAN");
+ assert_true(element instanceof Node, "Should be a Node");
+ assert_true(element instanceof Element, "Should be an Element");
+ assert_false(element instanceof HTMLElement, "Should not be an HTMLElement");
+ assert_false(element instanceof HTMLSpanElement, "Should not be an HTMLSpanElement");
+}, "Upper-case non-HTML element with a prefix");
+
+test(function() {
+ var element = document.createElementNS(null, "span");
+ assert_equals(element.namespaceURI, null);
+ assert_equals(element.prefix, null);
+ assert_equals(element.localName, "span");
+ assert_equals(element.tagName, "span");
+ assert_true(element instanceof Node, "Should be a Node");
+ assert_true(element instanceof Element, "Should be an Element");
+ assert_false(element instanceof HTMLElement, "Should not be an HTMLElement");
+ assert_false(element instanceof HTMLSpanElement, "Should not be an HTMLSpanElement");
+}, "null namespace");
+
+test(function() {
+ var element = document.createElementNS(undefined, "span");
+ assert_equals(element.namespaceURI, null);
+ assert_equals(element.prefix, null);
+ assert_equals(element.localName, "span");
+ assert_equals(element.tagName, "span");
+ assert_true(element instanceof Node, "Should be a Node");
+ assert_true(element instanceof Element, "Should be an Element");
+ assert_false(element instanceof HTMLElement, "Should not be an HTMLElement");
+ assert_false(element instanceof HTMLSpanElement, "Should not be an HTMLSpanElement");
+}, "undefined namespace");
+
+test(function() {
+ var element = document.createElementNS("", "span");
+ assert_equals(element.namespaceURI, null);
+ assert_equals(element.prefix, null);
+ assert_equals(element.localName, "span");
+ assert_equals(element.tagName, "span");
+ assert_true(element instanceof Node, "Should be a Node");
+ assert_true(element instanceof Element, "Should be an Element");
+ assert_false(element instanceof HTMLElement, "Should not be an HTMLElement");
+ assert_false(element instanceof HTMLSpanElement, "Should not be an HTMLSpanElement");
+}, "empty string namespace");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createElementNS.js b/testing/web-platform/tests/dom/nodes/Document-createElementNS.js
new file mode 100644
index 0000000000..2cf2948563
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createElementNS.js
@@ -0,0 +1,189 @@
+var createElementNS_tests = [
+ /* Arrays with three elements:
+ * the namespace argument
+ * the qualifiedName argument
+ * the expected exception, or null if none
+ */
+ [null, null, null],
+ [null, undefined, null],
+ [null, "foo", null],
+ [null, "1foo", "INVALID_CHARACTER_ERR"],
+ [null, "f1oo", null],
+ [null, "foo1", null],
+ [null, "\u0BC6foo", null],
+ [null, "\u037Efoo", "INVALID_CHARACTER_ERR"],
+ [null, "}foo", "INVALID_CHARACTER_ERR"],
+ [null, "f}oo", "INVALID_CHARACTER_ERR"],
+ [null, "foo}", "INVALID_CHARACTER_ERR"],
+ [null, "\uFFFFfoo", "INVALID_CHARACTER_ERR"],
+ [null, "f\uFFFFoo", "INVALID_CHARACTER_ERR"],
+ [null, "foo\uFFFF", "INVALID_CHARACTER_ERR"],
+ [null, "<foo", "INVALID_CHARACTER_ERR"],
+ [null, "foo>", "INVALID_CHARACTER_ERR"],
+ [null, "<foo>", "INVALID_CHARACTER_ERR"],
+ [null, "f<oo", "INVALID_CHARACTER_ERR"],
+ [null, "^^", "INVALID_CHARACTER_ERR"],
+ [null, "fo o", "INVALID_CHARACTER_ERR"],
+ [null, "-foo", "INVALID_CHARACTER_ERR"],
+ [null, ".foo", "INVALID_CHARACTER_ERR"],
+ [null, ":foo", "INVALID_CHARACTER_ERR"],
+ [null, "f:oo", "NAMESPACE_ERR"],
+ [null, "foo:", "INVALID_CHARACTER_ERR"],
+ [null, "f:o:o", "INVALID_CHARACTER_ERR"],
+ [null, ":", "INVALID_CHARACTER_ERR"],
+ [null, "xml", null],
+ [null, "xmlns", "NAMESPACE_ERR"],
+ [null, "xmlfoo", null],
+ [null, "xml:foo", "NAMESPACE_ERR"],
+ [null, "xmlns:foo", "NAMESPACE_ERR"],
+ [null, "xmlfoo:bar", "NAMESPACE_ERR"],
+ [null, "null:xml", "NAMESPACE_ERR"],
+ ["", null, null],
+ ["", ":foo", "INVALID_CHARACTER_ERR"],
+ ["", "f:oo", "NAMESPACE_ERR"],
+ ["", "foo:", "INVALID_CHARACTER_ERR"],
+ [undefined, null, null],
+ [undefined, undefined, null],
+ [undefined, "foo", null],
+ [undefined, "1foo", "INVALID_CHARACTER_ERR"],
+ [undefined, "f1oo", null],
+ [undefined, "foo1", null],
+ [undefined, ":foo", "INVALID_CHARACTER_ERR"],
+ [undefined, "f:oo", "NAMESPACE_ERR"],
+ [undefined, "foo:", "INVALID_CHARACTER_ERR"],
+ [undefined, "f::oo", "INVALID_CHARACTER_ERR"],
+ [undefined, "xml", null],
+ [undefined, "xmlns", "NAMESPACE_ERR"],
+ [undefined, "xmlfoo", null],
+ [undefined, "xml:foo", "NAMESPACE_ERR"],
+ [undefined, "xmlns:foo", "NAMESPACE_ERR"],
+ [undefined, "xmlfoo:bar", "NAMESPACE_ERR"],
+ ["http://example.com/", "foo", null],
+ ["http://example.com/", "1foo", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "<foo>", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "fo<o", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "-foo", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", ".foo", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "f1oo", null],
+ ["http://example.com/", "foo1", null],
+ ["http://example.com/", ":foo", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "f:oo", null],
+ ["http://example.com/", "f:o:o", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "foo:", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "f::oo", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "a:0", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "0:a", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "a:_", null],
+ ["http://example.com/", "a:\u0BC6", null],
+ ["http://example.com/", "a:\u037E", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "a:\u0300", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "\u0BC6:a", null],
+ ["http://example.com/", "\u0300:a", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "\u037E:a", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "a:a\u0BC6", null],
+ ["http://example.com/", "a\u0BC6:a", null],
+ ["http://example.com/", "xml:test", "NAMESPACE_ERR"],
+ ["http://example.com/", "xmlns:test", "NAMESPACE_ERR"],
+ ["http://example.com/", "test:xmlns", null],
+ ["http://example.com/", "xmlns", "NAMESPACE_ERR"],
+ ["http://example.com/", "_:_", null],
+ ["http://example.com/", "_:h0", null],
+ ["http://example.com/", "_:test", null],
+ ["http://example.com/", "l_:_", null],
+ ["http://example.com/", "ns:_0", null],
+ ["http://example.com/", "ns:a0", null],
+ ["http://example.com/", "ns0:test", null],
+ ["http://example.com/", "a.b:c", null],
+ ["http://example.com/", "a-b:c", null],
+ ["http://example.com/", "xml", null],
+ ["http://example.com/", "XMLNS", null],
+ ["http://example.com/", "xmlfoo", null],
+ ["http://example.com/", "xml:foo", "NAMESPACE_ERR"],
+ ["http://example.com/", "XML:foo", null],
+ ["http://example.com/", "xmlns:foo", "NAMESPACE_ERR"],
+ ["http://example.com/", "XMLNS:foo", null],
+ ["http://example.com/", "xmlfoo:bar", null],
+ ["http://example.com/", "prefix::local", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:{", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:}", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:~", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:'", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:!", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:@", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:#", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:$", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:%", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:^", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:&", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:*", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:(", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:)", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:+", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:=", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:[", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:]", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:\\", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:/", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:;", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:`", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:<", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:>", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:,", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:a ", "INVALID_CHARACTER_ERR"],
+ ["http://example.com/", "namespaceURI:\"", "INVALID_CHARACTER_ERR"],
+ ["/", "foo", null],
+ ["/", "1foo", "INVALID_CHARACTER_ERR"],
+ ["/", "f1oo", null],
+ ["/", "foo1", null],
+ ["/", ":foo", "INVALID_CHARACTER_ERR"],
+ ["/", "f:oo", null],
+ ["/", "foo:", "INVALID_CHARACTER_ERR"],
+ ["/", "xml", null],
+ ["/", "xmlns", "NAMESPACE_ERR"],
+ ["/", "xmlfoo", null],
+ ["/", "xml:foo", "NAMESPACE_ERR"],
+ ["/", "xmlns:foo", "NAMESPACE_ERR"],
+ ["/", "xmlfoo:bar", null],
+ ["http://www.w3.org/XML/1998/namespace", "foo", null],
+ ["http://www.w3.org/XML/1998/namespace", "1foo", "INVALID_CHARACTER_ERR"],
+ ["http://www.w3.org/XML/1998/namespace", "f1oo", null],
+ ["http://www.w3.org/XML/1998/namespace", "foo1", null],
+ ["http://www.w3.org/XML/1998/namespace", ":foo", "INVALID_CHARACTER_ERR"],
+ ["http://www.w3.org/XML/1998/namespace", "f:oo", null],
+ ["http://www.w3.org/XML/1998/namespace", "foo:", "INVALID_CHARACTER_ERR"],
+ ["http://www.w3.org/XML/1998/namespace", "xml", null],
+ ["http://www.w3.org/XML/1998/namespace", "xmlns", "NAMESPACE_ERR"],
+ ["http://www.w3.org/XML/1998/namespace", "xmlfoo", null],
+ ["http://www.w3.org/XML/1998/namespace", "xml:foo", null],
+ ["http://www.w3.org/XML/1998/namespace", "xmlns:foo", "NAMESPACE_ERR"],
+ ["http://www.w3.org/XML/1998/namespace", "xmlfoo:bar", null],
+ ["http://www.w3.org/XML/1998/namespaces", "xml:foo", "NAMESPACE_ERR"],
+ ["http://www.w3.org/xml/1998/namespace", "xml:foo", "NAMESPACE_ERR"],
+ ["http://www.w3.org/2000/xmlns/", "foo", "NAMESPACE_ERR"],
+ ["http://www.w3.org/2000/xmlns/", "1foo", "INVALID_CHARACTER_ERR"],
+ ["http://www.w3.org/2000/xmlns/", "f1oo", "NAMESPACE_ERR"],
+ ["http://www.w3.org/2000/xmlns/", "foo1", "NAMESPACE_ERR"],
+ ["http://www.w3.org/2000/xmlns/", ":foo", "INVALID_CHARACTER_ERR"],
+ ["http://www.w3.org/2000/xmlns/", "f:oo", "NAMESPACE_ERR"],
+ ["http://www.w3.org/2000/xmlns/", "foo:", "INVALID_CHARACTER_ERR"],
+ ["http://www.w3.org/2000/xmlns/", "xml", "NAMESPACE_ERR"],
+ ["http://www.w3.org/2000/xmlns/", "xmlns", null],
+ ["http://www.w3.org/2000/xmlns/", "xmlfoo", "NAMESPACE_ERR"],
+ ["http://www.w3.org/2000/xmlns/", "xml:foo", "NAMESPACE_ERR"],
+ ["http://www.w3.org/2000/xmlns/", "xmlns:foo", null],
+ ["http://www.w3.org/2000/xmlns/", "xmlfoo:bar", "NAMESPACE_ERR"],
+ ["http://www.w3.org/2000/xmlns/", "foo:xmlns", "NAMESPACE_ERR"],
+ ["foo:", "foo", null],
+ ["foo:", "1foo", "INVALID_CHARACTER_ERR"],
+ ["foo:", "f1oo", null],
+ ["foo:", "foo1", null],
+ ["foo:", ":foo", "INVALID_CHARACTER_ERR"],
+ ["foo:", "f:oo", null],
+ ["foo:", "foo:", "INVALID_CHARACTER_ERR"],
+ ["foo:", "xml", null],
+ ["foo:", "xmlns", "NAMESPACE_ERR"],
+ ["foo:", "xmlfoo", null],
+ ["foo:", "xml:foo", "NAMESPACE_ERR"],
+ ["foo:", "xmlns:foo", "NAMESPACE_ERR"],
+ ["foo:", "xmlfoo:bar", null],
+]
diff --git a/testing/web-platform/tests/dom/nodes/Document-createEvent-touchevent.window.js b/testing/web-platform/tests/dom/nodes/Document-createEvent-touchevent.window.js
new file mode 100644
index 0000000000..6523ac5a02
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createEvent-touchevent.window.js
@@ -0,0 +1,12 @@
+for (const variant of ['TouchEvent', 'touchevent', 'TOUCHEVENT']) {
+ test(() => {
+ if (!('ontouchstart' in document)) {
+ assert_throws_dom("NOT_SUPPORTED_ERR", () => {
+ document.createEvent(variant);
+ });
+ } else {
+ document.createEvent(variant);
+ // The interface and other details of the event is tested in Document-createEvent.https.html
+ }
+ }, `document.createEvent('${variant}') should throw if 'expose legacy touch event APIs' is false`);
+}
diff --git a/testing/web-platform/tests/dom/nodes/Document-createEvent.https.html b/testing/web-platform/tests/dom/nodes/Document-createEvent.https.html
new file mode 100644
index 0000000000..4781d54e8e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createEvent.https.html
@@ -0,0 +1,170 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Document.createEvent</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-createevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="Document-createEvent.js"></script>
+<div id="log"></div>
+<script>
+function supportsTouchEvents(isTouchEvent) {
+ if (isTouchEvent) {
+ assert_implements_optional('ontouchstart' in document, "'expose legacy touch event APIs'");
+ }
+}
+function testAlias(arg, iface, isTouchEvent) {
+ var ev;
+ test(function() {
+ supportsTouchEvents(isTouchEvent);
+ ev = document.createEvent(arg);
+ assert_equals(Object.getPrototypeOf(ev), window[iface].prototype);
+ }, arg + " should be an alias for " + iface + ".");
+ test(function() {
+ supportsTouchEvents(isTouchEvent);
+ assert_equals(ev.type, "",
+ "type should be initialized to the empty string");
+ assert_equals(ev.target, null,
+ "target should be initialized to null");
+ assert_equals(ev.currentTarget, null,
+ "currentTarget should be initialized to null");
+ assert_equals(ev.eventPhase, 0,
+ "eventPhase should be initialized to NONE (0)");
+ assert_equals(ev.bubbles, false,
+ "bubbles should be initialized to false");
+ assert_equals(ev.cancelable, false,
+ "cancelable should be initialized to false");
+ assert_equals(ev.defaultPrevented, false,
+ "defaultPrevented should be initialized to false");
+ assert_equals(ev.isTrusted, false,
+ "isTrusted should be initialized to false");
+ }, "createEvent('" + arg + "') should be initialized correctly.");
+}
+aliases.TouchEvent = 'TouchEvent';
+for (var alias in aliases) {
+ var isTouchEvent = alias === 'TouchEvent';
+ var iface = aliases[alias];
+ testAlias(alias, iface, isTouchEvent);
+ testAlias(alias.toLowerCase(), iface, isTouchEvent);
+ testAlias(alias.toUpperCase(), iface, isTouchEvent);
+
+ if (alias[alias.length - 1] != "s") {
+ var plural = alias + "s";
+ if (!(plural in aliases)) {
+ test(function () {
+ assert_throws_dom("NOT_SUPPORTED_ERR", function () {
+ var evt = document.createEvent(plural);
+ });
+ }, 'Should throw NOT_SUPPORTED_ERR for pluralized legacy event interface "' + plural + '"');
+ }
+ }
+}
+
+test(function() {
+ assert_throws_dom("NOT_SUPPORTED_ERR", function() {
+ var evt = document.createEvent("foo");
+ });
+ assert_throws_dom("NOT_SUPPORTED_ERR", function() {
+ // 'LATIN CAPITAL LETTER I WITH DOT ABOVE' (U+0130)
+ var evt = document.createEvent("U\u0130Event");
+ });
+ assert_throws_dom("NOT_SUPPORTED_ERR", function() {
+ // 'LATIN SMALL LETTER DOTLESS I' (U+0131)
+ var evt = document.createEvent("U\u0131Event");
+ });
+}, "Should throw NOT_SUPPORTED_ERR for unrecognized arguments");
+
+/*
+ * The following are event interfaces which do actually exist, but must still
+ * throw since they're absent from the table in the spec for
+ * document.createEvent(). This list is not exhaustive, but includes all
+ * interfaces that it is known some UA does or did not throw for.
+ */
+var someNonCreateableEvents = [
+ "AnimationEvent",
+ "AnimationPlaybackEvent",
+ "AnimationPlayerEvent",
+ "ApplicationCacheErrorEvent",
+ "AudioProcessingEvent",
+ "AutocompleteErrorEvent",
+ "BeforeInstallPromptEvent",
+ "BlobEvent",
+ "ClipboardEvent",
+ "CloseEvent",
+ "CommandEvent",
+ "DataContainerEvent",
+ "ErrorEvent",
+ "ExtendableEvent",
+ "ExtendableMessageEvent",
+ "FetchEvent",
+ "FontFaceSetLoadEvent",
+ "GamepadEvent",
+ "GeofencingEvent",
+ "IDBVersionChangeEvent",
+ "InstallEvent",
+ "KeyEvent",
+ "MIDIConnectionEvent",
+ "MIDIMessageEvent",
+ "MediaEncryptedEvent",
+ "MediaKeyEvent",
+ "MediaKeyMessageEvent",
+ "MediaQueryListEvent",
+ "MediaStreamEvent",
+ "MediaStreamTrackEvent",
+ "MouseScrollEvent",
+ "MutationEvent",
+ "NotificationEvent",
+ "NotifyPaintEvent",
+ "OfflineAudioCompletionEvent",
+ "OrientationEvent",
+ "PageTransition", // Yes, with no "Event"
+ "PageTransitionEvent",
+ "PointerEvent",
+ "PopStateEvent",
+ "PopUpEvent",
+ "PresentationConnectionAvailableEvent",
+ "PresentationConnectionCloseEvent",
+ "ProgressEvent",
+ "PromiseRejectionEvent",
+ "PushEvent",
+ "RTCDTMFToneChangeEvent",
+ "RTCDataChannelEvent",
+ "RTCIceCandidateEvent",
+ "RelatedEvent",
+ "ResourceProgressEvent",
+ "SVGEvent",
+ "SVGZoomEvent",
+ "ScrollAreaEvent",
+ "SecurityPolicyViolationEvent",
+ "ServicePortConnectEvent",
+ "ServiceWorkerMessageEvent",
+ "SimpleGestureEvent",
+ "SpeechRecognitionError",
+ "SpeechRecognitionEvent",
+ "SpeechSynthesisEvent",
+ "SyncEvent",
+ "TimeEvent",
+ "TrackEvent",
+ "TransitionEvent",
+ "WebGLContextEvent",
+ "WebKitAnimationEvent",
+ "WebKitTransitionEvent",
+ "WheelEvent",
+ "XULCommandEvent",
+];
+someNonCreateableEvents.forEach(function (eventInterface) {
+ test(function () {
+ assert_throws_dom("NOT_SUPPORTED_ERR", function () {
+ var evt = document.createEvent(eventInterface);
+ });
+ }, 'Should throw NOT_SUPPORTED_ERR for non-legacy event interface "' + eventInterface + '"');
+
+ // SVGEvents is allowed, other plurals are not
+ if (eventInterface !== "SVGEvent") {
+ test(function () {
+ assert_throws_dom("NOT_SUPPORTED_ERR", function () {
+ var evt = document.createEvent(eventInterface + "s");
+ });
+ }, 'Should throw NOT_SUPPORTED_ERR for pluralized non-legacy event interface "' + eventInterface + 's"');
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createEvent.js b/testing/web-platform/tests/dom/nodes/Document-createEvent.js
new file mode 100644
index 0000000000..57e8e966f8
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createEvent.js
@@ -0,0 +1,22 @@
+var aliases = {
+ "BeforeUnloadEvent": "BeforeUnloadEvent",
+ "CompositionEvent": "CompositionEvent",
+ "CustomEvent": "CustomEvent",
+ "DeviceMotionEvent": "DeviceMotionEvent",
+ "DeviceOrientationEvent": "DeviceOrientationEvent",
+ "DragEvent": "DragEvent",
+ "Event": "Event",
+ "Events": "Event",
+ "FocusEvent": "FocusEvent",
+ "HashChangeEvent": "HashChangeEvent",
+ "HTMLEvents": "Event",
+ "KeyboardEvent": "KeyboardEvent",
+ "MessageEvent": "MessageEvent",
+ "MouseEvent": "MouseEvent",
+ "MouseEvents": "MouseEvent",
+ "StorageEvent": "StorageEvent",
+ "SVGEvents": "Event",
+ "TextEvent": "CompositionEvent",
+ "UIEvent": "UIEvent",
+ "UIEvents": "UIEvent",
+};
diff --git a/testing/web-platform/tests/dom/nodes/Document-createProcessingInstruction-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Document-createProcessingInstruction-xhtml.xhtml
new file mode 100644
index 0000000000..d06f70fdcb
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createProcessingInstruction-xhtml.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Document.createProcessingInstruction in XML documents</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction"/>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-processinginstruction-target"/>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-characterdata-data"/>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-node-ownerdocument"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"/>
+<script src="Document-createProcessingInstruction.js"/>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createProcessingInstruction.html b/testing/web-platform/tests/dom/nodes/Document-createProcessingInstruction.html
new file mode 100644
index 0000000000..c57a792fac
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createProcessingInstruction.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Document.createProcessingInstruction in HTML documents</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-processinginstruction-target">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-data">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-ownerdocument">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script src="Document-createProcessingInstruction.js"></script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createProcessingInstruction.js b/testing/web-platform/tests/dom/nodes/Document-createProcessingInstruction.js
new file mode 100644
index 0000000000..d6cc3725f0
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createProcessingInstruction.js
@@ -0,0 +1,39 @@
+test(function() {
+ var invalid = [
+ ["A", "?>"],
+ ["\u00B7A", "x"],
+ ["\u00D7A", "x"],
+ ["A\u00D7", "x"],
+ ["\\A", "x"],
+ ["\f", "x"],
+ [0, "x"],
+ ["0", "x"]
+ ],
+ valid = [
+ ["xml:fail", "x"],
+ ["A\u00B7A", "x"],
+ ["a0", "x"]
+ ]
+
+ for (var i = 0, il = invalid.length; i < il; i++) {
+ test(function() {
+ assert_throws_dom("INVALID_CHARACTER_ERR", function() {
+ document.createProcessingInstruction(invalid[i][0], invalid[i][1])
+ })
+ }, "Should throw an INVALID_CHARACTER_ERR for target " +
+ format_value(invalid[i][0]) + " and data " +
+ format_value(invalid[i][1]) + ".")
+ }
+ for (var i = 0, il = valid.length; i < il; ++i) {
+ test(function() {
+ var pi = document.createProcessingInstruction(valid[i][0], valid[i][1]);
+ assert_equals(pi.target, valid[i][0]);
+ assert_equals(pi.data, valid[i][1]);
+ assert_equals(pi.ownerDocument, document);
+ assert_true(pi instanceof ProcessingInstruction);
+ assert_true(pi instanceof Node);
+ }, "Should get a ProcessingInstruction for target " +
+ format_value(valid[i][0]) + " and data " +
+ format_value(valid[i][1]) + ".")
+ }
+})
diff --git a/testing/web-platform/tests/dom/nodes/Document-createTextNode.html b/testing/web-platform/tests/dom/nodes/Document-createTextNode.html
new file mode 100644
index 0000000000..ccc1b1b77f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createTextNode.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Document.createTextNode</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-createtextnode">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-ownerdocument">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-data">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-nodevalue">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-textcontent">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-characterdata-length">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-nodetype">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-haschildnodes">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-childnodes">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-firstchild">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-lastchild">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="Document-createComment-createTextNode.js"></script>
+<div id="log"></div>
+<script>
+test_create("createTextNode", Text, 3, "#text");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-createTreeWalker.html b/testing/web-platform/tests/dom/nodes/Document-createTreeWalker.html
new file mode 100644
index 0000000000..1e8420d841
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-createTreeWalker.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Document.createTreeWalker</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ assert_throws_js(TypeError, function() {
+ document.createTreeWalker();
+ });
+}, "Required arguments to createTreeWalker should be required.");
+test(function() {
+ var tw = document.createTreeWalker(document.body);
+ assert_equals(tw.root, document.body);
+ assert_equals(tw.currentNode, document.body);
+ assert_equals(tw.whatToShow, 0xFFFFFFFF);
+ assert_equals(tw.filter, null);
+}, "Optional arguments to createTreeWalker should be optional (1 passed).");
+test(function() {
+ var tw = document.createTreeWalker(document.body, 42);
+ assert_equals(tw.root, document.body);
+ assert_equals(tw.currentNode, document.body);
+ assert_equals(tw.whatToShow, 42);
+ assert_equals(tw.filter, null);
+}, "Optional arguments to createTreeWalker should be optional (2 passed).");
+test(function() {
+ var tw = document.createTreeWalker(document.body, 42, null);
+ assert_equals(tw.root, document.body);
+ assert_equals(tw.currentNode, document.body);
+ assert_equals(tw.whatToShow, 42);
+ assert_equals(tw.filter, null);
+}, "Optional arguments to createTreeWalker should be optional (3 passed, null).");
+test(function() {
+ var fn = function() {};
+ var tw = document.createTreeWalker(document.body, 42, fn);
+ assert_equals(tw.root, document.body);
+ assert_equals(tw.currentNode, document.body);
+ assert_equals(tw.whatToShow, 42);
+ assert_equals(tw.filter, fn);
+}, "Optional arguments to createTreeWalker should be optional (3 passed, function).");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-doctype.html b/testing/web-platform/tests/dom/nodes/Document-doctype.html
new file mode 100644
index 0000000000..75bfd8506d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-doctype.html
@@ -0,0 +1,21 @@
+<!-- comment -->
+<!doctype html>
+<meta charset=utf-8>
+<title>Document.doctype</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-doctype">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_true(document.doctype instanceof DocumentType,
+ "Doctype should be a DocumentType");
+ assert_equals(document.doctype, document.childNodes[1]);
+}, "Window document with doctype");
+
+test(function() {
+ var newdoc = new Document();
+ newdoc.appendChild(newdoc.createElement("html"));
+ assert_equals(newdoc.doctype, null);
+}, "new Document()");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-getElementById.html b/testing/web-platform/tests/dom/nodes/Document-getElementById.html
new file mode 100644
index 0000000000..1dec4c085b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-getElementById.html
@@ -0,0 +1,350 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Document.getElementById</title>
+<link rel="author" title="Tetsuharu OHZEKI" href="mailto:saneyuki.snyk@gmail.com">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-getelementbyid">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+ <div id="log"></div>
+
+ <!-- test 0 -->
+ <div id=""></div>
+
+ <!-- test 1 -->
+ <div id="test1"></div>
+
+ <!-- test 5 -->
+ <div id="test5" data-name="1st">
+ <p id="test5" data-name="2nd">P</p>
+ <input id="test5" type="submit" value="Submit" data-name="3rd">
+ </div>
+
+ <!-- test 15 -->
+ <div id="outer">
+ <div id="middle">
+ <div id="inner"></div>
+ </div>
+ </div>
+
+<script>
+ var gBody = document.getElementsByTagName("body")[0];
+
+ test(function() {
+ assert_equals(document.getElementById(""), null);
+ }, "Calling document.getElementById with an empty string argument.");
+
+ test(function() {
+ var element = document.createElement("div");
+ element.setAttribute("id", "null");
+ document.body.appendChild(element);
+ this.add_cleanup(function() { document.body.removeChild(element) });
+ assert_equals(document.getElementById(null), element);
+ }, "Calling document.getElementById with a null argument.");
+
+ test(function() {
+ var element = document.createElement("div");
+ element.setAttribute("id", "undefined");
+ document.body.appendChild(element);
+ this.add_cleanup(function() { document.body.removeChild(element) });
+ assert_equals(document.getElementById(undefined), element);
+ }, "Calling document.getElementById with an undefined argument.");
+
+
+ test(function() {
+ var bar = document.getElementById("test1");
+ assert_not_equals(bar, null, "should not be null");
+ assert_equals(bar.tagName, "DIV", "should have expected tag name.");
+ assert_true(bar instanceof HTMLDivElement, "should be a valid Element instance");
+ }, "on static page");
+
+
+ test(function() {
+ var TEST_ID = "test2";
+
+ var test = document.createElement("div");
+ test.setAttribute("id", TEST_ID);
+ gBody.appendChild(test);
+
+ // test: appended element
+ var result = document.getElementById(TEST_ID);
+ assert_not_equals(result, null, "should not be null.");
+ assert_equals(result.tagName, "DIV", "should have appended element's tag name");
+ assert_true(result instanceof HTMLDivElement, "should be a valid Element instance");
+
+ // test: removed element
+ gBody.removeChild(test);
+ var removed = document.getElementById(TEST_ID);
+ // `document.getElementById()` returns `null` if there is none.
+ // https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid
+ assert_equals(removed, null, "should not get removed element.");
+ }, "Document.getElementById with a script-inserted element");
+
+
+ test(function() {
+ // setup fixtures.
+ var TEST_ID = "test3";
+ var test = document.createElement("div");
+ test.setAttribute("id", TEST_ID);
+ gBody.appendChild(test);
+
+ // update id
+ var UPDATED_ID = "test3-updated";
+ test.setAttribute("id", UPDATED_ID);
+ var e = document.getElementById(UPDATED_ID);
+ assert_equals(e, test, "should get the element with id.");
+
+ var old = document.getElementById(TEST_ID);
+ assert_equals(old, null, "shouldn't get the element by the old id.");
+
+ // remove id.
+ test.removeAttribute("id");
+ var e2 = document.getElementById(UPDATED_ID);
+ assert_equals(e2, null, "should return null when the passed id is none in document.");
+ }, "update `id` attribute via setAttribute/removeAttribute");
+
+
+ test(function() {
+ var TEST_ID = "test4-should-not-exist";
+
+ var e = document.createElement('div');
+ e.setAttribute("id", TEST_ID);
+
+ assert_equals(document.getElementById(TEST_ID), null, "should be null");
+ document.body.appendChild(e);
+ assert_equals(document.getElementById(TEST_ID), e, "should be the appended element");
+ }, "Ensure that the id attribute only affects elements present in a document");
+
+
+ test(function() {
+ // the method should return the 1st element.
+ var TEST_ID = "test5";
+ var target = document.getElementById(TEST_ID);
+ assert_not_equals(target, null, "should not be null");
+ assert_equals(target.getAttribute("data-name"), "1st", "should return the 1st");
+
+ // even if after the new element was appended.
+ var element4 = document.createElement("div");
+ element4.setAttribute("id", TEST_ID);
+ element4.setAttribute("data-name", "4th");
+ gBody.appendChild(element4);
+ var target2 = document.getElementById(TEST_ID);
+ assert_not_equals(target2, null, "should not be null");
+ assert_equals(target2.getAttribute("data-name"), "1st", "should be the 1st");
+
+ // should return the next element after removed the subtree including the 1st element.
+ target2.parentNode.removeChild(target2);
+ var target3 = document.getElementById(TEST_ID);
+ assert_not_equals(target3, null, "should not be null");
+ assert_equals(target3.getAttribute("data-name"), "4th", "should be the 4th");
+ }, "in tree order, within the context object's tree");
+
+
+ test(function() {
+ var TEST_ID = "test6";
+ var s = document.createElement("div");
+ s.setAttribute("id", TEST_ID);
+ // append to Element, not Document.
+ document.createElement("div").appendChild(s);
+
+ assert_equals(document.getElementById(TEST_ID), null, "should be null");
+ }, "Modern browsers optimize this method with using internal id cache. " +
+ "This test checks that their optimization should effect only append to `Document`, not append to `Node`.");
+
+
+ test(function() {
+ var TEST_ID = "test7"
+ var element = document.createElement("div");
+ element.setAttribute("id", TEST_ID);
+ gBody.appendChild(element);
+
+ var target = document.getElementById(TEST_ID);
+ assert_equals(target, element, "should return the element before changing the value");
+
+ element.attributes[0].value = TEST_ID + "-updated";
+ var target2 = document.getElementById(TEST_ID);
+ assert_equals(target2, null, "should return null after updated id via Attr.value");
+ var target3 = document.getElementById(TEST_ID + "-updated");
+ assert_equals(target3, element, "should be equal to the updated element.");
+ }, "changing attribute's value via `Attr` gotten from `Element.attribute`.");
+
+
+ test(function() {
+ var TEST_ID = "test8";
+
+ // setup fixture
+ var element = document.createElement("div");
+ element.setAttribute("id", TEST_ID + "-fixture");
+ gBody.appendChild(element);
+
+ // add id-ed element with using innerHTML
+ element.innerHTML = "<div id='"+ TEST_ID +"'></div>";
+ var test = document.getElementById(TEST_ID);
+ assert_equals(test, element.firstChild, "should not be null");
+ assert_equals(test.tagName, "DIV", "should have expected tag name.");
+ assert_true(test instanceof HTMLDivElement, "should be a valid Element instance");
+ }, "add id attribute via innerHTML");
+
+
+ test(function() {
+ var TEST_ID = "test9";
+
+ // add fixture
+ var fixture = document.createElement("div");
+ fixture.setAttribute("id", TEST_ID + "-fixture");
+ gBody.appendChild(fixture);
+
+ var element = document.createElement("div");
+ element.setAttribute("id", TEST_ID);
+ fixture.appendChild(element);
+
+ // check 'getElementById' should get the 'element'
+ assert_equals(document.getElementById(TEST_ID), element, "should not be null");
+
+ // remove id-ed element with using innerHTML (clear 'element')
+ fixture.innerHTML = "";
+ var test = document.getElementById(TEST_ID);
+ assert_equals(test, null, "should be null.");
+ }, "remove id attribute via innerHTML");
+
+
+ test(function() {
+ var TEST_ID = "test10";
+
+ // setup fixture
+ var element = document.createElement("div");
+ element.setAttribute("id", TEST_ID + "-fixture");
+ gBody.appendChild(element);
+
+ // add id-ed element with using outerHTML
+ element.outerHTML = "<div id='"+ TEST_ID +"'></div>";
+ var test = document.getElementById(TEST_ID);
+ assert_not_equals(test, null, "should not be null");
+ assert_equals(test.tagName, "DIV", "should have expected tag name.");
+ assert_true(test instanceof HTMLDivElement,"should be a valid Element instance");
+ }, "add id attribute via outerHTML");
+
+
+ test(function() {
+ var TEST_ID = "test11";
+
+ var element = document.createElement("div");
+ element.setAttribute("id", TEST_ID);
+ gBody.appendChild(element);
+
+ var test = document.getElementById(TEST_ID);
+ assert_equals(test, element, "should be equal to the appended element.");
+
+ // remove id-ed element with using outerHTML
+ element.outerHTML = "<div></div>";
+ var test = document.getElementById(TEST_ID);
+ assert_equals(test, null, "should be null.");
+ }, "remove id attribute via outerHTML");
+
+
+ test(function() {
+ // setup fixtures.
+ var TEST_ID = "test12";
+ var test = document.createElement("div");
+ test.id = TEST_ID;
+ gBody.appendChild(test);
+
+ // update id
+ var UPDATED_ID = TEST_ID + "-updated";
+ test.id = UPDATED_ID;
+ var e = document.getElementById(UPDATED_ID);
+ assert_equals(e, test, "should get the element with id.");
+
+ var old = document.getElementById(TEST_ID);
+ assert_equals(old, null, "shouldn't get the element by the old id.");
+
+ // remove id.
+ test.id = "";
+ var e2 = document.getElementById(UPDATED_ID);
+ assert_equals(e2, null, "should return null when the passed id is none in document.");
+ }, "update `id` attribute via element.id");
+
+
+ test(function() {
+ var TEST_ID = "test13";
+
+ var create_same_id_element = function (order) {
+ var element = document.createElement("div");
+ element.setAttribute("id", TEST_ID);
+ element.setAttribute("data-order", order);// for debug
+ return element;
+ };
+
+ // create fixture
+ var container = document.createElement("div");
+ container.setAttribute("id", TEST_ID + "-fixture");
+ gBody.appendChild(container);
+
+ var element1 = create_same_id_element("1");
+ var element2 = create_same_id_element("2");
+ var element3 = create_same_id_element("3");
+ var element4 = create_same_id_element("4");
+
+ // append element: 2 -> 4 -> 3 -> 1
+ container.appendChild(element2);
+ container.appendChild(element4);
+ container.insertBefore(element3, element4);
+ container.insertBefore(element1, element2);
+
+
+ var test = document.getElementById(TEST_ID);
+ assert_equals(test, element1, "should return 1st element");
+ container.removeChild(element1);
+
+ test = document.getElementById(TEST_ID);
+ assert_equals(test, element2, "should return 2nd element");
+ container.removeChild(element2);
+
+ test = document.getElementById(TEST_ID);
+ assert_equals(test, element3, "should return 3rd element");
+ container.removeChild(element3);
+
+ test = document.getElementById(TEST_ID);
+ assert_equals(test, element4, "should return 4th element");
+ container.removeChild(element4);
+
+
+ }, "where insertion order and tree order don't match");
+
+ test(function() {
+ var TEST_ID = "test14";
+ var a = document.createElement("a");
+ var b = document.createElement("b");
+ a.appendChild(b);
+ b.id = TEST_ID;
+ assert_equals(document.getElementById(TEST_ID), null);
+
+ gBody.appendChild(a);
+ assert_equals(document.getElementById(TEST_ID), b);
+ }, "Inserting an id by inserting its parent node");
+
+ test(function () {
+ var TEST_ID = "test15"
+ var outer = document.getElementById("outer");
+ var middle = document.getElementById("middle");
+ var inner = document.getElementById("inner");
+ outer.removeChild(middle);
+
+ var new_el = document.createElement("h1");
+ new_el.id = "heading";
+ inner.appendChild(new_el);
+ // the new element is not part of the document since
+ // "middle" element was removed previously
+ assert_equals(document.getElementById("heading"), null);
+ }, "Document.getElementById must not return nodes not present in document");
+
+ // TODO:
+ // id attribute in a namespace
+
+
+ // TODO:
+ // SVG + MathML elements with id attributes
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Document-getElementsByClassName.html b/testing/web-platform/tests/dom/nodes/Document-getElementsByClassName.html
new file mode 100644
index 0000000000..db8fac212d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-getElementsByClassName.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Document.getElementsByClassName</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var a = document.createElement("a"),
+ b = document.createElement("b");
+ a.className = "foo";
+ this.add_cleanup(function() {document.body.removeChild(a);});
+ document.body.appendChild(a);
+
+ var l = document.getElementsByClassName("foo");
+ assert_true(l instanceof HTMLCollection);
+ assert_equals(l.length, 1);
+
+ b.className = "foo";
+ document.body.appendChild(b);
+ assert_equals(l.length, 2);
+
+ document.body.removeChild(b);
+ assert_equals(l.length, 1);
+}, "getElementsByClassName() should be a live collection");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-getElementsByTagName-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Document-getElementsByTagName-xhtml.xhtml
new file mode 100644
index 0000000000..309a29ae77
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-getElementsByTagName-xhtml.xhtml
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Document.getElementsByTagName</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<pre id="x"></pre>
+<script>
+test(function() {
+ var t = document.body.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "I"))
+ this.add_cleanup(function() {document.body.removeChild(t)})
+ assert_equals(t.localName, "I")
+ assert_equals(t.tagName, "I")
+ assert_array_equals(document.getElementsByTagName("I"), [t])
+ assert_array_equals(document.getElementsByTagName("i"), [])
+ assert_array_equals(document.body.getElementsByTagName("I"), [t])
+ assert_array_equals(document.body.getElementsByTagName("i"), [])
+}, "HTML element with uppercase tag name matches in XHTML documents")
+
+test(function() {
+ var t = document.body.appendChild(document.createElementNS("test", "st"))
+ this.add_cleanup(function() {document.body.removeChild(t)})
+ assert_array_equals(document.getElementsByTagName("st"), [t])
+ assert_array_equals(document.getElementsByTagName("ST"), [])
+}, "Element in non-HTML namespace, no prefix, lowercase name")
+
+test(function() {
+ var t = document.body.appendChild(document.createElementNS("test", "ST"))
+ this.add_cleanup(function() {document.body.removeChild(t)})
+ assert_array_equals(document.getElementsByTagName("ST"), [t])
+ assert_array_equals(document.getElementsByTagName("st"), [])
+}, "Element in non-HTML namespace, no prefix, uppercase name")
+
+test(function() {
+ var t = document.body.appendChild(document.createElementNS("test", "te:st"))
+ this.add_cleanup(function() {document.body.removeChild(t)})
+ assert_array_equals(document.getElementsByTagName("st"), [])
+ assert_array_equals(document.getElementsByTagName("ST"), [])
+ assert_array_equals(document.getElementsByTagName("te:st"), [t])
+ assert_array_equals(document.getElementsByTagName("te:ST"), [])
+}, "Element in non-HTML namespace, prefix, lowercase name")
+
+test(function() {
+ var t = document.body.appendChild(document.createElementNS("test", "te:ST"))
+ this.add_cleanup(function() {document.body.removeChild(t)})
+ assert_array_equals(document.getElementsByTagName("ST"), [])
+ assert_array_equals(document.getElementsByTagName("st"), [])
+ assert_array_equals(document.getElementsByTagName("te:st"), [])
+ assert_array_equals(document.getElementsByTagName("te:ST"), [t])
+}, "Element in non-HTML namespace, prefix, uppercase name")
+
+test(function() {
+ var t = document.body.appendChild(document.createElement("AÇ"))
+ this.add_cleanup(function() {document.body.removeChild(t)})
+ assert_array_equals(document.getElementsByTagName("AÇ"), [t], "All uppercase input")
+ assert_array_equals(document.getElementsByTagName("aÇ"), [], "Ascii lowercase input")
+ assert_array_equals(document.getElementsByTagName("aç"), [], "All lowercase input")
+}, "Element in HTML namespace, no prefix, non-ascii characters in name")
+
+test(function() {
+ var t = document.body.appendChild(document.createElementNS("test", "AÇ"))
+ this.add_cleanup(function() {document.body.removeChild(t)})
+ assert_array_equals(document.getElementsByTagName("AÇ"), [t], "All uppercase input")
+ assert_array_equals(document.getElementsByTagName("aÇ"), [], "Ascii lowercase input")
+ assert_array_equals(document.getElementsByTagName("aç"), [], "All lowercase input")
+}, "Element in non-HTML namespace, non-ascii characters in name")
+
+test(function() {
+ var t = document.body.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "test:aÇ"))
+ this.add_cleanup(function() {document.body.removeChild(t)})
+ assert_array_equals(document.getElementsByTagName("TEST:AÇ"), [], "All uppercase input")
+ assert_array_equals(document.getElementsByTagName("test:aÇ"), [t], "Ascii lowercase input")
+ assert_array_equals(document.getElementsByTagName("test:aç"), [], "All lowercase input")
+}, "Element in HTML namespace, prefix, non-ascii characters in name")
+
+test(function() {
+ var t = document.body.appendChild(document.createElementNS("test", "TEST:AÇ"))
+ this.add_cleanup(function() {document.body.removeChild(t)})
+ assert_array_equals(document.getElementsByTagName("TEST:AÇ"), [t], "All uppercase input")
+ assert_array_equals(document.getElementsByTagName("test:aÇ"), [], "Ascii lowercase input")
+ assert_array_equals(document.getElementsByTagName("test:aç"), [], "All lowercase input")
+}, "Element in non-HTML namespace, prefix, non-ascii characters in name")
+
+test(function() {
+ var actual = document.getElementsByTagName("*");
+ var expected = [];
+ var get_elements = function(node) {
+ for (var i = 0; i &lt; node.childNodes.length; i++) {
+ var child = node.childNodes[i];
+ if (child.nodeType === child.ELEMENT_NODE) {
+ expected.push(child);
+ get_elements(child);
+ }
+ }
+ }
+ get_elements(document);
+ assert_array_equals(actual, expected);
+}, "getElementsByTagName('*')")
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Document-getElementsByTagName.html b/testing/web-platform/tests/dom/nodes/Document-getElementsByTagName.html
new file mode 100644
index 0000000000..00e3435c4c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-getElementsByTagName.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Document.getElementsByTagName</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-getelementsbytagname">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="Document-Element-getElementsByTagName.js"></script>
+<div id="log"></div>
+<script>
+test_getElementsByTagName(document, document.body);
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-getElementsByTagNameNS.html b/testing/web-platform/tests/dom/nodes/Document-getElementsByTagNameNS.html
new file mode 100644
index 0000000000..063dc98215
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-getElementsByTagNameNS.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Document.getElementsByTagNameNS</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-getelementsbytagnamens">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="Document-Element-getElementsByTagNameNS.js"></script>
+<div id="log"></div>
+<script>
+test_getElementsByTagNameNS(document, document.body);
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-implementation.html b/testing/web-platform/tests/dom/nodes/Document-implementation.html
new file mode 100644
index 0000000000..aed5259659
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-implementation.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Document.implementation</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-implementation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var implementation = document.implementation;
+ assert_true(implementation instanceof DOMImplementation,
+ "implementation should implement DOMImplementation");
+ assert_equals(document.implementation, implementation);
+}, "Getting implementation off the same document");
+
+test(function() {
+ var doc = document.implementation.createHTMLDocument();
+ assert_not_equals(document.implementation, doc.implementation);
+}, "Getting implementation off different documents");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Document-importNode.html b/testing/web-platform/tests/dom/nodes/Document-importNode.html
new file mode 100644
index 0000000000..d27cce6c56
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Document-importNode.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Document.importNode</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-importnode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var doc = document.implementation.createHTMLDocument("Title");
+ var div = doc.body.appendChild(doc.createElement("div"));
+ div.appendChild(doc.createElement("span"));
+ assert_equals(div.ownerDocument, doc);
+ assert_equals(div.firstChild.ownerDocument, doc);
+ var newDiv = document.importNode(div);
+ assert_equals(div.ownerDocument, doc);
+ assert_equals(div.firstChild.ownerDocument, doc);
+ assert_equals(newDiv.ownerDocument, document);
+ assert_equals(newDiv.firstChild, null);
+}, "No 'deep' argument.")
+test(function() {
+ var doc = document.implementation.createHTMLDocument("Title");
+ var div = doc.body.appendChild(doc.createElement("div"));
+ div.appendChild(doc.createElement("span"));
+ assert_equals(div.ownerDocument, doc);
+ assert_equals(div.firstChild.ownerDocument, doc);
+ var newDiv = document.importNode(div, undefined);
+ assert_equals(div.ownerDocument, doc);
+ assert_equals(div.firstChild.ownerDocument, doc);
+ assert_equals(newDiv.ownerDocument, document);
+ assert_equals(newDiv.firstChild, null);
+}, "Undefined 'deep' argument.")
+test(function() {
+ var doc = document.implementation.createHTMLDocument("Title");
+ var div = doc.body.appendChild(doc.createElement("div"));
+ div.appendChild(doc.createElement("span"));
+ assert_equals(div.ownerDocument, doc);
+ assert_equals(div.firstChild.ownerDocument, doc);
+ var newDiv = document.importNode(div, true);
+ assert_equals(div.ownerDocument, doc);
+ assert_equals(div.firstChild.ownerDocument, doc);
+ assert_equals(newDiv.ownerDocument, document);
+ assert_equals(newDiv.firstChild.ownerDocument, document);
+}, "True 'deep' argument.")
+test(function() {
+ var doc = document.implementation.createHTMLDocument("Title");
+ var div = doc.body.appendChild(doc.createElement("div"));
+ div.appendChild(doc.createElement("span"));
+ assert_equals(div.ownerDocument, doc);
+ assert_equals(div.firstChild.ownerDocument, doc);
+ var newDiv = document.importNode(div, false);
+ assert_equals(div.ownerDocument, doc);
+ assert_equals(div.firstChild.ownerDocument, doc);
+ assert_equals(newDiv.ownerDocument, document);
+ assert_equals(newDiv.firstChild, null);
+}, "False 'deep' argument.")
+
+test(function() {
+ let doc = document.implementation.createHTMLDocument("Title");
+ doc.body.setAttributeNS("http://example.com/", "p:name", "value");
+ let originalAttr = doc.body.getAttributeNodeNS("http://example.com/", "name");
+ let imported = document.importNode(originalAttr, true);
+ assert_equals(imported.prefix, originalAttr.prefix);
+ assert_equals(imported.namespaceURI, originalAttr.namespaceURI);
+ assert_equals(imported.localName, originalAttr.localName);
+}, "Import an Attr node with namespace/prefix correctly.");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/DocumentFragment-constructor.html b/testing/web-platform/tests/dom/nodes/DocumentFragment-constructor.html
new file mode 100644
index 0000000000..e97a7c4836
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DocumentFragment-constructor.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>DocumentFragment constructor</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-documentfragment-documentfragment">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+ const fragment = new DocumentFragment();
+ assert_equals(fragment.ownerDocument, document);
+}, "Sets the owner document to the current global object associated document");
+
+test(() => {
+ const fragment = new DocumentFragment();
+ const text = document.createTextNode("");
+ fragment.appendChild(text);
+ assert_equals(fragment.firstChild, text);
+}, "Create a valid document DocumentFragment");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/DocumentFragment-getElementById.html b/testing/web-platform/tests/dom/nodes/DocumentFragment-getElementById.html
new file mode 100644
index 0000000000..ce0d302c12
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DocumentFragment-getElementById.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>DocumentFragment.prototype.getElementById</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<template>
+ <div id="bar">
+ <span id="foo" data-yes></span>
+ </div>
+ <div id="foo">
+ <span id="foo"></span>
+ <ul id="bar">
+ <li id="foo"></li>
+ </ul>
+ </div>
+</template>
+
+<script>
+"use strict";
+
+test(() => {
+ assert_equals(typeof DocumentFragment.prototype.getElementById, "function", "It must exist on the prototype");
+ assert_equals(typeof document.createDocumentFragment().getElementById, "function", "It must exist on an instance");
+}, "The method must exist");
+
+test(() => {
+ assert_equals(document.createDocumentFragment().getElementById("foo"), null);
+ assert_equals(document.createDocumentFragment().getElementById(""), null);
+}, "It must return null when there are no matches");
+
+test(() => {
+ const frag = document.createDocumentFragment();
+ frag.appendChild(document.createElement("div"));
+ frag.appendChild(document.createElement("span"));
+ frag.childNodes[0].id = "foo";
+ frag.childNodes[1].id = "foo";
+
+ assert_equals(frag.getElementById("foo"), frag.childNodes[0]);
+}, "It must return the first element when there are matches");
+
+test(() => {
+ const frag = document.createDocumentFragment();
+ frag.appendChild(document.createElement("div"));
+ frag.childNodes[0].setAttribute("id", "");
+
+ assert_equals(
+ frag.getElementById(""),
+ null,
+ "Even if there is an element with an empty-string ID attribute, it must not be returned"
+ );
+}, "Empty string ID values");
+
+test(() => {
+ const frag = document.querySelector("template").content;
+
+ assert_true(frag.getElementById("foo").hasAttribute("data-yes"));
+}, "It must return the first element when there are matches, using a template");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/DocumentFragment-querySelectorAll-after-modification.html b/testing/web-platform/tests/dom/nodes/DocumentFragment-querySelectorAll-after-modification.html
new file mode 100644
index 0000000000..8049363885
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DocumentFragment-querySelectorAll-after-modification.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>querySelectorAll should still work on DocumentFragments after they are modified</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!-- Regression test for https://github.com/jsdom/jsdom/issues/2290 -->
+
+<script>
+"use strict";
+
+setup({ single_test: true });
+
+const frag = document.createDocumentFragment();
+frag.appendChild(document.createElement("div"));
+
+assert_array_equals(frag.querySelectorAll("img"), [], "before modification");
+
+frag.appendChild(document.createElement("div"));
+
+// If the bug is present, this will throw.
+assert_array_equals(frag.querySelectorAll("img"), [], "after modification");
+
+done();
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/DocumentType-literal-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/DocumentType-literal-xhtml.xhtml
new file mode 100644
index 0000000000..2b6965c14b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DocumentType-literal-xhtml.xhtml
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "STAFF" "staffNS.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>DocumentType literals</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-documenttype-name"/>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-documenttype-publicid"/>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-documenttype-systemid"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"/>
+<script>
+test(function() {
+ var doctype = document.firstChild;
+ assert_true(doctype instanceof DocumentType)
+ assert_equals(doctype.name, "html")
+ assert_equals(doctype.publicId, 'STAFF')
+ assert_equals(doctype.systemId, 'staffNS.dtd')
+})
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/DocumentType-literal.html b/testing/web-platform/tests/dom/nodes/DocumentType-literal.html
new file mode 100644
index 0000000000..a755c397b0
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DocumentType-literal.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html PUBLIC "STAFF" "staffNS.dtd">
+<title>DocumentType literals</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-documenttype-name">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-documenttype-publicid">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-documenttype-systemid">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var doctype = document.firstChild;
+ assert_true(doctype instanceof DocumentType)
+ assert_equals(doctype.name, "html")
+ assert_equals(doctype.publicId, 'STAFF')
+ assert_equals(doctype.systemId, 'staffNS.dtd')
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/DocumentType-remove.html b/testing/web-platform/tests/dom/nodes/DocumentType-remove.html
new file mode 100644
index 0000000000..9e18d3511a
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/DocumentType-remove.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>DocumentType.remove</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-childnode-remove">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="ChildNode-remove.js"></script>
+<div id=log></div>
+<script>
+var node, parentNode;
+setup(function() {
+ node = document.implementation.createDocumentType("html", "", "");
+ parentNode = document.implementation.createDocument(null, "", null);
+});
+testRemove(node, parentNode, "doctype");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElement-null-svg.svg b/testing/web-platform/tests/dom/nodes/Element-childElement-null-svg.svg
new file mode 100644
index 0000000000..388482874b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElement-null-svg.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml"
+ version="1.1"
+ width="100%" height="100%" viewBox="0 0 400 400">
+<title>Null test</title>
+<h:script src="/resources/testharness.js"/>
+<h:script src="/resources/testharnessreport.js"/>
+
+<text x="200" y="40" font-size="25" fill="black" text-anchor="middle">Test of firstElementChild and lastChildElement returning null</text>
+<text id="parentEl" x="200" y="70" font-size="20" fill="black" text-anchor="middle" font-weight="bold">Test</text>
+
+<h:script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl")
+ assert_equals(parentEl.firstElementChild, null)
+ assert_equals(parentEl.lastElementChild, null)
+})
+]]></h:script>
+</svg>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElement-null-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Element-childElement-null-xhtml.xhtml
new file mode 100644
index 0000000000..daedab6d97
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElement-null-xhtml.xhtml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Null Test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Test of firstElementChild and lastChildElement returning null</h1>
+<div id="log"></div>
+<p id="parentEl" style="font-weight:bold;">Test.</p>
+<script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl")
+ assert_equals(parentEl.firstElementChild, null)
+ assert_equals(parentEl.lastElementChild, null)
+})
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElement-null.html b/testing/web-platform/tests/dom/nodes/Element-childElement-null.html
new file mode 100644
index 0000000000..1863a41da5
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElement-null.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Null test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1>Test of firstElementChild and lastChildElement returning null</h1>
+<div id="log"></div>
+<p id="parentEl" style="font-weight:bold;">Test.</p>
+<script>
+test(function() {
+ var parentEl = document.getElementById("parentEl")
+ assert_equals(parentEl.firstElementChild, null)
+ assert_equals(parentEl.lastElementChild, null)
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-add-svg.svg b/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-add-svg.svg
new file mode 100644
index 0000000000..d149f1ea3f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-add-svg.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml"
+ version="1.1"
+ width="100%" height="100%" viewBox="0 0 400 400">
+<title>Dynamic Adding of Elements</title>
+<h:script src="/resources/testharness.js"/>
+<h:script src="/resources/testharnessreport.js"/>
+
+<text x="200" y="40" font-size="25" fill="black" text-anchor="middle">Test of Dynamic Adding of Elements</text>
+<text id="parentEl" x="200" y="70" font-size="20" fill="black" text-anchor="middle">The result of this test is
+<tspan id="first_element_child" font-weight="bold">unknown.</tspan></text>
+
+<h:script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var newChild = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+ parentEl.appendChild(newChild);
+ assert_equals(parentEl.childElementCount, 2)
+})
+]]></h:script>
+</svg>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-add-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-add-xhtml.xhtml
new file mode 100644
index 0000000000..c97ed1965b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-add-xhtml.xhtml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Dynamic Adding of Elements</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Test of Dynamic Adding of Elements</h1>
+<div id="log"></div>
+<p id="parentEl">The result of this test is
+<span id="first_element_child" style="font-weight:bold;">logged above.</span></p>
+<script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var newChild = document.createElement("span");
+ parentEl.appendChild(newChild);
+ assert_equals(parentEl.childElementCount, 2)
+})
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-add.html b/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-add.html
new file mode 100644
index 0000000000..3e7490b21d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-add.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Dynamic Adding of Elements</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1>Test of Dynamic Adding of Elements</h1>
+<div id="log"></div>
+<p id="parentEl">The result of this test is
+<span id="first_element_child" style="font-weight:bold;">logged above.</span></p>
+<script>
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var newChild = document.createElement("span");
+ parentEl.appendChild(newChild);
+ assert_equals(parentEl.childElementCount, 2)
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-remove-svg.svg b/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-remove-svg.svg
new file mode 100644
index 0000000000..bf99de65aa
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-remove-svg.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml"
+ version="1.1"
+ width="100%" height="100%" viewBox="0 0 400 400">
+<title>Dynamic Removal of Elements</title>
+<h:script src="/resources/testharness.js"/>
+<h:script src="/resources/testharnessreport.js"/>
+
+<text x="200" y="40" font-size="25" fill="black" text-anchor="middle">Test of Dynamic Removal of Elements</text>
+<text id="parentEl" x="200" y="70" font-size="20" fill="black" text-anchor="middle">The result of this test is
+<tspan id="first_element_child" font-weight="bold">unknown.</tspan><tspan id="last_element_child"> </tspan></text>
+
+<h:script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var lec = parentEl.lastElementChild;
+ parentEl.removeChild(lec);
+ assert_equals(parentEl.childElementCount, 1)
+})
+]]></h:script>
+</svg>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-remove-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-remove-xhtml.xhtml
new file mode 100644
index 0000000000..f0009b0a77
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-remove-xhtml.xhtml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Dynamic Removal of Elements</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Test of Removal Adding of Elements</h1>
+<div id="log"></div>
+<p id="parentEl">The result of this test is
+<span id="first_element_child" style="font-weight:bold;">logged above.</span><span id="last_element_child"> </span></p>
+<script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var lec = parentEl.lastElementChild;
+ parentEl.removeChild(lec);
+ assert_equals(parentEl.childElementCount, 1)
+})
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-remove.html b/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-remove.html
new file mode 100644
index 0000000000..3f7e7c7ead
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElementCount-dynamic-remove.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Dynamic Removal of Elements</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1>Test of Dynamic Removal of Elements</h1>
+<div id="log"></div>
+<p id="parentEl">The result of this test is
+<span id="first_element_child" style="font-weight:bold;">unknown.</span><span id="last_element_child"> </span></p>
+<script>
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var lec = parentEl.lastElementChild;
+ parentEl.removeChild(lec);
+ assert_equals(parentEl.childElementCount, 1)
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElementCount-nochild-svg.svg b/testing/web-platform/tests/dom/nodes/Element-childElementCount-nochild-svg.svg
new file mode 100644
index 0000000000..8ba5743607
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElementCount-nochild-svg.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml"
+ version="1.1"
+ width="100%" height="100%" viewBox="0 0 400 400">
+<title>childElementCount</title>
+<h:script src="/resources/testharness.js"/>
+<h:script src="/resources/testharnessreport.js"/>
+
+<text x="200" y="40" font-size="25" fill="black" text-anchor="middle">Test of childElementCount with No Child Element Nodes</text>
+<text id="parentEl" x="200" y="70" font-size="20" fill="black" text-anchor="middle" font-weight="bold">Test</text>
+
+<h:script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl")
+ assert_equals(parentEl.childElementCount, 0)
+})
+]]></h:script>
+</svg>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElementCount-nochild-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Element-childElementCount-nochild-xhtml.xhtml
new file mode 100644
index 0000000000..f567a20c23
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElementCount-nochild-xhtml.xhtml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>childElementCount without Child Element Nodes</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Test of childElementCount with No Child Element Nodes</h1>
+<div id="log"></div>
+<p id="parentEl" style="font-weight:bold;">Test.</p>
+<script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl")
+ assert_equals(parentEl.childElementCount, 0)
+})
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElementCount-nochild.html b/testing/web-platform/tests/dom/nodes/Element-childElementCount-nochild.html
new file mode 100644
index 0000000000..fb52fb205c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElementCount-nochild.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>childElementCount without Child Element Nodes</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1>Test of childElementCount with No Child Element Nodes</h1>
+<div id="log"></div>
+<p id="parentEl" style="font-weight:bold;">Test.</p>
+<script>
+test(function() {
+ var parentEl = document.getElementById("parentEl")
+ assert_equals(parentEl.childElementCount, 0)
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElementCount-svg.svg b/testing/web-platform/tests/dom/nodes/Element-childElementCount-svg.svg
new file mode 100644
index 0000000000..ff93eff625
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElementCount-svg.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml"
+ version="1.1"
+ width="100%" height="100%" viewBox="0 0 400 400">
+<title>childElementCount</title>
+<h:script src="/resources/testharness.js"/>
+<h:script src="/resources/testharnessreport.js"/>
+
+<text x="200" y="40" font-size="25" fill="black" text-anchor="middle">Test of childElementCount</text>
+<text id="parentEl" x="200" y="70" font-size="20" fill="black" text-anchor="middle">The result of <tspan id="first_element_child"><tspan>this</tspan> <tspan>test</tspan></tspan> is
+<tspan id="middle_element_child" font-weight="bold">unknown.</tspan>
+
+
+
+<tspan id="last_element_child" style="display:none;">fnord</tspan> </text>
+
+<h:script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl")
+ assert_true("childElementCount" in parentEl)
+ assert_equals(parentEl.childElementCount, 3)
+})
+]]></h:script>
+</svg>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElementCount-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Element-childElementCount-xhtml.xhtml
new file mode 100644
index 0000000000..6b719ff7a8
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElementCount-xhtml.xhtml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>childElementCount</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Test of childElementCount</h1>
+<div id="log"></div>
+<p id="parentEl">The result of <span id="first_element_child"><span>this</span> <span>test</span></span> is
+<span id="middle_element_child" style="font-weight:bold;">unknown.</span>
+
+
+
+<span id="last_element_child" style="display:none;">fnord</span> </p>
+<script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl")
+ assert_true("childElementCount" in parentEl)
+ assert_equals(parentEl.childElementCount, 3)
+})
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Element-childElementCount.html b/testing/web-platform/tests/dom/nodes/Element-childElementCount.html
new file mode 100644
index 0000000000..8cfe567f91
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-childElementCount.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>childElementCount</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1>Test of childElementCount</h1>
+<div id="log"></div>
+<p id="parentEl">The result of <span id="first_element_child"><span>this</span> <span>test</span></span> is
+<span id="middle_element_child" style="font-weight:bold;">given above.</span>
+
+
+
+<span id="last_element_child" style="display:none;">fnord</span> </p>
+<script>
+test(function() {
+ var parentEl = document.getElementById("parentEl")
+ assert_true("childElementCount" in parentEl)
+ assert_equals(parentEl.childElementCount, 3)
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-children.html b/testing/web-platform/tests/dom/nodes/Element-children.html
new file mode 100644
index 0000000000..c0210f9667
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-children.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<title>HTMLCollection edge cases</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="test"><img><img id=foo><img id=foo><img name="bar"></div>
+<script>
+setup(function() {
+ // Add some non-HTML elements in there to test what happens with those.
+ var container = document.getElementById("test");
+ var child = document.createElementNS("", "img");
+ child.setAttribute("id", "baz");
+ container.appendChild(child);
+
+ child = document.createElementNS("", "img");
+ child.setAttribute("name", "qux");
+ container.appendChild(child);
+});
+
+test(function() {
+ var container = document.getElementById("test");
+ var result = container.children.item("foo");
+ assert_true(result instanceof Element, "Expected an Element.");
+ assert_false(result.hasAttribute("id"), "Expected the IDless Element.")
+})
+
+test(function() {
+ var container = document.getElementById("test");
+ var list = container.children;
+ var result = [];
+ for (var p in list) {
+ if (list.hasOwnProperty(p)) {
+ result.push(p);
+ }
+ }
+ assert_array_equals(result, ['0', '1', '2', '3', '4', '5']);
+ result = Object.getOwnPropertyNames(list);
+ assert_array_equals(result, ['0', '1', '2', '3', '4', '5', 'foo', 'bar', 'baz']);
+
+ // Mapping of exposed names to their indices in the list.
+ var exposedNames = { 'foo': 1, 'bar': 3, 'baz': 4 };
+ for (var exposedName in exposedNames) {
+ assert_true(exposedName in list);
+ assert_true(list.hasOwnProperty(exposedName));
+ assert_equals(list[exposedName], list.namedItem(exposedName));
+ assert_equals(list[exposedName], list.item(exposedNames[exposedName]));
+ assert_true(list[exposedName] instanceof Element);
+ }
+
+ var unexposedNames = ['qux'];
+ for (var unexposedName of unexposedNames) {
+ assert_false(unexposedName in list);
+ assert_false(list.hasOwnProperty(unexposedName));
+ assert_equals(list[unexposedName], undefined);
+ assert_equals(list.namedItem(unexposedName), null);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-classlist.html b/testing/web-platform/tests/dom/nodes/Element-classlist.html
new file mode 100644
index 0000000000..2b5a271ba4
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-classlist.html
@@ -0,0 +1,478 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for the classList element attribute</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="content"></div>
+<script>
+const SVG_NS = "http://www.w3.org/2000/svg";
+const XHTML_NS = "http://www.w3.org/1999/xhtml"
+const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
+
+function setClass(e, newVal) {
+ if (newVal === null) {
+ e.removeAttribute("class");
+ } else {
+ e.setAttribute("class", newVal);
+ }
+}
+
+function checkModification(e, funcName, args, expectedRes, before, after,
+ expectedException, desc) {
+ if (!Array.isArray(args)) {
+ args = [args];
+ }
+
+ test(function() {
+ var shouldThrow = typeof(expectedException) === "string";
+ if (shouldThrow) {
+ // If an exception is thrown, the class attribute shouldn't change.
+ after = before;
+ }
+ setClass(e, before);
+
+ var obs;
+ // If we have MutationObservers available, do some checks to make
+ // sure attribute sets are happening at sane times.
+ if (self.MutationObserver) {
+ obs = new MutationObserver(() => {});
+ obs.observe(e, { attributes: true });
+ }
+ if (shouldThrow) {
+ assert_throws_dom(expectedException, function() {
+ var list = e.classList;
+ var res = list[funcName].apply(list, args);
+ });
+ } else {
+ var list = e.classList;
+ var res = list[funcName].apply(list, args);
+ }
+ if (obs) {
+ var mutationRecords = obs.takeRecords();
+ obs.disconnect();
+ if (shouldThrow) {
+ assert_equals(mutationRecords.length, 0,
+ "There should have been no mutation");
+ } else if (funcName == "replace") {
+ assert_equals(mutationRecords.length == 1,
+ expectedRes,
+ "Should have a mutation exactly when replace() returns true");
+ } else {
+ // For other functions, would need to check when exactly
+ // mutations are supposed to happen.
+ }
+ }
+ if (!shouldThrow) {
+ assert_equals(res, expectedRes, "wrong return value");
+ }
+
+ var expectedAfter = after;
+
+ assert_equals(e.getAttribute("class"), expectedAfter,
+ "wrong class after modification");
+ }, "classList." + funcName + "(" + args.map(format_value).join(", ") +
+ ") with attribute value " + format_value(before) + desc);
+}
+
+function assignToClassListStrict(e) {
+ "use strict";
+ e.classList = "foo";
+ e.removeAttribute("class");
+}
+
+function assignToClassList(e) {
+ var expect = e.classList;
+ e.classList = "foo";
+ assert_equals(e.classList, expect,
+ "classList should be unchanged after assignment");
+ e.removeAttribute("class");
+}
+
+function testClassList(e, desc) {
+
+ // assignment
+
+ test(function() {
+ assignToClassListStrict(e);
+ assignToClassList(e);
+ }, "Assigning to classList" + desc);
+
+ // supports
+ test(function() {
+ assert_throws_js(TypeError, function() {
+ e.classList.supports("a");
+ })
+ }, ".supports() must throw TypeError" + desc);
+
+ // length attribute
+
+ function checkLength(value, length) {
+ test(function() {
+ setClass(e, value);
+ assert_equals(e.classList.length, length);
+ }, "classList.length when " +
+ (value === null ? "removed" : "set to " + format_value(value)) + desc);
+ }
+
+ checkLength(null, 0);
+ checkLength("", 0);
+ checkLength(" \t \f", 0);
+ checkLength("a", 1);
+ checkLength("a A", 2);
+ checkLength("\r\na\t\f", 1);
+ checkLength("a a", 1);
+ checkLength("a a a a a a", 1);
+ checkLength("a a b b", 2);
+ checkLength("a A B b", 4);
+ checkLength("a b c c b a a b c c", 3);
+ checkLength(" a a b", 2);
+ checkLength("a\tb\nc\fd\re f", 6);
+
+ // [Stringifies]
+
+ function checkStringifier(value, expected) {
+ test(function() {
+ setClass(e, value);
+ assert_equals(e.classList.toString(), expected);
+ }, "classList.toString() when " +
+ (value === null ? "removed" : "set to " + format_value(value)) + desc);
+ }
+
+ checkStringifier(null, "");
+ checkStringifier("foo", "foo");
+ checkStringifier(" a a b", " a a b");
+
+ // item() method
+
+ function checkItems(attributeValue, expectedValues) {
+ function checkItemFunction(index, expected) {
+ assert_equals(e.classList.item(index), expected,
+ "classList.item(" + index + ")");
+ }
+
+ function checkItemArray(index, expected) {
+ assert_equals(e.classList[index], expected, "classList[" + index + "]");
+ }
+
+ test(function() {
+ setClass(e, attributeValue);
+
+ checkItemFunction(-1, null);
+ checkItemArray(-1, undefined);
+
+ var i = 0;
+ while (i < expectedValues.length) {
+ checkItemFunction(i, expectedValues[i]);
+ checkItemArray(i, expectedValues[i]);
+ i++;
+ }
+
+ checkItemFunction(i, null);
+ checkItemArray(i, undefined);
+
+ checkItemFunction(0xffffffff, null);
+ checkItemArray(0xffffffff, undefined);
+
+ checkItemFunction(0xfffffffe, null);
+ checkItemArray(0xfffffffe, undefined);
+ }, "classList.item() when set to " + format_value(attributeValue) + desc);
+ }
+
+ checkItems(null, []);
+ checkItems("a", ["a"]);
+ checkItems("aa AA aa", ["aa", "AA"]);
+ checkItems("a b", ["a", "b"]);
+ checkItems(" a a b", ["a", "b"]);
+ checkItems("\t\n\f\r a\t\n\f\r b\t\n\f\r ", ["a", "b"]);
+
+ // contains() method
+
+ function checkContains(attributeValue, args, expectedRes) {
+ if (!Array.isArray(expectedRes)) {
+ expectedRes = Array(args.length).fill(expectedRes);
+ }
+ setClass(e, attributeValue);
+ for (var i = 0; i < args.length; i++) {
+ test(function() {
+ assert_equals(e.classList.contains(args[i]), expectedRes[i],
+ "classList.contains(\"" + args[i] + "\")");
+ }, "classList.contains(" + format_value(args[i]) + ") when set to " +
+ format_value(attributeValue) + desc);
+ }
+ }
+
+ checkContains(null, ["a", "", " "], false);
+ checkContains("", ["a"], false);
+
+ checkContains("a", ["a"], true);
+ checkContains("a", ["aa", "b", "A", "a.", "a)",, "a'", 'a"', "a$", "a~",
+ "a?", "a\\"], false);
+
+ // All "ASCII whitespace" per spec, before and after
+ checkContains("a", ["a\t", "\ta", "a\n", "\na", "a\f", "\fa", "a\r", "\ra",
+ "a ", " a"], false);
+
+ checkContains("aa AA", ["aa", "AA", "aA"], [true, true, false]);
+ checkContains("a a a", ["a", "aa", "b"], [true, false, false]);
+ checkContains("a b c", ["a", "b"], true);
+
+ checkContains("null undefined", [null, undefined], true);
+ checkContains("\t\n\f\r a\t\n\f\r b\t\n\f\r ", ["a", "b"], true);
+
+ // add() method
+
+ function checkAdd(before, argument, after, param) {
+ var expectedException = undefined;
+ var noop = false;
+ if (param == "noop") {
+ noop = true;
+ } else {
+ expectedException = param;
+ }
+ checkModification(e, "add", argument, undefined, before, after,
+ expectedException, desc);
+ // Also check force toggle. The only difference is that it doesn't run the
+ // update steps for a no-op.
+ if (!Array.isArray(argument)) {
+ checkModification(e, "toggle", [argument, true], true, before,
+ noop ? before : after, expectedException, desc);
+ }
+ }
+
+ checkAdd(null, "", null, "SyntaxError");
+ checkAdd(null, ["a", ""], null, "SyntaxError");
+ checkAdd(null, " ", null, "InvalidCharacterError");
+ checkAdd(null, "\ta", null, "InvalidCharacterError");
+ checkAdd(null, "a\t", null, "InvalidCharacterError");
+ checkAdd(null, "\na", null, "InvalidCharacterError");
+ checkAdd(null, "a\n", null, "InvalidCharacterError");
+ checkAdd(null, "\fa", null, "InvalidCharacterError");
+ checkAdd(null, "a\f", null, "InvalidCharacterError");
+ checkAdd(null, "\ra", null, "InvalidCharacterError");
+ checkAdd(null, "a\r", null, "InvalidCharacterError");
+ checkAdd(null, " a", null, "InvalidCharacterError");
+ checkAdd(null, "a ", null, "InvalidCharacterError");
+ checkAdd(null, ["a", " "], null, "InvalidCharacterError");
+ checkAdd(null, ["a", "aa "], null, "InvalidCharacterError");
+
+ checkAdd("a", "a", "a");
+ checkAdd("aa", "AA", "aa AA");
+ checkAdd("a b c", "a", "a b c");
+ checkAdd("a a a b", "a", "a b", "noop");
+ checkAdd(null, "a", "a");
+ checkAdd("", "a", "a");
+ checkAdd(" ", "a", "a");
+ checkAdd(" \f", "a", "a");
+ checkAdd("a", "b", "a b");
+ checkAdd("a b c", "d", "a b c d");
+ checkAdd("a b c ", "d", "a b c d");
+ checkAdd(" a a b", "c", "a b c");
+ checkAdd(" a a b", "a", "a b", "noop");
+ checkAdd("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "c", "a b c");
+
+ // multiple add
+ checkAdd("a b c ", ["d", "e"], "a b c d e");
+ checkAdd("a b c ", ["a", "a"], "a b c");
+ checkAdd("a b c ", ["d", "d"], "a b c d");
+ checkAdd("a b c a ", [], "a b c");
+ checkAdd(null, ["a", "b"], "a b");
+ checkAdd("", ["a", "b"], "a b");
+
+ checkAdd(null, null, "null");
+ checkAdd(null, undefined, "undefined");
+
+ // remove() method
+
+ function checkRemove(before, argument, after, param) {
+ var expectedException = undefined;
+ var noop = false;
+ if (param == "noop") {
+ noop = true;
+ } else {
+ expectedException = param;
+ }
+ checkModification(e, "remove", argument, undefined, before, after,
+ expectedException, desc);
+ // Also check force toggle. The only difference is that it doesn't run the
+ // update steps for a no-op.
+ if (!Array.isArray(argument)) {
+ checkModification(e, "toggle", [argument, false], false, before,
+ noop ? before : after, expectedException, desc);
+ }
+ }
+
+ checkRemove(null, "", null, "SyntaxError");
+ checkRemove(null, " ", null, "InvalidCharacterError");
+ checkRemove("\ta", "\ta", "\ta", "InvalidCharacterError");
+ checkRemove("a\t", "a\t", "a\t", "InvalidCharacterError");
+ checkRemove("\na", "\na", "\na", "InvalidCharacterError");
+ checkRemove("a\n", "a\n", "a\n", "InvalidCharacterError");
+ checkRemove("\fa", "\fa", "\fa", "InvalidCharacterError");
+ checkRemove("a\f", "a\f", "a\f", "InvalidCharacterError");
+ checkRemove("\ra", "\ra", "\ra", "InvalidCharacterError");
+ checkRemove("a\r", "a\r", "a\r", "InvalidCharacterError");
+ checkRemove(" a", " a", " a", "InvalidCharacterError");
+ checkRemove("a ", "a ", "a ", "InvalidCharacterError");
+ checkRemove("aa ", "aa ", null, "InvalidCharacterError");
+
+ checkRemove(null, "a", null);
+ checkRemove("", "a", "");
+ checkRemove("a b c", "d", "a b c", "noop");
+ checkRemove("a b c", "A", "a b c", "noop");
+ checkRemove(" a a a ", "a", "");
+ checkRemove("a b", "a", "b");
+ checkRemove("a b ", "a", "b");
+ checkRemove("a a b", "a", "b");
+ checkRemove("aa aa bb", "aa", "bb");
+ checkRemove("a a b a a c a a", "a", "b c");
+
+ checkRemove("a b c", "b", "a c");
+ checkRemove("aaa bbb ccc", "bbb", "aaa ccc");
+ checkRemove(" a b c ", "b", "a c");
+ checkRemove("a b b b c", "b", "a c");
+
+ checkRemove("a b c", "c", "a b");
+ checkRemove(" a b c ", "c", "a b");
+ checkRemove("a b c c c", "c", "a b");
+
+ checkRemove("a b a c a d a", "a", "b c d");
+ checkRemove("AA BB aa CC AA dd aa", "AA", "BB aa CC dd");
+
+ checkRemove("\ra\na\ta\f", "a", "");
+ checkRemove("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", "b");
+
+ // multiple remove
+ checkRemove("a b c ", ["d", "e"], "a b c");
+ checkRemove("a b c ", ["a", "b"], "c");
+ checkRemove("a b c ", ["a", "c"], "b");
+ checkRemove("a b c ", ["a", "a"], "b c");
+ checkRemove("a b c ", ["d", "d"], "a b c");
+ checkRemove("a b c ", [], "a b c");
+ checkRemove(null, ["a", "b"], null);
+ checkRemove("", ["a", "b"], "");
+ checkRemove("a a", [], "a");
+
+ checkRemove("null", null, "");
+ checkRemove("undefined", undefined, "");
+
+ // toggle() method
+
+ function checkToggle(before, argument, expectedRes, after, expectedException) {
+ checkModification(e, "toggle", argument, expectedRes, before, after,
+ expectedException, desc);
+ }
+
+ checkToggle(null, "", null, null, "SyntaxError");
+ checkToggle(null, "aa ", null, null, "InvalidCharacterError");
+
+ checkToggle(null, "a", true, "a");
+ checkToggle("", "a", true, "a");
+ checkToggle(" ", "a", true, "a");
+ checkToggle(" \f", "a", true, "a");
+ checkToggle("a", "b", true, "a b");
+ checkToggle("a", "A", true, "a A");
+ checkToggle("a b c", "d", true, "a b c d");
+ checkToggle(" a a b", "d", true, "a b d");
+
+ checkToggle("a", "a", false, "");
+ checkToggle(" a a a ", "a", false, "");
+ checkToggle(" A A A ", "a", true, "A a");
+ checkToggle(" a b c ", "b", false, "a c");
+ checkToggle(" a b c b b", "b", false, "a c");
+ checkToggle(" a b c ", "c", false, "a b");
+ checkToggle(" a b c ", "a", false, "b c");
+ checkToggle(" a a b", "b", false, "a");
+ checkToggle("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", false, "b");
+ checkToggle("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "c", true, "a b c");
+
+ checkToggle("null", null, false, "");
+ checkToggle("", null, true, "null");
+ checkToggle("undefined", undefined, false, "");
+ checkToggle("", undefined, true, "undefined");
+
+
+ // replace() method
+ function checkReplace(before, token, newToken, expectedRes, after, expectedException) {
+ checkModification(e, "replace", [token, newToken], expectedRes, before,
+ after, expectedException, desc);
+ }
+
+ checkReplace(null, "", "a", null, null, "SyntaxError");
+ checkReplace(null, "", " ", null, null, "SyntaxError");
+ checkReplace(null, " ", "a", null, null, "InvalidCharacterError");
+ checkReplace(null, "\ta", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "a\t", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "\na", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "a\n", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "\fa", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "a\f", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "\ra", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "a\r", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, " a", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "a ", "b", null, null, "InvalidCharacterError");
+
+ checkReplace(null, "a", "", null, null, "SyntaxError");
+ checkReplace(null, " ", "", null, null, "SyntaxError");
+ checkReplace(null, "a", " ", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "\ta", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "a\t", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "\na", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "a\n", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "\fa", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "a\f", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "\ra", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "a\r", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", " a", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "a ", null, null, "InvalidCharacterError");
+
+ checkReplace("a", "a", "a", true, "a");
+ checkReplace("a", "a", "b", true, "b");
+ checkReplace("a", "A", "b", false, "a");
+ checkReplace("a b", "b", "A", true, "a A");
+ checkReplace("a b", "c", "a", false, "a b");
+ checkReplace("a b c", "d", "e", false, "a b c");
+ // https://github.com/whatwg/dom/issues/443
+ checkReplace("a a a b", "a", "a", true, "a b");
+ checkReplace("a a a b", "c", "d", false, "a a a b");
+ checkReplace(null, "a", "b", false, null);
+ checkReplace("", "a", "b", false, "");
+ checkReplace(" ", "a", "b", false, " ");
+ checkReplace(" a \f", "a", "b", true, "b");
+ checkReplace("a b c", "b", "d", true, "a d c");
+ checkReplace("a b c", "c", "a", true, "a b");
+ checkReplace("c b a", "c", "a", true, "a b");
+ checkReplace("a b a", "a", "c", true, "c b");
+ checkReplace("a b a", "b", "c", true, "a c");
+ checkReplace(" a a b", "a", "c", true, "c b");
+ checkReplace(" a a b", "b", "c", true, "a c");
+ checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", "c", true, "c b");
+ checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "b", "c", true, "a c");
+
+ checkReplace("a null", null, "b", true, "a b");
+ checkReplace("a b", "a", null, true, "null b");
+ checkReplace("a undefined", undefined, "b", true, "a b");
+ checkReplace("a b", "a", undefined, true, "undefined b");
+}
+
+var content = document.getElementById("content");
+
+var htmlNode = document.createElement("div");
+content.appendChild(htmlNode);
+testClassList(htmlNode, " (HTML node)");
+
+var xhtmlNode = document.createElementNS(XHTML_NS, "div");
+content.appendChild(xhtmlNode);
+testClassList(xhtmlNode, " (XHTML node)");
+
+var mathMLNode = document.createElementNS(MATHML_NS, "math");
+content.appendChild(mathMLNode);
+testClassList(mathMLNode, " (MathML node)");
+
+var xmlNode = document.createElementNS(null, "foo");
+content.appendChild(xmlNode);
+testClassList(xmlNode, " (XML node with null namespace)");
+
+var fooNode = document.createElementNS("http://example.org/foo", "foo");
+content.appendChild(fooNode);
+testClassList(fooNode, " (foo node)");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-closest.html b/testing/web-platform/tests/dom/nodes/Element-closest.html
new file mode 100644
index 0000000000..386e3bd251
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-closest.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<meta charset=utf8>
+<title>Test for Element.closest</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body id="body">
+ <div id="test8" class="div3" style="display:none">
+ <div id="test7" class="div2">
+ <div id="test6" class="div1">
+ <form id="test10" class="form2"></form>
+ <form id="test5" class="form1" name="form-a">
+ <input id="test1" class="input1" required>
+ <fieldset class="fieldset2" id="test2">
+ <select id="test3" class="select1" required>
+ <option default id="test4" value="">Test4</option>
+ <option selected id="test11">Test11</option>
+ <option id="test12">Test12</option>
+ <option id="test13">Test13</option>
+ </select>
+ <input id="test9" type="text" required>
+ </fieldset>
+ </form>
+ </div>
+ </div>
+ </div>
+ <div id=log></div>
+<script>
+ do_test("select" , "test12", "test3");
+ do_test("fieldset" , "test13", "test2");
+ do_test("div" , "test13", "test6");
+ do_test("body" , "test3" , "body");
+
+ do_test("[default]" , "test4" , "test4");
+ do_test("[selected]" , "test4" , "");
+ do_test("[selected]" , "test11", "test11");
+ do_test('[name="form-a"]' , "test12", "test5");
+ do_test('form[name="form-a"]' , "test13", "test5");
+ do_test("input[required]" , "test9" , "test9");
+ do_test("select[required]" , "test9" , "");
+
+ do_test("div:not(.div1)" , "test13", "test7");
+ do_test("div.div3" , "test6" , "test8");
+ do_test("div#test7" , "test1" , "test7");
+
+ do_test(".div3 > .div2" , "test12", "test7");
+ do_test(".div3 > .div1" , "test12", "");
+ do_test("form > input[required]" , "test9" , "");
+ do_test("fieldset > select[required]", "test12", "test3");
+
+ do_test("input + fieldset" , "test6" , "");
+ do_test("form + form" , "test3" , "test5");
+ do_test("form + form" , "test5" , "test5");
+
+ do_test(":empty" , "test10", "test10");
+ do_test(":last-child" , "test11", "test2");
+ do_test(":first-child" , "test12", "test3");
+ do_test(":invalid" , "test11", "test2");
+
+ do_test(":scope" , "test4", "test4");
+ do_test("select > :scope" , "test4", "test4");
+ do_test("div > :scope" , "test4", "");
+ do_test(":has(> :scope)" , "test4", "test3");
+function do_test(aSelector, aElementId, aTargetId) {
+ test(function() {
+ var el = document.getElementById(aElementId).closest(aSelector);
+ if (el === null) {
+ assert_equals("", aTargetId, aSelector);
+ } else {
+ assert_equals(el.id, aTargetId, aSelector);
+ }
+ }, "Element.closest with context node '" + aElementId + "' and selector '" + aSelector + "'");
+}
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-firstElementChild-entity-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-entity-xhtml.xhtml
new file mode 100644
index 0000000000..f28005e9c8
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-entity-xhtml.xhtml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
+[
+<!ENTITY tree "<span id='first_element_child' style='font-weight:bold;'>unknown.</span>">
+]>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Entity References</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Test of Entity References</h1>
+<div id="log"></div>
+<p id="parentEl">The result of this test is &tree;</p>
+<script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl")
+ var fec = parentEl.firstElementChild;
+ assert_true(!!fec)
+ assert_equals(fec.nodeType, 1)
+ assert_equals(fec.getAttribute("id"), "first_element_child")
+})
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Element-firstElementChild-entity.svg b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-entity.svg
new file mode 100644
index 0000000000..3a20ea79ab
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-entity.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
+[
+<!ENTITY tree "<tspan id='first_element_child' font-weight='bold'>unknown.</tspan>">
+]>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml"
+ version="1.1"
+ width="100%" height="100%" viewBox="0 0 400 400">
+<title>Entity References</title>
+<h:script src="/resources/testharness.js"/>
+<h:script src="/resources/testharnessreport.js"/>
+
+<text x="200" y="40" font-size="25" fill="black" text-anchor="middle">Test of Entity References</text>
+<text id="parentEl" x="200" y="70" font-size="20" fill="black" text-anchor="middle">The result of this test is &tree;</text>
+
+<h:script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl")
+ var fec = parentEl.firstElementChild;
+ assert_true(!!fec)
+ assert_equals(fec.nodeType, 1)
+ assert_equals(fec.getAttribute("id"), "first_element_child")
+})
+]]></h:script>
+</svg>
diff --git a/testing/web-platform/tests/dom/nodes/Element-firstElementChild-namespace-svg.svg b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-namespace-svg.svg
new file mode 100644
index 0000000000..d42c08777c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-namespace-svg.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml"
+ xmlns:pickle="http://ns.example.org/pickle"
+ version="1.1"
+ width="100%" height="100%" viewBox="0 0 400 400">
+<title>firstElementChild with namespaces</title>
+<h:script src="/resources/testharness.js"/>
+<h:script src="/resources/testharnessreport.js"/>
+
+<text x="200" y="40" font-size="25" fill="black" text-anchor="middle">Test of firstElementChild with namespaces</text>
+<g id="parentEl">
+ <pickle:dill id="first_element_child"/>
+</g>
+
+<h:script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var fec = parentEl.firstElementChild;
+ assert_true(!!fec)
+ assert_equals(fec.nodeType, 1)
+ assert_equals(fec.getAttribute("id"), "first_element_child")
+ assert_equals(fec.localName, "dill")
+})
+]]></h:script>
+</svg>
diff --git a/testing/web-platform/tests/dom/nodes/Element-firstElementChild-namespace-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-namespace-xhtml.xhtml
new file mode 100644
index 0000000000..29441d2786
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-namespace-xhtml.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:pickle="http://ns.example.org/pickle">
+<head>
+<title>firstElementChild with namespaces</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Test of firstElementChild with namespaces</h1>
+<div id="parentEl">
+ <pickle:dill id="first_element_child"/>
+</div>
+<div id="log"></div>
+<p id="parentEl">The result of this test is
+<span id="first_element_child" style="font-weight:bold;">logged above.</span></p>
+<script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var fec = parentEl.firstElementChild;
+ assert_true(!!fec)
+ assert_equals(fec.nodeType, 1)
+ assert_equals(fec.getAttribute("id"), "first_element_child")
+ assert_equals(fec.localName, "dill")
+})
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Element-firstElementChild-namespace.html b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-namespace.html
new file mode 100644
index 0000000000..629deab3ae
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-namespace.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>firstElementChild with namespaces</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1>Test of firstElementChild with namespaces</h1>
+<div id="log"></div>
+<p id="parentEl">The result of this test is a unknown.</p>
+<script>
+test(function() {
+ var parentEl = document.getElementById("parentEl")
+ var el = document.createElementNS("http://ns.example.org/pickle", "pickle:dill")
+ el.setAttribute("id", "first_element_child")
+ parentEl.appendChild(el)
+ var fec = parentEl.firstElementChild
+ assert_true(!!fec)
+ assert_equals(fec.nodeType, 1)
+ assert_equals(fec.getAttribute("id"), "first_element_child")
+ assert_equals(fec.localName, "dill")
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-firstElementChild-svg.svg b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-svg.svg
new file mode 100644
index 0000000000..359c5b82ca
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-svg.svg
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml"
+ version="1.1"
+ width="100%" height="100%" viewBox="0 0 400 400">
+<title>firstElementChild</title>
+<h:script src="/resources/testharness.js"/>
+<h:script src="/resources/testharnessreport.js"/>
+
+<text x="200" y="40" font-size="25" fill="black" text-anchor="middle">Test of firstElementChild</text>
+<text id="parentEl" x="200" y="70" font-size="20" fill="black" text-anchor="middle">The result of this test is
+<tspan id="first_element_child" font-weight="bold">unknown.</tspan></text>
+
+<h:script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var fec = parentEl.firstElementChild;
+ assert_true(!!fec)
+ assert_equals(fec.nodeType, 1)
+ assert_equals(fec.getAttribute("id"), "first_element_child")
+})
+]]></h:script>
+</svg>
diff --git a/testing/web-platform/tests/dom/nodes/Element-firstElementChild-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-xhtml.xhtml
new file mode 100644
index 0000000000..302052b0fc
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-firstElementChild-xhtml.xhtml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>firstElementChild</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Test of firstElementChild</h1>
+<div id="log"></div>
+<p id="parentEl">The result of this test is
+<span id="first_element_child" style="font-weight:bold;">logged above.</span></p>
+<script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var fec = parentEl.firstElementChild;
+ assert_true(!!fec)
+ assert_equals(fec.nodeType, 1)
+ assert_equals(fec.getAttribute("id"), "first_element_child")
+})
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Element-firstElementChild.html b/testing/web-platform/tests/dom/nodes/Element-firstElementChild.html
new file mode 100644
index 0000000000..12a0c5946e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-firstElementChild.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>firstElementChild</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1>Test of firstElementChild</h1>
+<div id="log"></div>
+<p id="parentEl">The result of this test is
+<span id="first_element_child" style="font-weight:bold;">logged above.</span></p>
+<script>
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var fec = parentEl.firstElementChild;
+ assert_true(!!fec)
+ assert_equals(fec.nodeType, 1)
+ assert_equals(fec.getAttribute("id"), "first_element_child")
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-getElementsByClassName.html b/testing/web-platform/tests/dom/nodes/Element-getElementsByClassName.html
new file mode 100644
index 0000000000..bc87b05d25
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-getElementsByClassName.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>Element.getElementsByClassName</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var a = document.createElement("a"), b = document.createElement("b")
+ b.className = "foo"
+ a.appendChild(b)
+ var list = a.getElementsByClassName("foo")
+ assert_array_equals(list, [b])
+ var secondList = a.getElementsByClassName("foo")
+ assert_true(list === secondList || list !== secondList, "Caching is allowed.")
+}, "getElementsByClassName should work on disconnected subtrees.")
+
+test(function() {
+ var list = document.getElementsByClassName("foo")
+ assert_false(list instanceof NodeList, "NodeList")
+ assert_true(list instanceof HTMLCollection, "HTMLCollection")
+}, "Interface should be correct.")
+
+test(function() {
+ var a = document.createElement("a");
+ var b = document.createElement("b");
+ var c = document.createElement("c");
+ b.className = "foo";
+ document.body.appendChild(a);
+ this.add_cleanup(function() {document.body.removeChild(a)});
+ a.appendChild(b);
+
+ var l = a.getElementsByClassName("foo");
+ assert_true(l instanceof HTMLCollection);
+ assert_equals(l.length, 1);
+
+ c.className = "foo";
+ a.appendChild(c);
+ assert_equals(l.length, 2);
+
+ a.removeChild(c);
+ assert_equals(l.length, 1);
+}, "getElementsByClassName() should be a live collection");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-getElementsByTagName-change-document-HTMLNess-iframe.xml b/testing/web-platform/tests/dom/nodes/Element-getElementsByTagName-change-document-HTMLNess-iframe.xml
new file mode 100644
index 0000000000..f3f286eafc
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-getElementsByTagName-change-document-HTMLNess-iframe.xml
@@ -0,0 +1 @@
+<root/>
diff --git a/testing/web-platform/tests/dom/nodes/Element-getElementsByTagName-change-document-HTMLNess.html b/testing/web-platform/tests/dom/nodes/Element-getElementsByTagName-change-document-HTMLNess.html
new file mode 100644
index 0000000000..c41ee2e877
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-getElementsByTagName-change-document-HTMLNess.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe src="Element-getElementsByTagName-change-document-HTMLNess-iframe.xml"></iframe>
+<script>
+ setup({ single_test: true });
+ onload = function() {
+ var parent = document.createElement("div");
+ var child1 = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
+ child1.textContent = "xhtml:a";
+ var child2 = document.createElementNS("http://www.w3.org/1999/xhtml", "A");
+ child2.textContent = "xhtml:A";
+ var child3 = document.createElementNS("", "a");
+ child3.textContent = "a";
+ var child4 = document.createElementNS("", "A");
+ child4.textContent = "A";
+
+ parent.appendChild(child1);
+ parent.appendChild(child2);
+ parent.appendChild(child3);
+ parent.appendChild(child4);
+
+ var list = parent.getElementsByTagName("A");
+ assert_array_equals(list, [child1, child4],
+ "In an HTML document, should lowercase the tagname passed in for HTML " +
+ "elements only");
+
+ frames[0].document.documentElement.appendChild(parent);
+ assert_array_equals(list, [child1, child4],
+ "After changing document, should still be lowercasing for HTML");
+
+ assert_array_equals(parent.getElementsByTagName("A"),
+ [child2, child4],
+ "New list with same root and argument should not be lowercasing now");
+
+ // Now reinsert all those nodes into the parent, to blow away caches.
+ parent.appendChild(child1);
+ parent.appendChild(child2);
+ parent.appendChild(child3);
+ parent.appendChild(child4);
+ assert_array_equals(list, [child1, child4],
+ "After blowing away caches, should still have the same list");
+
+ assert_array_equals(parent.getElementsByTagName("A"),
+ [child2, child4],
+ "New list with same root and argument should still not be lowercasing");
+ done();
+ }
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-getElementsByTagName.html b/testing/web-platform/tests/dom/nodes/Element-getElementsByTagName.html
new file mode 100644
index 0000000000..87c4fe934a
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-getElementsByTagName.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Element.getElementsByTagName</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-getelementsbytagname">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="Document-Element-getElementsByTagName.js"></script>
+<div id="log"></div>
+<script>
+var element;
+setup(function() {
+ element = document.createElement("div");
+ element.appendChild(document.createTextNode("text"));
+ var p = element.appendChild(document.createElement("p"));
+ p.appendChild(document.createElement("a"))
+ .appendChild(document.createTextNode("link"));
+ p.appendChild(document.createElement("b"))
+ .appendChild(document.createTextNode("bold"));
+ p.appendChild(document.createElement("em"))
+ .appendChild(document.createElement("u"))
+ .appendChild(document.createTextNode("emphasized"));
+ element.appendChild(document.createComment("comment"));
+});
+
+test_getElementsByTagName(element, element);
+
+test(function() {
+ assert_array_equals(element.getElementsByTagName(element.localName), []);
+}, "Matching the context object");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-getElementsByTagNameNS.html b/testing/web-platform/tests/dom/nodes/Element-getElementsByTagNameNS.html
new file mode 100644
index 0000000000..f826afc391
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-getElementsByTagNameNS.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Element.getElementsByTagNameNS</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-getelementsbytagnamens">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="Document-Element-getElementsByTagNameNS.js"></script>
+<div id="log"></div>
+<script>
+var element;
+setup(function() {
+ element = document.createElement("div");
+ element.appendChild(document.createTextNode("text"));
+ var p = element.appendChild(document.createElement("p"));
+ p.appendChild(document.createElement("a"))
+ .appendChild(document.createTextNode("link"));
+ p.appendChild(document.createElement("b"))
+ .appendChild(document.createTextNode("bold"));
+ p.appendChild(document.createElement("em"))
+ .appendChild(document.createElement("u"))
+ .appendChild(document.createTextNode("emphasized"));
+ element.appendChild(document.createComment("comment"));
+});
+
+test_getElementsByTagNameNS(element, element);
+
+test(function() {
+ assert_array_equals(element.getElementsByTagNameNS("*", element.localName), []);
+}, "Matching the context object (wildcard namespace)");
+
+test(function() {
+ assert_array_equals(
+ element.getElementsByTagNameNS("http://www.w3.org/1999/xhtml",
+ element.localName),
+ []);
+}, "Matching the context object (specific namespace)");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-hasAttribute.html b/testing/web-platform/tests/dom/nodes/Element-hasAttribute.html
new file mode 100644
index 0000000000..26528d7569
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-hasAttribute.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Element.prototype.hasAttribute</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-hasattribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<span data-e2="2" data-F2="3" id="t"></span>
+
+<script>
+"use strict";
+
+test(() => {
+
+ const el = document.createElement("p");
+ el.setAttributeNS("foo", "x", "first");
+
+ assert_true(el.hasAttribute("x"));
+
+}, "hasAttribute should check for attribute presence, irrespective of namespace");
+
+test(() => {
+
+ const el = document.getElementById("t");
+
+ assert_true(el.hasAttribute("data-e2"));
+ assert_true(el.hasAttribute("data-E2"));
+ assert_true(el.hasAttribute("data-f2"));
+ assert_true(el.hasAttribute("data-F2"));
+
+}, "hasAttribute should work with all attribute casings");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-hasAttributes.html b/testing/web-platform/tests/dom/nodes/Element-hasAttributes.html
new file mode 100644
index 0000000000..fbb9c233b7
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-hasAttributes.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset="utf-8">
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+
+<button></button>
+<div id="foo"></div>
+<p data-foo=""></p>
+
+<script>
+test(function() {
+ var buttonElement = document.getElementsByTagName('button')[0];
+ assert_equals(buttonElement.hasAttributes(), false, 'hasAttributes() on empty element must return false.');
+
+ var emptyDiv = document.createElement('div');
+ assert_equals(emptyDiv.hasAttributes(), false, 'hasAttributes() on dynamically created empty element must return false.');
+
+}, 'element.hasAttributes() must return false when the element does not have attribute.');
+
+test(function() {
+ var divWithId = document.getElementById('foo');
+ assert_equals(divWithId.hasAttributes(), true, 'hasAttributes() on element with id attribute must return true.');
+
+ var divWithClass = document.createElement('div');
+ divWithClass.setAttribute('class', 'foo');
+ assert_equals(divWithClass.hasAttributes(), true, 'hasAttributes() on dynamically created element with class attribute must return true.');
+
+ var pWithCustomAttr = document.getElementsByTagName('p')[0];
+ assert_equals(pWithCustomAttr.hasAttributes(), true, 'hasAttributes() on element with custom attribute must return true.');
+
+ var divWithCustomAttr = document.createElement('div');
+ divWithCustomAttr.setAttribute('data-custom', 'foo');
+ assert_equals(divWithCustomAttr.hasAttributes(), true, 'hasAttributes() on dynamically created element with custom attribute must return true.');
+
+}, 'element.hasAttributes() must return true when the element has attribute.');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/dom/nodes/Element-insertAdjacentElement.html b/testing/web-platform/tests/dom/nodes/Element-insertAdjacentElement.html
new file mode 100644
index 0000000000..9eafee6a76
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-insertAdjacentElement.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<div id="target"></div>
+<div id="parent"><span id=target2></span></div>
+<div id="log" style="visibility:visible"></div>
+<span id="test1"></span>
+<span id="test2"></span>
+<span id="test3"></span>
+<span id="test4"></span>
+<script>
+var target = document.getElementById("target");
+var target2 = document.getElementById("target2");
+
+test(function() {
+ assert_throws_dom("SyntaxError", function() {
+ target.insertAdjacentElement("test", document.getElementById("test1"))
+ });
+
+ assert_throws_dom("SyntaxError", function() {
+ target2.insertAdjacentElement("test", document.getElementById("test1"))
+ });
+}, "Inserting to an invalid location should cause a Syntax Error exception")
+
+test(function() {
+ var el = target.insertAdjacentElement("beforebegin", document.getElementById("test1"));
+ assert_equals(target.previousSibling.id, "test1");
+ assert_equals(el.id, "test1");
+
+ el = target2.insertAdjacentElement("beforebegin", document.getElementById("test1"));
+ assert_equals(target2.previousSibling.id, "test1");
+ assert_equals(el.id, "test1");
+}, "Inserted element should be target element's previous sibling for 'beforebegin' case")
+
+test(function() {
+ var el = target.insertAdjacentElement("afterbegin", document.getElementById("test2"));
+ assert_equals(target.firstChild.id, "test2");
+ assert_equals(el.id, "test2");
+
+ el = target2.insertAdjacentElement("afterbegin", document.getElementById("test2"));
+ assert_equals(target2.firstChild.id, "test2");
+ assert_equals(el.id, "test2");
+}, "Inserted element should be target element's first child for 'afterbegin' case")
+
+test(function() {
+ var el = target.insertAdjacentElement("beforeend", document.getElementById("test3"));
+ assert_equals(target.lastChild.id, "test3");
+ assert_equals(el.id, "test3");
+
+ el = target2.insertAdjacentElement("beforeend", document.getElementById("test3"));
+ assert_equals(target2.lastChild.id, "test3");
+ assert_equals(el.id, "test3");
+}, "Inserted element should be target element's last child for 'beforeend' case")
+
+test(function() {
+ var el = target.insertAdjacentElement("afterend", document.getElementById("test4"));
+ assert_equals(target.nextSibling.id, "test4");
+ assert_equals(el.id, "test4");
+
+ el = target2.insertAdjacentElement("afterend", document.getElementById("test4"));
+ assert_equals(target2.nextSibling.id, "test4");
+ assert_equals(el.id, "test4");
+}, "Inserted element should be target element's next sibling for 'afterend' case")
+
+test(function() {
+ var docElement = document.documentElement;
+ docElement.style.visibility="hidden";
+
+ assert_throws_dom("HierarchyRequestError", function() {
+ var el = docElement.insertAdjacentElement("beforebegin", document.getElementById("test1"));
+ assert_equals(el, null);
+ });
+
+ var el = docElement.insertAdjacentElement("afterbegin", document.getElementById("test2"));
+ assert_equals(docElement.firstChild.id, "test2");
+ assert_equals(el.id, "test2");
+
+ el = docElement.insertAdjacentElement("beforeend", document.getElementById("test3"));
+ assert_equals(docElement.lastChild.id, "test3");
+ assert_equals(el.id, "test3");
+
+ assert_throws_dom("HierarchyRequestError", function() {
+ var el = docElement.insertAdjacentElement("afterend", document.getElementById("test4"));
+ assert_equals(el, null);
+ });
+}, "Adding more than one child to document should cause a HierarchyRequestError exception")
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-insertAdjacentText.html b/testing/web-platform/tests/dom/nodes/Element-insertAdjacentText.html
new file mode 100644
index 0000000000..be744fd49e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-insertAdjacentText.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>
+<body style="visibility:hidden">
+<div id="target"></div>
+<div id="parent"><span id=target2></span></div>
+<div id="log" style="visibility:visible"></div>
+</body>
+<script>
+var target = document.getElementById("target");
+var target2 = document.getElementById("target2");
+
+test(function() {
+ assert_throws_dom("SyntaxError", function() {
+ target.insertAdjacentText("test", "text")
+ });
+
+ assert_throws_dom("SyntaxError", function() {
+ target2.insertAdjacentText("test", "test")
+ });
+}, "Inserting to an invalid location should cause a Syntax Error exception")
+
+test(function() {
+ target.insertAdjacentText("beforebegin", "test1");
+ assert_equals(target.previousSibling.nodeValue, "test1");
+
+ target2.insertAdjacentText("beforebegin", "test1");
+ assert_equals(target2.previousSibling.nodeValue, "test1");
+}, "Inserted text node should be target element's previous sibling for 'beforebegin' case")
+
+test(function() {
+ target.insertAdjacentText("afterbegin", "test2");
+ assert_equals(target.firstChild.nodeValue, "test2");
+
+ target2.insertAdjacentText("afterbegin", "test2");
+ assert_equals(target2.firstChild.nodeValue, "test2");
+}, "Inserted text node should be target element's first child for 'afterbegin' case")
+
+test(function() {
+ target.insertAdjacentText("beforeend", "test3");
+ assert_equals(target.lastChild.nodeValue, "test3");
+
+ target2.insertAdjacentText("beforeend", "test3");
+ assert_equals(target2.lastChild.nodeValue, "test3");
+}, "Inserted text node should be target element's last child for 'beforeend' case")
+
+test(function() {
+ target.insertAdjacentText("afterend", "test4");
+ assert_equals(target.nextSibling.nodeValue, "test4");
+
+ target2.insertAdjacentText("afterend", "test4");
+ assert_equals(target.nextSibling.nodeValue, "test4");
+}, "Inserted text node should be target element's next sibling for 'afterend' case")
+
+test(function() {
+ var docElement = document.documentElement;
+ docElement.style.visibility="hidden";
+
+ assert_throws_dom("HierarchyRequestError", function() {
+ docElement.insertAdjacentText("beforebegin", "text1")
+ });
+
+ docElement.insertAdjacentText("afterbegin", "test2");
+ assert_equals(docElement.firstChild.nodeValue, "test2");
+
+ docElement.insertAdjacentText("beforeend", "test3");
+ assert_equals(docElement.lastChild.nodeValue, "test3");
+
+ assert_throws_dom("HierarchyRequestError", function() {
+ docElement.insertAdjacentText("afterend", "test4")
+ });
+}, "Adding more than one child to document should cause a HierarchyRequestError exception")
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-lastElementChild-svg.svg b/testing/web-platform/tests/dom/nodes/Element-lastElementChild-svg.svg
new file mode 100644
index 0000000000..1cec4a1308
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-lastElementChild-svg.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml"
+ version="1.1"
+ width="100%" height="100%" viewBox="0 0 400 400">
+<title>lastElementChild</title>
+<h:script src="/resources/testharness.js"/>
+<h:script src="/resources/testharnessreport.js"/>
+
+<text x="200" y="40" font-size="25" fill="black" text-anchor="middle">Test of lastElementChild</text>
+<text id="parentEl" x="200" y="70" font-size="20" fill="black" text-anchor="middle">The result of <tspan id="first_element_child">this test</tspan> is <tspan id="last_element_child" font-weight="bold">not</tspan> known.</text>
+
+<h:script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var lec = parentEl.lastElementChild;
+ assert_true(!!lec)
+ assert_equals(lec.nodeType, 1)
+ assert_equals(lec.getAttribute("id"), "last_element_child")
+})
+]]></h:script>
+</svg>
diff --git a/testing/web-platform/tests/dom/nodes/Element-lastElementChild-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Element-lastElementChild-xhtml.xhtml
new file mode 100644
index 0000000000..3150b92a42
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-lastElementChild-xhtml.xhtml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>firstElementChild</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Test of firstElementChild</h1>
+<div id="log"></div>
+<p id="parentEl">The result of <span id="first_element_child">this test</span> is <span id="last_element_child" style="font-weight:bold;">logged</span> above.</p>
+<script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var lec = parentEl.lastElementChild;
+ assert_true(!!lec)
+ assert_equals(lec.nodeType, 1)
+ assert_equals(lec.getAttribute("id"), "last_element_child")
+})
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Element-lastElementChild.html b/testing/web-platform/tests/dom/nodes/Element-lastElementChild.html
new file mode 100644
index 0000000000..de7aebdf24
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-lastElementChild.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>lastElementChild</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1>Test of lastElementChild</h1>
+<div id="log"></div>
+<p id="parentEl">The result of <span id="first_element_child">this test</span> is <span id="last_element_child" style="font-weight:bold;">logged</span> above.</p>
+<script>
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var lec = parentEl.lastElementChild;
+ assert_true(!!lec)
+ assert_equals(lec.nodeType, 1)
+ assert_equals(lec.getAttribute("id"), "last_element_child")
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-matches-init.js b/testing/web-platform/tests/dom/nodes/Element-matches-init.js
new file mode 100644
index 0000000000..254af61565
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-matches-init.js
@@ -0,0 +1,65 @@
+function init(e, method) {
+ /*
+ * This test suite tests Selectors API methods in 4 different contexts:
+ * 1. Document node
+ * 2. In-document Element node
+ * 3. Detached Element node (an element with no parent, not in the document)
+ * 4. Document Fragment node
+ *
+ * For each context, the following tests are run:
+ *
+ * The interface check tests ensure that each type of node exposes the Selectors API methods.
+ *
+ * The matches() tests are run
+ * All the selectors tested for both the valid and invalid selector tests are found in selectors.js.
+ * See comments in that file for documentation of the format used.
+ *
+ * The level2-lib.js file contains all the common test functions for running each of the aforementioned tests
+ */
+
+ var docType = "html"; // Only run tests suitable for HTML
+
+ // Prepare the nodes for testing
+ var doc = e.target.contentDocument; // Document Node tests
+
+ var element = doc.getElementById("root"); // In-document Element Node tests
+
+ //Setup the namespace tests
+ setupSpecialElements(doc, element);
+
+ var outOfScope = element.cloneNode(true); // Append this to the body before running the in-document
+ // Element tests, but after running the Document tests. This
+ // tests that no elements that are not descendants of element
+ // are selected.
+
+ traverse(outOfScope, function(elem) { // Annotate each element as being a clone; used for verifying
+ elem.setAttribute("data-clone", ""); // that none of these elements ever match.
+ });
+
+
+ var detached = element.cloneNode(true); // Detached Element Node tests
+
+ var fragment = doc.createDocumentFragment(); // Fragment Node tests
+ fragment.appendChild(element.cloneNode(true));
+
+ // Setup Tests
+ interfaceCheckMatches(method, "Document", doc);
+ interfaceCheckMatches(method, "Detached Element", detached);
+ interfaceCheckMatches(method, "Fragment", fragment);
+ interfaceCheckMatches(method, "In-document Element", element);
+
+ runSpecialMatchesTests(method, "DIV Element", element);
+ runSpecialMatchesTests(method, "NULL Element", document.createElement("null"));
+ runSpecialMatchesTests(method, "UNDEFINED Element", document.createElement("undefined"));
+
+ runInvalidSelectorTestMatches(method, "Document", doc, invalidSelectors);
+ runInvalidSelectorTestMatches(method, "Detached Element", detached, invalidSelectors);
+ runInvalidSelectorTestMatches(method, "Fragment", fragment, invalidSelectors);
+ runInvalidSelectorTestMatches(method, "In-document Element", element, invalidSelectors);
+
+ runMatchesTest(method, "In-document", doc, validSelectors, "html");
+ runMatchesTest(method, "Detached", detached, validSelectors, "html");
+ runMatchesTest(method, "Fragment", fragment, validSelectors, "html");
+
+ runMatchesTest(method, "In-document", doc, scopedSelectors, "html");
+}
diff --git a/testing/web-platform/tests/dom/nodes/Element-matches-namespaced-elements.html b/testing/web-platform/tests/dom/nodes/Element-matches-namespaced-elements.html
new file mode 100644
index 0000000000..e61b11ca61
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-matches-namespaced-elements.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>matches/webkitMatchesSelector must work when an element has a namespace</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!-- Regression tests for https://github.com/jsdom/jsdom/issues/1846, https://github.com/jsdom/jsdom/issues/2247 -->
+
+<script>
+"use strict";
+
+for (const method of ["matches", "webkitMatchesSelector"]) {
+ test(() => {
+ assert_true(document.createElementNS("", "element")[method]("element"));
+ }, `empty string namespace, ${method}`);
+
+ test(() => {
+ assert_true(document.createElementNS("urn:ns", "h")[method]("h"));
+ }, `has a namespace, ${method}`);
+
+ test(() => {
+ assert_true(document.createElementNS("urn:ns", "h")[method]("*|h"));
+ }, `has a namespace, *|, ${method}`);
+}
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-matches.html b/testing/web-platform/tests/dom/nodes/Element-matches.html
new file mode 100644
index 0000000000..de234b6639
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-matches.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>Selectors-API Level 2 Test Suite: HTML with Selectors Level 3</title>
+<!-- Selectors API Test Suite Version 3 -->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/dom/nodes/selectors.js"></script>
+<script src="/dom/nodes/ParentNode-querySelector-All.js"></script>
+<script src="Element-matches.js"></script>
+<script src="Element-matches-init.js"></script>
+<style>iframe { visibility: hidden; position: absolute; }</style>
+
+<div id="log">This test requires JavaScript.</div>
+
+<script>
+ async_test(function() {
+ var frame = document.createElement("iframe");
+ frame.onload = this.step_func_done(e => init(e, "matches" ));
+ frame.src = "/dom/nodes/ParentNode-querySelector-All-content.html#target";
+ document.body.appendChild(frame);
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-matches.js b/testing/web-platform/tests/dom/nodes/Element-matches.js
new file mode 100644
index 0000000000..a1455c671f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-matches.js
@@ -0,0 +1,135 @@
+/*
+ * Check that the matches() method exists on the given Node
+ */
+function interfaceCheckMatches(method, type, obj) {
+ if (obj.nodeType === obj.ELEMENT_NODE) {
+ test(function() {
+ assert_idl_attribute(obj, method, type + " supports " + method);
+ }, type + " supports " + method)
+ } else {
+ test(function() {
+ assert_false(method in obj, type + " supports " + method);
+ }, type + " should not support " + method)
+ }
+}
+
+function runSpecialMatchesTests(method, type, element) {
+ test(function() { // 1
+ if (element.tagName.toLowerCase() === "null") {
+ assert_true(element[method](null), "An element with the tag name '" + element.tagName.toLowerCase() + "' should match.");
+ } else {
+ assert_false(element[method](null), "An element with the tag name '" + element.tagName.toLowerCase() + "' should not match.");
+ }
+ }, type + "." + method + "(null)")
+
+ test(function() { // 2
+ if (element.tagName.toLowerCase() === "undefined") {
+ assert_true(element[method](undefined), "An element with the tag name '" + element.tagName.toLowerCase() + "' should match.");
+ } else {
+ assert_false(element[method](undefined), "An element with the tag name '" + element.tagName.toLowerCase() + "' should not match.");
+ }
+ }, type + "." + method + "(undefined)")
+
+ test(function() { // 3
+ assert_throws_js(element.ownerDocument.defaultView.TypeError, function() {
+ element[method]();
+ }, "This should throw a TypeError.")
+ }, type + "." + method + " no parameter")
+}
+
+/*
+ * Execute queries with the specified invalid selectors for matches()
+ * Only run these tests when errors are expected. Don't run for valid selector tests.
+ */
+function runInvalidSelectorTestMatches(method, type, root, selectors) {
+ if (root.nodeType === root.ELEMENT_NODE) {
+ for (var i = 0; i < selectors.length; i++) {
+ var s = selectors[i];
+ var n = s["name"];
+ var q = s["selector"];
+
+ test(function() {
+ assert_throws_dom(
+ "SyntaxError",
+ root.ownerDocument.defaultView.DOMException,
+ function() {
+ root[method](q)
+ }
+ );
+ }, type + "." + method + ": " + n + ": " + q);
+ }
+ }
+}
+
+function runMatchesTest(method, type, root, selectors, docType) {
+ var nodeType = getNodeType(root);
+
+ for (var i = 0; i < selectors.length; i++) {
+ var s = selectors[i];
+ var n = s["name"];
+ var q = s["selector"];
+ var e = s["expect"];
+ var u = s["unexpected"];
+
+ var ctx = s["ctx"];
+ var ref = s["ref"];
+
+ if ((!s["exclude"] || (s["exclude"].indexOf(nodeType) === -1 && s["exclude"].indexOf(docType) === -1))
+ && (s["testType"] & TEST_MATCH) ) {
+
+ if (ctx && !ref) {
+ test(function() {
+ var j, element, refNode;
+ for (j = 0; j < e.length; j++) {
+ element = root.querySelector("#" + e[j]);
+ refNode = root.querySelector(ctx);
+ assert_true(element[method](q, refNode), "The element #" + e[j] + " should match the selector.")
+ }
+
+ if (u) {
+ for (j = 0; j < u.length; j++) {
+ element = root.querySelector("#" + u[j]);
+ refNode = root.querySelector(ctx);
+ assert_false(element[method](q, refNode), "The element #" + u[j] + " should not match the selector.")
+ }
+ }
+ }, type + " Element." + method + ": " + n + " (with refNode Element): " + q);
+ }
+
+ if (ref) {
+ test(function() {
+ var j, element, refNodes;
+ for (j = 0; j < e.length; j++) {
+ element = root.querySelector("#" + e[j]);
+ refNodes = root.querySelectorAll(ref);
+ assert_true(element[method](q, refNodes), "The element #" + e[j] + " should match the selector.")
+ }
+
+ if (u) {
+ for (j = 0; j < u.length; j++) {
+ element = root.querySelector("#" + u[j]);
+ refNodes = root.querySelectorAll(ref);
+ assert_false(element[method](q, refNodes), "The element #" + u[j] + " should not match the selector.")
+ }
+ }
+ }, type + " Element." + method + ": " + n + " (with refNodes NodeList): " + q);
+ }
+
+ if (!ctx && !ref) {
+ test(function() {
+ for (var j = 0; j < e.length; j++) {
+ var element = root.querySelector("#" + e[j]);
+ assert_true(element[method](q), "The element #" + e[j] + " should match the selector.")
+ }
+
+ if (u) {
+ for (j = 0; j < u.length; j++) {
+ element = root.querySelector("#" + u[j]);
+ assert_false(element[method](q), "The element #" + u[j] + " should not match the selector.")
+ }
+ }
+ }, type + " Element." + method + ": " + n + " (with no refNodes): " + q);
+ }
+ }
+ }
+}
diff --git a/testing/web-platform/tests/dom/nodes/Element-nextElementSibling-svg.svg b/testing/web-platform/tests/dom/nodes/Element-nextElementSibling-svg.svg
new file mode 100644
index 0000000000..3e17cad20c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-nextElementSibling-svg.svg
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml"
+ version="1.1"
+ width="100%" height="100%" viewBox="0 0 400 400">
+<title>nextElementSibling</title>
+<h:script src="/resources/testharness.js"/>
+<h:script src="/resources/testharnessreport.js"/>
+
+<text x="200" y="40" font-size="25" fill="black" text-anchor="middle">Test of nextElementSibling</text>
+<text id="parentEl" x="200" y="70" font-size="20" fill="black" text-anchor="middle">The result of <tspan id="first_element_child">this test</tspan> is <tspan id="last_element_child" font-weight="bold">unknown.</tspan></text>
+
+<h:script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var fec = document.getElementById("first_element_child");
+ var nes = fec.nextElementSibling;
+ assert_true(!!nes)
+ assert_equals(nes.nodeType, 1)
+ assert_equals(nes.getAttribute("id"), "last_element_child")
+})
+]]></h:script>
+</svg>
diff --git a/testing/web-platform/tests/dom/nodes/Element-nextElementSibling-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Element-nextElementSibling-xhtml.xhtml
new file mode 100644
index 0000000000..915209bda2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-nextElementSibling-xhtml.xhtml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>nextElementSibling</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Test of nextElementSibling</h1>
+<div id="log"></div>
+<p id="parentEl">The result of <span id="first_element_child">this test</span> is <span id="last_element_child" style="font-weight:bold;">unknown.</span></p>
+<script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var fec = document.getElementById("first_element_child");
+ var nes = fec.nextElementSibling;
+ assert_true(!!nes)
+ assert_equals(nes.nodeType, 1)
+ assert_equals(nes.getAttribute("id"), "last_element_child")
+})
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Element-nextElementSibling.html b/testing/web-platform/tests/dom/nodes/Element-nextElementSibling.html
new file mode 100644
index 0000000000..985c602f41
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-nextElementSibling.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>nextElementSibling</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1>Test of nextElementSibling</h1>
+<div id="log"></div>
+<p id="parentEl">The result of <span id="first_element_child">this test</span> is <span id="last_element_child" style="font-weight:bold;">unknown.</span></p>
+<script>
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var fec = document.getElementById("first_element_child");
+ var nes = fec.nextElementSibling;
+ assert_true(!!nes)
+ assert_equals(nes.nodeType, 1)
+ assert_equals(nes.getAttribute("id"), "last_element_child")
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-previousElementSibling-svg.svg b/testing/web-platform/tests/dom/nodes/Element-previousElementSibling-svg.svg
new file mode 100644
index 0000000000..671d2c87f2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-previousElementSibling-svg.svg
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml"
+ version="1.1"
+ width="100%" height="100%" viewBox="0 0 400 400">
+<title>previousElementSibling</title>
+<h:script src="/resources/testharness.js"/>
+<h:script src="/resources/testharnessreport.js"/>
+
+<text x="200" y="40" font-size="25" fill="black" text-anchor="middle">Test of previousElementSibling</text>
+<text id="parentEl" x="200" y="70" font-size="20" fill="black" text-anchor="middle">The result of <tspan id="first_element_child">this test</tspan> is
+<tspan id="middle_element_child" font-weight="bold">unknown.</tspan>
+
+
+
+<tspan id="last_element_child" display="none">fnord</tspan> </text>
+
+<h:script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var lec = document.getElementById("last_element_child");
+ var pes = lec.previousElementSibling;
+ assert_true(!!pes)
+ assert_equals(pes.nodeType, 1)
+ assert_equals(pes.getAttribute("id"), "middle_element_child")
+})
+]]></h:script>
+</svg>
diff --git a/testing/web-platform/tests/dom/nodes/Element-previousElementSibling-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Element-previousElementSibling-xhtml.xhtml
new file mode 100644
index 0000000000..7fbbc6d384
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-previousElementSibling-xhtml.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>previousElementSibling</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Test of previousElementSibling</h1>
+<div id="log"></div>
+<p id="parentEl">The result of <span id="first_element_child">this test</span> is
+<span id="middle_element_child" style="font-weight:bold;">unknown.</span>
+
+
+
+<span id="last_element_child" style="display:none;">fnord</span> </p>
+<script><![CDATA[
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var lec = document.getElementById("last_element_child");
+ var pes = lec.previousElementSibling;
+ assert_true(!!pes)
+ assert_equals(pes.nodeType, 1)
+ assert_equals(pes.getAttribute("id"), "middle_element_child")
+})
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Element-previousElementSibling.html b/testing/web-platform/tests/dom/nodes/Element-previousElementSibling.html
new file mode 100644
index 0000000000..02c7b16df5
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-previousElementSibling.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>previousElementSibling</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1>Test of previousElementSibling</h1>
+<div id="log"></div>
+<p id="parentEl">The result of <span id="first_element_child">this test</span> is
+<span id="middle_element_child" style="font-weight:bold;">unknown.</span>
+
+
+
+<span id="last_element_child" style="display:none;">fnord</span> </p>
+<script>
+test(function() {
+ var parentEl = document.getElementById("parentEl");
+ var lec = document.getElementById("last_element_child");
+ var pes = lec.previousElementSibling;
+ assert_true(!!pes)
+ assert_equals(pes.nodeType, 1)
+ assert_equals(pes.getAttribute("id"), "middle_element_child")
+})
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-remove.html b/testing/web-platform/tests/dom/nodes/Element-remove.html
new file mode 100644
index 0000000000..ab642d660b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-remove.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Element.remove</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-childnode-remove">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="ChildNode-remove.js"></script>
+<div id=log></div>
+<script>
+var node, parentNode;
+setup(function() {
+ node = document.createElement("div");
+ parentNode = document.createElement("div");
+});
+testRemove(node, parentNode, "element");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-removeAttribute.html b/testing/web-platform/tests/dom/nodes/Element-removeAttribute.html
new file mode 100644
index 0000000000..df79e62cf4
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-removeAttribute.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Element.prototype.removeAttribute</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-removeattribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+
+ const el = document.createElement("p");
+ el.setAttribute("x", "first");
+ el.setAttributeNS("foo", "x", "second");
+
+ assert_equals(el.attributes.length, 2);
+ assert_equals(el.getAttribute("x"), "first");
+ assert_equals(el.getAttributeNS(null, "x"), "first");
+ assert_equals(el.getAttributeNS("foo", "x"), "second");
+
+ // removeAttribute removes the first attribute with name "x" that
+ // we set on the element, irrespective of namespace.
+ el.removeAttribute("x");
+
+ // The only attribute remaining should be the second one.
+ assert_equals(el.getAttribute("x"), "second");
+ assert_equals(el.getAttributeNS(null, "x"), null);
+ assert_equals(el.getAttributeNS("foo", "x"), "second");
+ assert_equals(el.attributes.length, 1, "one attribute");
+
+}, "removeAttribute should remove the first attribute, irrespective of namespace, when the first attribute is " +
+ "not in a namespace");
+
+test(() => {
+
+ const el = document.createElement("p");
+ el.setAttributeNS("foo", "x", "first");
+ el.setAttributeNS("foo2", "x", "second");
+
+ assert_equals(el.attributes.length, 2);
+ assert_equals(el.getAttribute("x"), "first");
+ assert_equals(el.getAttributeNS("foo", "x"), "first");
+ assert_equals(el.getAttributeNS("foo2", "x"), "second");
+
+ // removeAttribute removes the first attribute with name "x" that
+ // we set on the element, irrespective of namespace.
+ el.removeAttribute("x");
+
+ // The only attribute remaining should be the second one.
+ assert_equals(el.getAttribute("x"), "second");
+ assert_equals(el.getAttributeNS("foo", "x"), null);
+ assert_equals(el.getAttributeNS("foo2", "x"), "second");
+ assert_equals(el.attributes.length, 1, "one attribute");
+
+}, "removeAttribute should remove the first attribute, irrespective of namespace, when the first attribute is " +
+ "in a namespace");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-removeAttributeNS.html b/testing/web-platform/tests/dom/nodes/Element-removeAttributeNS.html
new file mode 100644
index 0000000000..a2773e6f1e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-removeAttributeNS.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>Element.removeAttributeNS</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="attributes.js"></script>
+<div id="log"></div>
+<script>
+var XML = "http://www.w3.org/XML/1998/namespace"
+
+test(function() {
+ var el = document.createElement("foo")
+ el.setAttributeNS(XML, "a:bb", "pass")
+ attr_is(el.attributes[0], "pass", "bb", XML, "a", "a:bb")
+ el.removeAttributeNS(XML, "a:bb")
+ assert_equals(el.attributes.length, 1)
+ attr_is(el.attributes[0], "pass", "bb", XML, "a", "a:bb")
+}, "removeAttributeNS should take a local name.")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-setAttribute-crbug-1138487.html b/testing/web-platform/tests/dom/nodes/Element-setAttribute-crbug-1138487.html
new file mode 100644
index 0000000000..9aa9ed8139
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-setAttribute-crbug-1138487.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Regression test for crbug.com/1138487.
+//
+// It was possible for a non-ASCII-lowercase string to be used when inserting
+// into the attribute collection if a hashtable encountered it during probing
+// while looking for the ASCII-lowercase equivalent.
+//
+// This caused such a string to be illegally used as an attribute name, thus
+// causing inconsistent behavior in future attribute lookup.
+test(() => {
+ const el = document.createElement('div');
+ el.setAttribute('labelXQL', 'abc');
+ el.setAttribute('_valueXQL', 'def');
+ assert_equals(el.getAttribute('labelXQL'), 'abc');
+ assert_equals(el.getAttribute('labelxql'), 'abc');
+ assert_equals(el.getAttribute('_valueXQL'), 'def');
+ assert_equals(el.getAttribute('_valuexql'), 'def');
+}, "Attributes first seen in mixed ASCII case should not be corrupted.");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-setAttribute.html b/testing/web-platform/tests/dom/nodes/Element-setAttribute.html
new file mode 100644
index 0000000000..7609406815
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-setAttribute.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Element.prototype.setAttribute</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-setattribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+
+ const el = document.createElement("p");
+ el.setAttributeNS("foo", "x", "first");
+ el.setAttributeNS("foo2", "x", "second");
+
+ el.setAttribute("x", "changed");
+
+ assert_equals(el.attributes.length, 2);
+ assert_equals(el.getAttribute("x"), "changed");
+ assert_equals(el.getAttributeNS("foo", "x"), "changed");
+ assert_equals(el.getAttributeNS("foo2", "x"), "second");
+
+}, "setAttribute should change the first attribute, irrespective of namespace");
+
+test(() => {
+ // https://github.com/whatwg/dom/issues/31
+
+ const el = document.createElement("p");
+ el.setAttribute("FOO", "bar");
+
+ assert_equals(el.getAttribute("foo"), "bar");
+ assert_equals(el.getAttribute("FOO"), "bar");
+ assert_equals(el.getAttributeNS("", "foo"), "bar");
+ assert_equals(el.getAttributeNS("", "FOO"), null);
+
+}, "setAttribute should lowercase before setting");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-siblingElement-null-svg.svg b/testing/web-platform/tests/dom/nodes/Element-siblingElement-null-svg.svg
new file mode 100644
index 0000000000..48c981b8c8
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-siblingElement-null-svg.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:h="http://www.w3.org/1999/xhtml"
+ version="1.1"
+ width="100%" height="100%" viewBox="0 0 400 400">
+<title>Null test</title>
+<h:script src="/resources/testharness.js"/>
+<h:script src="/resources/testharnessreport.js"/>
+
+<text x="200" y="40" font-size="25" fill="black" text-anchor="middle">Test of previousElementSibling and nextElementSibling returning null</text>
+<text id="parentEl" x="200" y="70" font-size="20" fill="black" text-anchor="middle">The result of this test is <tspan id="first_element_child" font-weight="bold">unknown.</tspan></text>
+
+<h:script><![CDATA[
+test(function() {
+ var fec = document.getElementById("first_element_child");
+ assert_equals(fec.previousElementSibling, null)
+ assert_equals(fec.nextElementSibling, null)
+})
+]]></h:script>
+</svg>
diff --git a/testing/web-platform/tests/dom/nodes/Element-siblingElement-null-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Element-siblingElement-null-xhtml.xhtml
new file mode 100644
index 0000000000..fcf4d54ff9
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-siblingElement-null-xhtml.xhtml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Null Test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Test of previousElementSibling and nextElementSibling returning null</h1>
+<div id="log"></div>
+<p id="parentEl">The result of this test is <span id="first_element_child" style="font-weight:bold;">unknown.</span></p>
+<script><![CDATA[
+test(function() {
+ var fec = document.getElementById("first_element_child");
+ assert_equals(fec.previousElementSibling, null)
+ assert_equals(fec.nextElementSibling, null)
+})
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Element-siblingElement-null.html b/testing/web-platform/tests/dom/nodes/Element-siblingElement-null.html
new file mode 100644
index 0000000000..a7920b4fb8
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-siblingElement-null.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Null test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1>Test of previousElementSibling and nextElementSibling returning null</h1>
+<div id="log"></div>
+<p id="parentEl">The result of this test is <span id="first_element_child" style="font-weight:bold;">unknown.</span></p>
+<script>
+test(function() {
+ var fec = document.getElementById("first_element_child");
+ assert_equals(fec.previousElementSibling, null)
+ assert_equals(fec.nextElementSibling, null)
+})
+</script>
+
diff --git a/testing/web-platform/tests/dom/nodes/Element-tagName.html b/testing/web-platform/tests/dom/nodes/Element-tagName.html
new file mode 100644
index 0000000000..43e7a2d2bf
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-tagName.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<title>Element.tagName</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var HTMLNS = "http://www.w3.org/1999/xhtml"
+ assert_equals(document.createElementNS(HTMLNS, "I").tagName, "I")
+ assert_equals(document.createElementNS(HTMLNS, "i").tagName, "I")
+ assert_equals(document.createElementNS(HTMLNS, "x:b").tagName, "X:B")
+}, "tagName should upper-case for HTML elements in HTML documents.")
+
+test(function() {
+ var SVGNS = "http://www.w3.org/2000/svg"
+ assert_equals(document.createElementNS(SVGNS, "svg").tagName, "svg")
+ assert_equals(document.createElementNS(SVGNS, "SVG").tagName, "SVG")
+ assert_equals(document.createElementNS(SVGNS, "s:svg").tagName, "s:svg")
+ assert_equals(document.createElementNS(SVGNS, "s:SVG").tagName, "s:SVG")
+
+ assert_equals(document.createElementNS(SVGNS, "textPath").tagName, "textPath");
+}, "tagName should not upper-case for SVG elements in HTML documents.")
+
+test(() => {
+ const el2 = document.createElementNS("http://example.com/", "mixedCase");
+ assert_equals(el2.tagName, "mixedCase");
+}, "tagName should not upper-case for other non-HTML namespaces");
+
+test(function() {
+ if ("DOMParser" in window) {
+ var xmlel = new DOMParser()
+ .parseFromString('<div xmlns="http://www.w3.org/1999/xhtml">Test</div>', 'text/xml')
+ .documentElement;
+ assert_equals(xmlel.tagName, "div", "tagName should be lowercase in XML")
+ var htmlel = document.importNode(xmlel, true)
+ assert_equals(htmlel.tagName, "DIV", "tagName should be uppercase in HTML")
+ }
+}, "tagName should be updated when changing ownerDocument")
+
+test(function() {
+ var xmlel = document.implementation
+ .createDocument("http://www.w3.org/1999/xhtml", "div", null)
+ .documentElement;
+ assert_equals(xmlel.tagName, "div", "tagName should be lowercase in XML")
+ var htmlel = document.importNode(xmlel, true)
+ assert_equals(htmlel.tagName, "DIV", "tagName should be uppercase in HTML")
+}, "tagName should be updated when changing ownerDocument (createDocument without prefix)")
+
+test(function() {
+ var xmlel = document.implementation
+ .createDocument("http://www.w3.org/1999/xhtml", "foo:div", null)
+ .documentElement;
+ assert_equals(xmlel.tagName, "foo:div", "tagName should be lowercase in XML")
+ var htmlel = document.importNode(xmlel, true)
+ assert_equals(htmlel.tagName, "FOO:DIV", "tagName should be uppercase in HTML")
+}, "tagName should be updated when changing ownerDocument (createDocument with prefix)")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Element-webkitMatchesSelector.html b/testing/web-platform/tests/dom/nodes/Element-webkitMatchesSelector.html
new file mode 100644
index 0000000000..107f810203
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Element-webkitMatchesSelector.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>Selectors-API Level 2 Test Suite: HTML with Selectors Level 3</title>
+<!-- Selectors API Test Suite Version 3 -->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/dom/nodes/selectors.js"></script>
+<script src="/dom/nodes/ParentNode-querySelector-All.js"></script>
+<script src="Element-matches.js"></script>
+<script src="Element-matches-init.js"></script>
+<style>iframe { visibility: hidden; position: absolute; }</style>
+
+<div id="log">This test requires JavaScript.</div>
+
+<script>
+ async_test(function() {
+ var frame = document.createElement("iframe");
+ frame.onload = this.step_func_done(e => init(e, "webkitMatchesSelector" ));
+ frame.src = "/dom/nodes/ParentNode-querySelector-All-content.html#target";
+ document.body.appendChild(frame);
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/MutationObserver-attributes.html b/testing/web-platform/tests/dom/nodes/MutationObserver-attributes.html
new file mode 100644
index 0000000000..6721b7eecd
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/MutationObserver-attributes.html
@@ -0,0 +1,406 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>MutationObservers: attributes mutations</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="mutationobservers.js"></script>
+<h1>MutationObservers: attributes mutations</h1>
+<div id="log"></div>
+
+<section style="display: none">
+<p id='n'></p>
+
+<p id='n00'></p>
+<p id='n01'></p>
+<p id='n02'></p>
+<p id='n03'></p>
+<input id="n04" type="text">
+
+<p id='n10'></p>
+<p id='n11'></p>
+<p id='n12' class='c01'></p>
+<p id='n13' class='c01 c02'></p>
+
+<p id='n20'></p>
+<p id='n21'></p>
+<p id='n22'></p>
+<p id='n23'></p>
+<p id='n24' class="c01 c02"></p>
+
+<p id='n30' class="c01 c02"></p>
+<p id='n31' class="c01 c02"></p>
+<p id='n32' class="c01 c02"></p>
+
+<p id='n40' class="c01 c02"></p>
+<p id='n41' class="c01 c02"></p>
+<p id='n42' class="c01 c02"></p>
+<p id='n43' class="c01 c02"></p>
+<p id='n44' class="c01 c02"></p>
+<p id='n45' class="c01 c02"></p>
+
+<p id='n50' class="c01 c02"></p>
+<p id='n51'></p>
+
+<p id='n60'></p>
+<p id='n61' class="c01"></p>
+<p id='n62'></p>
+
+<p id='n70' class="c01"></p>
+<p id='n71'></p>
+<input id="n72" type="text">
+
+<p id='n80'></p>
+<p id='n81'></p>
+
+<p id='n90'></p>
+<p id='n91'></p>
+<p id='n92'></p>
+
+<p id='n1000'></p>
+<p id='n1001' class='c01'></p>
+
+<p id='n2000'></p>
+<p id='n2001' class='c01'></p>
+
+<p id='n3000'></p>
+
+</section>
+
+<script>
+
+var n = document.getElementById('n');
+
+runMutationTest(n,
+ {"attributes":true},
+ [{type: "attributes", attributeName: "id"}],
+ function() { n.id = "n000";},
+ "attributes Element.id: update, no oldValue, mutation");
+
+var n00 = document.getElementById('n00');
+runMutationTest(n00,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "n00", attributeName: "id"}],
+ function() { n00.id = "n000";},
+ "attributes Element.id: update mutation");
+
+var n01 = document.getElementById('n01');
+runMutationTest(n01,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "n01", attributeName: "id"}],
+ function() { n01.id = "";},
+ "attributes Element.id: empty string update mutation");
+
+var n02 = document.getElementById('n02');
+runMutationTest(n02,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "n02", attributeName: "id"}, {type: "attributes", attributeName: "class"}],
+ function() { n02.id = "n02"; n02.setAttribute("class", "c01");},
+ "attributes Element.id: same value mutation");
+
+var n03 = document.getElementById('n03');
+runMutationTest(n03,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "n03", attributeName: "id"}],
+ function() { n03.unknown = "c02"; n03.id = "n030";},
+ "attributes Element.unknown: IDL attribute no mutation");
+
+var n04 = document.getElementById('n04');
+runMutationTest(n04,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "text", attributeName: "type"}, {type: "attributes", oldValue: "n04", attributeName: "id"}],
+ function() { n04.type = "unknown"; n04.id = "n040";},
+ "attributes HTMLInputElement.type: type update mutation");
+
+ var n10 = document.getElementById('n10');
+runMutationTest(n10,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", attributeName: "class"}],
+ function() { n10.className = "c01";},
+ "attributes Element.className: new value mutation");
+
+ var n11 = document.getElementById('n11');
+runMutationTest(n11,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", attributeName: "class"}],
+ function() { n11.className = "";},
+ "attributes Element.className: empty string update mutation");
+
+ var n12 = document.getElementById('n12');
+runMutationTest(n12,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "c01", attributeName: "class"}],
+ function() { n12.className = "c01";},
+ "attributes Element.className: same value mutation");
+
+ var n13 = document.getElementById('n13');
+runMutationTest(n13,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "c01 c02", attributeName: "class"}],
+ function() { n13.className = "c01 c02";},
+ "attributes Element.className: same multiple values mutation");
+
+ var n20 = document.getElementById('n20');
+runMutationTest(n20,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", attributeName: "class"}],
+ function() { n20.classList.add("c01");},
+ "attributes Element.classList.add: single token addition mutation");
+
+ var n21 = document.getElementById('n21');
+runMutationTest(n21,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", attributeName: "class"}],
+ function() { n21.classList.add("c01", "c02", "c03");},
+ "attributes Element.classList.add: multiple tokens addition mutation");
+
+ var n22 = document.getElementById('n22');
+runMutationTest(n22,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "n22", attributeName: "id"}],
+ function() { try { n22.classList.add("c01", "", "c03"); } catch (e) { };
+ n22.id = "n220"; },
+ "attributes Element.classList.add: syntax err/no mutation");
+
+ var n23 = document.getElementById('n23');
+runMutationTest(n23,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "n23", attributeName: "id"}],
+ function() { try { n23.classList.add("c01", "c 02", "c03"); } catch (e) { };
+ n23.id = "n230"; },
+ "attributes Element.classList.add: invalid character/no mutation");
+
+ var n24 = document.getElementById('n24');
+runMutationTest(n24,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "c01 c02", attributeName: "class"}, {type: "attributes", oldValue: "n24", attributeName: "id"}],
+ function() { n24.classList.add("c02"); n24.id = "n240";},
+ "attributes Element.classList.add: same value mutation");
+
+ var n30 = document.getElementById('n30');
+runMutationTest(n30,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "c01 c02", attributeName: "class"}],
+ function() { n30.classList.remove("c01");},
+ "attributes Element.classList.remove: single token removal mutation");
+
+ var n31 = document.getElementById('n31');
+runMutationTest(n31,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "c01 c02", attributeName: "class"}],
+ function() { n31.classList.remove("c01", "c02");},
+ "attributes Element.classList.remove: multiple tokens removal mutation");
+
+ var n32 = document.getElementById('n32');
+runMutationTest(n32,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "c01 c02", attributeName: "class"}, {type: "attributes", oldValue: "n32", attributeName: "id"}],
+ function() { n32.classList.remove("c03"); n32.id = "n320";},
+ "attributes Element.classList.remove: missing token removal mutation");
+
+ var n40 = document.getElementById('n40');
+runMutationTest(n40,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "c01 c02", attributeName: "class"}],
+ function() { n40.classList.toggle("c01");},
+ "attributes Element.classList.toggle: token removal mutation");
+
+ var n41 = document.getElementById('n41');
+runMutationTest(n41,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "c01 c02", attributeName: "class"}],
+ function() { n41.classList.toggle("c03");},
+ "attributes Element.classList.toggle: token addition mutation");
+
+ var n42 = document.getElementById('n42');
+runMutationTest(n42,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "c01 c02", attributeName: "class"}],
+ function() { n42.classList.toggle("c01", false);},
+ "attributes Element.classList.toggle: forced token removal mutation");
+
+ var n43 = document.getElementById('n43');
+runMutationTest(n43,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "n43", attributeName: "id"}],
+ function() { n43.classList.toggle("c03", false); n43.id = "n430"; },
+ "attributes Element.classList.toggle: forced missing token removal no mutation");
+
+ var n44 = document.getElementById('n44');
+runMutationTest(n44,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "n44", attributeName: "id"}],
+ function() { n44.classList.toggle("c01", true); n44.id = "n440"; },
+ "attributes Element.classList.toggle: forced existing token addition no mutation");
+
+ var n45 = document.getElementById('n45');
+runMutationTest(n45,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "c01 c02", attributeName: "class"}],
+ function() { n45.classList.toggle("c03", true);},
+ "attributes Element.classList.toggle: forced token addition mutation");
+
+ var n50 = document.getElementById('n50');
+runMutationTest(n50,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "c01 c02", attributeName: "class"}],
+ function() {
+ for (var i = 0; i < n50.attributes.length; i++) {
+ var attr = n50.attributes[i];
+ if (attr.localName === "class") {
+ attr.value = "c03";
+ }
+ };
+ },
+ "attributes Element.attributes.value: update mutation");
+
+ var n51 = document.getElementById('n51');
+runMutationTest(n51,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "n51", attributeName: "id"}],
+ function() {
+ n51.attributes[0].value = "n51";
+ },
+ "attributes Element.attributes.value: same id mutation");
+
+ var n60 = document.getElementById('n60');
+runMutationTest(n60,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "n60", attributeName: "id"}],
+ function() {
+ n60.setAttribute("id", "n601");
+ },
+ "attributes Element.setAttribute: id mutation");
+
+ var n61 = document.getElementById('n61');
+runMutationTest(n61,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "c01", attributeName: "class"}],
+ function() {
+ n61.setAttribute("class", "c01");
+ },
+ "attributes Element.setAttribute: same class mutation");
+
+ var n62 = document.getElementById('n62');
+runMutationTest(n62,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", attributeName: "classname"}],
+ function() {
+ n62.setAttribute("classname", "c01");
+ },
+ "attributes Element.setAttribute: classname mutation");
+
+ var n70 = document.getElementById('n70');
+runMutationTest(n70,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "c01", attributeName: "class"}],
+ function() {
+ n70.removeAttribute("class");
+ },
+ "attributes Element.removeAttribute: removal mutation");
+
+ var n71 = document.getElementById('n71');
+runMutationTest(n71,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "n71", attributeName: "id"}],
+ function() {
+ n71.removeAttribute("class");
+ n71.id = "n710";
+ },
+ "attributes Element.removeAttribute: removal no mutation");
+
+ var n72 = document.getElementById('n72');
+runMutationTest(n72,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "text", attributeName: "type"}, {type: "attributes", oldValue: "n72", attributeName: "id"}],
+ function() {
+ n72.removeAttribute("type");
+ n72.id = "n720";
+ },
+ "childList HTMLInputElement.removeAttribute: type removal mutation");
+
+ var n80 = document.getElementById('n80');
+runMutationTest(n80,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", attributeName: "private", attributeNamespace: "http://example.org/"}],
+ function() {
+ n80.setAttributeNS("http://example.org/", "private", "42");
+ },
+ "attributes Element.setAttributeNS: creation mutation");
+
+ var n81 = document.getElementById('n81');
+runMutationTest(n81,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", attributeName: "lang", attributeNamespace: "http://www.w3.org/XML/1998/namespace"}],
+ function() {
+ n81.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:lang", "42");
+ },
+ "attributes Element.setAttributeNS: prefixed attribute creation mutation");
+
+ var n90 = document.getElementById('n90');
+ n90.setAttributeNS("http://example.org/", "private", "42");
+runMutationTest(n90,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "42", attributeName: "private", attributeNamespace: "http://example.org/"}],
+ function() {
+ n90.removeAttributeNS("http://example.org/", "private");
+ },
+ "attributes Element.removeAttributeNS: removal mutation");
+
+ var n91 = document.getElementById('n91');
+runMutationTest(n91,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "n91", attributeName: "id"}],
+ function() {
+ n91.removeAttributeNS("http://example.org/", "private");
+ n91.id = "n910";
+ },
+ "attributes Element.removeAttributeNS: removal no mutation");
+
+ var n92 = document.getElementById('n92');
+runMutationTest(n92,
+ {"attributes":true, "attributeOldValue": true},
+ [{type: "attributes", oldValue: "n92", attributeName: "id"}],
+ function() {
+ n92.removeAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:lang");
+ n92.id = "n920";
+ },
+ "attributes Element.removeAttributeNS: prefixed attribute removal no mutation");
+
+ var n1000 = document.getElementById('n1000');
+runMutationTest(n1000,
+ {"attributes":true, "attributeOldValue": true,"attributeFilter": ["id"]},
+ [{type: "attributes", oldValue: "n1000", attributeName: "id"}],
+ function() { n1000.id = "abc"; n1000.className = "c01"},
+ "attributes/attributeFilter Element.id/Element.className: update mutation");
+
+ var n1001 = document.getElementById('n1001');
+runMutationTest(n1001,
+ {"attributes":true, "attributeOldValue": true,"attributeFilter": ["id", "class"]},
+ [{type: "attributes", oldValue: "n1001", attributeName: "id"},
+ {type: "attributes", oldValue: "c01", attributeName: "class"}],
+ function() { n1001.id = "abc"; n1001.className = "c02"; n1001.setAttribute("lang", "fr");},
+ "attributes/attributeFilter Element.id/Element.className: multiple filter update mutation");
+
+ var n2000 = document.getElementById('n2000');
+runMutationTest(n2000,
+ {"attributeOldValue": true},
+ [{type: "attributes", oldValue: "n2000", attributeName: "id"}],
+ function() { n2000.id = "abc";},
+ "attributeOldValue alone Element.id: update mutation");
+
+ var n2001 = document.getElementById('n2001');
+runMutationTest(n2001,
+ {"attributeFilter": ["id", "class"]},
+ [{type: "attributes", attributeName: "id"},
+ {type: "attributes", attributeName: "class"}],
+ function() { n2001.id = "abcd"; n2001.className = "c02"; n2001.setAttribute("lang", "fr");},
+ "attributeFilter alone Element.id/Element.className: multiple filter update mutation");
+
+ var n3000 = document.getElementById('n3000');
+runMutationTest(n3000,
+ {"subtree": true, "childList":false, "attributes" : true},
+ [{type: "attributes", attributeName: "id" }],
+ function() { n3000.textContent = "CHANGED"; n3000.id = "abc";},
+ "childList false: no childList mutation");
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/MutationObserver-callback-arguments.html b/testing/web-platform/tests/dom/nodes/MutationObserver-callback-arguments.html
new file mode 100644
index 0000000000..d64758cb4f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/MutationObserver-callback-arguments.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>MutationObserver: callback arguments</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#notify-mutation-observers">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="mo-target"></div>
+<div id="log"></div>
+<script>
+"use strict";
+
+async_test(t => {
+ const moTarget = document.querySelector("#mo-target");
+ const mo = new MutationObserver(function(records, observer) {
+ t.step(() => {
+ assert_equals(this, mo);
+ assert_equals(arguments.length, 2);
+ assert_true(Array.isArray(records));
+ assert_equals(records.length, 1);
+ assert_true(records[0] instanceof MutationRecord);
+ assert_equals(observer, mo);
+
+ mo.disconnect();
+ t.done();
+ });
+ });
+
+ mo.observe(moTarget, {attributes: true});
+ moTarget.className = "trigger-mutation";
+}, "Callback is invoked with |this| value of MutationObserver and two arguments");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/MutationObserver-characterData.html b/testing/web-platform/tests/dom/nodes/MutationObserver-characterData.html
new file mode 100644
index 0000000000..addaef03da
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/MutationObserver-characterData.html
@@ -0,0 +1,215 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>MutationObservers: characterData mutations</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="mutationobservers.js"></script>
+<h1>MutationObservers: characterData mutations</h1>
+<div id="log"></div>
+
+<section style="display: none">
+
+<p id='n'>text content</p>
+
+<p id='n00'>text content</p>
+
+<p id='n10'>CHAN</p>
+<p id='n11'>CHANGED</p>
+<p id='n12'>CHANGED</p>
+
+<p id='n20'>CHGED</p>
+<p id='n21'>CHANGED</p>
+<p id='n22'>CHANGED</p>
+
+<p id='n30'>CCCHANGED</p>
+<p id='n31'>CHANGED</p>
+
+<p id='n40'>CCCHANGED</p>
+<p id='n41'>CHANGED</p>
+
+<p id="n50"><?processing data?></p>
+
+<p id="n60"><!-- data --></p>
+
+<p id='n70'>CHANN</p>
+<p id='n71'>CHANN</p>
+
+<p id='n80'>CHANN</p>
+<p id='n81'>CHANN</p>
+
+<p id='n90'>CHANN</p>
+
+</section>
+
+<script>
+ var n = document.getElementById('n').firstChild;
+runMutationTest(n,
+ {"characterData":true},
+ [{type: "characterData"}],
+ function() { n.data = "NEW VALUE"; },
+ "characterData Text.data: simple mutation without oldValue");
+
+ var n00 = document.getElementById('n00').firstChild;
+runMutationTest(n00,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "text content" }],
+ function() { n00.data = "CHANGED"; },
+ "characterData Text.data: simple mutation");
+
+ var n10 = document.getElementById('n10').firstChild;
+runMutationTest(n10,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "CHAN" }],
+ function() { n10.appendData("GED"); },
+ "characterData Text.appendData: simple mutation");
+
+ var n11 = document.getElementById('n11').firstChild;
+runMutationTest(n11,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "CHANGED" }],
+ function() { n11.appendData(""); },
+ "characterData Text.appendData: empty string mutation");
+
+ var n12 = document.getElementById('n12').firstChild;
+runMutationTest(n12,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "CHANGED" }],
+ function() { n12.appendData(null); },
+ "characterData Text.appendData: null string mutation");
+
+ var n20 = document.getElementById('n20').firstChild;
+runMutationTest(n20,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "CHGED" }],
+ function() { n20.insertData(2, "AN"); },
+ "characterData Text.insertData: simple mutation");
+
+ var n21 = document.getElementById('n21').firstChild;
+runMutationTest(n21,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "CHANGED" }],
+ function() { n21.insertData(2, ""); },
+ "characterData Text.insertData: empty string mutation");
+
+ var n22 = document.getElementById('n22').firstChild;
+runMutationTest(n22,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "CHANGED" }],
+ function() { n22.insertData(2, null); },
+ "characterData Text.insertData: null string mutation");
+
+ var n30 = document.getElementById('n30').firstChild;
+runMutationTest(n30,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "CCCHANGED" }],
+ function() { n30.deleteData(0, 2); },
+ "characterData Text.deleteData: simple mutation");
+
+ var n31 = document.getElementById('n31').firstChild;
+runMutationTest(n31,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "CHANGED" }, {type: "characterData", oldValue: "CHANGED" }],
+ function() { n31.deleteData(0, 0); n31.data = "n31"; },
+ "characterData Text.deleteData: empty mutation");
+
+ var n40 = document.getElementById('n40').firstChild;
+runMutationTest(n40,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "CCCHANGED" }],
+ function() { n40.replaceData(0, 2, "CH"); },
+ "characterData Text.replaceData: simple mutation");
+
+ var n41 = document.getElementById('n41').firstChild;
+runMutationTest(n41,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "CHANGED" }],
+ function() { n41.replaceData(0, 0, "CH"); },
+ "characterData Text.replaceData: empty mutation");
+
+ var n50 = document.getElementById('n50').firstChild;
+runMutationTest(n50,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "?processing data?" },{type: "characterData", oldValue: "CHANGED" },{type: "characterData", oldValue: "CHANGED" }],
+ function() {
+ n50.data = "CHANGED";
+ n50.deleteData(0, 0);
+ n50.replaceData(0, 2, "CH"); },
+ "characterData ProcessingInstruction: data mutations");
+
+ var n60 = document.getElementById('n60').firstChild;
+runMutationTest(n60,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: " data " },{type: "characterData", oldValue: "CHANGED" },{type: "characterData", oldValue: "CHANGED" }],
+ function() {
+ n60.data = "CHANGED";
+ n60.deleteData(0, 0);
+ n60.replaceData(0, 2, "CH"); },
+ "characterData Comment: data mutations");
+
+ var n70 = document.getElementById('n70');
+ var r70 = null;
+ test(function () {
+ n70.appendChild(document.createTextNode("NNN"));
+ n70.appendChild(document.createTextNode("NGED"));
+ r70 = document.createRange();
+ r70.setStart(n70.firstChild, 4);
+ r70.setEnd(n70.lastChild, 1);
+ }, "Range (r70) is created");
+runMutationTest(n70.firstChild,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "CHANN" }],
+ function() { r70.deleteContents(); },
+ "characterData Range.deleteContents: child and data removal mutation");
+
+ var n71 = document.getElementById('n71');
+ var r71 = null;
+ test(function () {
+ n71.appendChild(document.createTextNode("NNN"));
+ n71.appendChild(document.createTextNode("NGED"));
+ r71 = document.createRange();
+ r71.setStart(n71.firstChild, 4);
+ r71.setEnd(n71.lastChild, 1);
+ }, "Range (r71) is created");
+runMutationTest(n71.lastChild,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "NGED"}],
+ function() { r71.deleteContents(); },
+ "characterData Range.deleteContents: child and data removal mutation (2)");
+
+ var n80 = document.getElementById('n80');
+ var r80 = null;
+ test(function () {
+ n80.appendChild(document.createTextNode("NNN"));
+ n80.appendChild(document.createTextNode("NGED"));
+ r80 = document.createRange();
+ r80.setStart(n80.firstChild, 4);
+ r80.setEnd(n80.lastChild, 1);
+ }, "Range (r80) is created");
+runMutationTest(n80.firstChild,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "CHANN" }],
+ function() { r80.extractContents(); },
+ "characterData Range.extractContents: child and data removal mutation");
+
+ var n81 = document.getElementById('n81');
+ var r81 = null;
+ test(function () {
+ n81.appendChild(document.createTextNode("NNN"));
+ n81.appendChild(document.createTextNode("NGED"));
+ r81 = document.createRange();
+ r81.setStart(n81.firstChild, 4);
+ r81.setEnd(n81.lastChild, 1);
+ }, "Range (r81) is created");
+runMutationTest(n81.lastChild,
+ {"characterData":true,"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "NGED" }],
+ function() { r81.extractContents(); },
+ "characterData Range.extractContents: child and data removal mutation (2)");
+
+ var n90 = document.getElementById('n90').firstChild;
+runMutationTest(n90,
+ {"characterDataOldValue":true},
+ [{type: "characterData", oldValue: "CHANN" }],
+ function() { n90.data = "CHANGED"; },
+ "characterData/characterDataOldValue alone Text.data: simple mutation");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/MutationObserver-childList.html b/testing/web-platform/tests/dom/nodes/MutationObserver-childList.html
new file mode 100644
index 0000000000..e4c674e027
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/MutationObserver-childList.html
@@ -0,0 +1,434 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>MutationObservers: childList mutations</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="mutationobservers.js"></script>
+<h1>MutationObservers: childList mutations</h1>
+<div id="log"></div>
+
+<section style="display: none">
+<p id='dummies'>
+<span id='d30'>text content</span>
+<span id='d35'>text content</span>
+<span id='d40'>text content</span>
+<span id='d45'>text content</span>
+<span id='d50'>text content</span>
+<span id='d51'>text content</span>
+</p>
+
+</section>
+<section style="display: none">
+<p id='n00'><span>text content</span></p>
+
+<p id='n10'><span>text content</span></p>
+<p id='n11'></p>
+<p id='n12'></p>
+<p id='n13'><span>text content</span></p>
+
+<p id='n20'>PAS</p>
+<p id='n21'>CH</p>
+
+<p id='n30'><span>text content</span></p>
+<p id='n31'><span>text content</span></p>
+<p id='n32'><span>AN</span><span>CH</span><span>GED</span></p>
+<p id='n33'><span>text content</span></p>
+<p id='n34'><span>text content</span></p>
+<p id='n35'><span>text content</span></p>
+
+<p id='n40'><span>text content</span></p>
+<p id='n41'><span>text content</span></p>
+<p id='n42'><span>CH</span><span>GED</span><span>AN</span></p>
+<p id='n43'><span>text content</span></p>
+<p id='n44'><span>text content</span></p>
+<p id='n45'><span>text content</span></p>
+
+
+<p id='n50'><span>text content</span></p>
+<p id='n51'><span>text content</span></p>
+<p id='n52'><span>NO </span><span>CHANGED</span></p>
+<p id='n53'><span>text content</span></p>
+
+<p id='n60'><span>text content</span></p>
+
+<p id='n70'><span>NO </span><span>CHANGED</span></p>
+<p id='n71'>CHANN</p>
+
+<p id='n80'><span>NO </span><span>CHANGED</span></p>
+<p id='n81'>CHANN</p>
+
+<p id='n90'><span>CHA</span><span>ED</span></p>
+<p id='n91'>CHAE</p>
+
+<p id='n100'><span id="s1">CHAN</span><span id="s2">GED</span></p>
+
+</section>
+
+<script>
+ var dummies = document.getElementById('dummies');
+
+ function createFragment() {
+ var fragment = document.createDocumentFragment();
+ fragment.appendChild(document.createTextNode("11"));
+ fragment.appendChild(document.createTextNode("22"));
+ return fragment;
+ }
+
+ var n00 = document.getElementById('n00');
+
+ runMutationTest(n00,
+ {"childList":true, "attributes":true},
+ [{type: "attributes", attributeName: "class"}],
+ function() { n00.nodeValue = ""; n00.setAttribute("class", "dummy");},
+ "childList Node.nodeValue: no mutation");
+
+ var n10 = document.getElementById('n10');
+ runMutationTest(n10,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n10.firstChild],
+ addedNodes: function() {return [n10.firstChild]}}],
+ function() { n10.textContent = "new data"; },
+ "childList Node.textContent: replace content mutation");
+
+ var n11 = document.getElementById('n11');
+ runMutationTest(n11,
+ {"childList":true},
+ [{type: "childList",
+ addedNodes: function() {return [n11.firstChild]}}],
+ function() { n11.textContent = "new data"; },
+ "childList Node.textContent: no previous content mutation");
+
+ var n12 = document.getElementById('n12');
+ runMutationTest(n12,
+ {"childList":true, "attributes":true},
+ [{type: "attributes", attributeName: "class"}],
+ function() { n12.textContent = ""; n12.setAttribute("class", "dummy");},
+ "childList Node.textContent: textContent no mutation");
+
+ var n13 = document.getElementById('n13');
+ runMutationTest(n13,
+ {"childList":true},
+ [{type: "childList", removedNodes: [n13.firstChild]}],
+ function() { n13.textContent = ""; },
+ "childList Node.textContent: empty string mutation");
+
+ var n20 = document.getElementById('n20');
+ n20.appendChild(document.createTextNode("S"));
+ runMutationTest(n20,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n20.lastChild],
+ previousSibling: n20.firstChild}],
+ function() { n20.normalize(); },
+ "childList Node.normalize mutation");
+
+ var n21 = document.getElementById('n21');
+ n21.appendChild(document.createTextNode("AN"));
+ n21.appendChild(document.createTextNode("GED"));
+ runMutationTest(n21,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n21.lastChild.previousSibling],
+ previousSibling: n21.firstChild,
+ nextSibling: n21.lastChild},
+ {type: "childList",
+ removedNodes: [n21.lastChild],
+ previousSibling: n21.firstChild}],
+ function() { n21.normalize(); },
+ "childList Node.normalize mutations");
+
+ var n30 = document.getElementById('n30');
+ var d30 = document.getElementById('d30');
+ runMutationTest(n30,
+ {"childList":true},
+ [{type: "childList",
+ addedNodes: [d30],
+ nextSibling: n30.firstChild}],
+ function() { n30.insertBefore(d30, n30.firstChild); },
+ "childList Node.insertBefore: addition mutation");
+
+ var n31 = document.getElementById('n31');
+ runMutationTest(n31,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n31.firstChild]}],
+ function() { dummies.insertBefore(n31.firstChild, dummies.firstChild); },
+ "childList Node.insertBefore: removal mutation");
+
+ var n32 = document.getElementById('n32');
+ runMutationTest(n32,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n32.firstChild.nextSibling],
+ previousSibling: n32.firstChild, nextSibling: n32.lastChild},
+ {type: "childList",
+ addedNodes: [n32.firstChild.nextSibling],
+ nextSibling: n32.firstChild}],
+ function() { n32.insertBefore(n32.firstChild.nextSibling, n32.firstChild); },
+ "childList Node.insertBefore: removal and addition mutations");
+
+ var n33 = document.getElementById('n33');
+ var f33 = createFragment();
+ runMutationTest(n33,
+ {"childList":true},
+ [{type: "childList",
+ addedNodes: [f33.firstChild, f33.lastChild],
+ nextSibling: n33.firstChild}],
+ function() { n33.insertBefore(f33, n33.firstChild); },
+ "childList Node.insertBefore: fragment addition mutations");
+
+ var n34 = document.getElementById('n34');
+ var f34 = createFragment();
+ runMutationTest(f34,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [f34.firstChild, f34.lastChild]}],
+ function() { n34.insertBefore(f34, n34.firstChild); },
+ "childList Node.insertBefore: fragment removal mutations");
+
+ var n35 = document.getElementById('n35');
+ var d35 = document.getElementById('d35');
+ runMutationTest(n35,
+ {"childList":true},
+ [{type: "childList",
+ addedNodes: [d35],
+ previousSibling: n35.firstChild}],
+ function() { n35.insertBefore(d35, null); },
+ "childList Node.insertBefore: last child addition mutation");
+
+ var n40 = document.getElementById('n40');
+ var d40 = document.getElementById('d40');
+ runMutationTest(n40,
+ {"childList":true},
+ [{type: "childList",
+ addedNodes: [d40],
+ previousSibling: n40.firstChild}],
+ function() { n40.appendChild(d40); },
+ "childList Node.appendChild: addition mutation");
+
+ var n41 = document.getElementById('n41');
+ runMutationTest(n41,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n41.firstChild]}],
+ function() { dummies.appendChild(n41.firstChild); },
+ "childList Node.appendChild: removal mutation");
+
+ var n42 = document.getElementById('n42');
+ runMutationTest(n42,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n42.firstChild.nextSibling],
+ previousSibling: n42.firstChild, nextSibling: n42.lastChild},
+ {type: "childList",
+ addedNodes: [n42.firstChild.nextSibling],
+ previousSibling: n42.lastChild}],
+ function() { n42.appendChild(n42.firstChild.nextSibling); },
+ "childList Node.appendChild: removal and addition mutations");
+
+ var n43 = document.getElementById('n43');
+ var f43 = createFragment();
+ runMutationTest(n43,
+ {"childList":true},
+ [{type: "childList",
+ addedNodes: [f43.firstChild, f43.lastChild],
+ previousSibling: n43.firstChild}],
+ function() { n43.appendChild(f43); },
+ "childList Node.appendChild: fragment addition mutations");
+
+ var n44 = document.getElementById('n44');
+ var f44 = createFragment();
+ runMutationTest(f44,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [f44.firstChild, f44.lastChild]}],
+ function() { n44.appendChild(f44); },
+ "childList Node.appendChild: fragment removal mutations");
+
+ var n45 = document.createElement('p');
+ var d45 = document.createElement('span');
+ runMutationTest(n45,
+ {"childList":true},
+ [{type: "childList",
+ addedNodes: [d45]}],
+ function() { n45.appendChild(d45); },
+ "childList Node.appendChild: addition outside document tree mutation");
+
+ var n50 = document.getElementById('n50');
+ var d50 = document.getElementById('d50');
+ runMutationTest(n50,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n50.firstChild],
+ addedNodes: [d50]}],
+ function() { n50.replaceChild(d50, n50.firstChild); },
+ "childList Node.replaceChild: replacement mutation");
+
+ var n51 = document.getElementById('n51');
+ var d51 = document.getElementById('d51');
+ runMutationTest(n51,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n51.firstChild]}],
+ function() { d51.parentNode.replaceChild(n51.firstChild, d51); },
+ "childList Node.replaceChild: removal mutation");
+
+ var n52 = document.getElementById('n52');
+ runMutationTest(n52,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n52.lastChild],
+ previousSibling: n52.firstChild},
+ {type: "childList",
+ removedNodes: [n52.firstChild],
+ addedNodes: [n52.lastChild]}],
+ function() { n52.replaceChild(n52.lastChild, n52.firstChild); },
+ "childList Node.replaceChild: internal replacement mutation");
+
+ var n53 = document.getElementById('n53');
+ runMutationTest(n53,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n53.firstChild]},
+ {type: "childList",
+ addedNodes: [n53.firstChild]}],
+ function() { n53.replaceChild(n53.firstChild, n53.firstChild); },
+ "childList Node.replaceChild: self internal replacement mutation");
+
+ var n60 = document.getElementById('n60');
+ runMutationTest(n60,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n60.firstChild]}],
+ function() { n60.removeChild(n60.firstChild); },
+ "childList Node.removeChild: removal mutation");
+
+ var n70 = document.getElementById('n70');
+ var r70 = null;
+ test(function () {
+ r70 = document.createRange();
+ r70.setStartBefore(n70.firstChild);
+ r70.setEndAfter(n70.firstChild);
+ }, "Range (r70) is created");
+ runMutationTest(n70,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n70.firstChild],
+ nextSibling: n70.lastChild}],
+ function() { r70.deleteContents(); },
+ "childList Range.deleteContents: child removal mutation");
+
+ var n71 = document.getElementById('n71');
+ var r71 = null;
+ test(function () {
+ n71.appendChild(document.createTextNode("NNN"));
+ n71.appendChild(document.createTextNode("NGED"));
+ r71 = document.createRange();
+ r71.setStart(n71.firstChild, 4);
+ r71.setEnd(n71.lastChild, 1);
+ }, "Range (r71) is created");
+ runMutationTest(n71,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n71.firstChild.nextSibling],
+ previousSibling: n71.firstChild,
+ nextSibling: n71.lastChild}],
+ function() { r71.deleteContents(); },
+ "childList Range.deleteContents: child and data removal mutation");
+
+ var n80 = document.getElementById('n80');
+ var r80 = null;
+ test(function () {
+ r80 = document.createRange();
+ r80.setStartBefore(n80.firstChild);
+ r80.setEndAfter(n80.firstChild);
+ }, "Range (r80) is created");
+ runMutationTest(n80,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n80.firstChild],
+ nextSibling: n80.lastChild}],
+ function() { r80.extractContents(); },
+ "childList Range.extractContents: child removal mutation");
+
+ var n81 = document.getElementById('n81');
+ var r81 = null;
+ test(function () {
+ n81.appendChild(document.createTextNode("NNN"));
+ n81.appendChild(document.createTextNode("NGED"));
+ r81 = document.createRange();
+ r81.setStart(n81.firstChild, 4);
+ r81.setEnd(n81.lastChild, 1);
+ }, "Range (r81) is created");
+ runMutationTest(n81,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n81.firstChild.nextSibling],
+ previousSibling: n81.firstChild,
+ nextSibling: n81.lastChild}],
+ function() { r81.extractContents(); },
+ "childList Range.extractContents: child and data removal mutation");
+
+ var n90 = document.getElementById('n90');
+ var f90 = document.createTextNode("NG");
+ var r90 = null;
+ test(function () {
+ r90 = document.createRange();
+ r90.setStartAfter(n90.firstChild);
+ r90.setEndBefore(n90.lastChild);
+ }, "Range (r90) is created");
+ runMutationTest(n90,
+ {"childList":true},
+ [{type: "childList",
+ addedNodes: [f90],
+ previousSibling: n90.firstChild,
+ nextSibling: n90.lastChild}],
+ function() { r90.insertNode(f90); },
+ "childList Range.insertNode: child insertion mutation");
+
+ var n91 = document.getElementById('n91');
+ var f91 = document.createTextNode("NG");
+ var r91 = null;
+ test(function () {
+ n91.appendChild(document.createTextNode("D"));
+ r91 = document.createRange();
+ r91.setStart(n91.firstChild, 3);
+ r91.setEnd(n91.lastChild, 0);
+ }, "Range (r91) is created");
+ runMutationTest(n91,
+ {"childList":true},
+ [{type: "childList",
+ addedNodes: function() { return [n91.lastChild.previousSibling]; },
+ previousSibling: n91.firstChild,
+ nextSibling: n91.lastChild},
+ {type: "childList",
+ addedNodes: [f91],
+ previousSibling: n91.firstChild,
+ nextSibling: function () { return n91.lastChild.previousSibling; } }],
+ function() { r91.insertNode(f91); },
+ "childList Range.insertNode: children insertion mutation");
+
+ var n100 = document.getElementById('n100');
+ var f100 = document.createElement("span");
+ var r100 = null;
+ test(function () {
+ r100 = document.createRange();
+ r100.setStartBefore(n100.firstChild);
+ r100.setEndAfter(n100.lastChild);
+ }, "Range (r100) is created");
+ runMutationTest(n100,
+ {"childList":true},
+ [{type: "childList",
+ removedNodes: [n100.firstChild],
+ nextSibling: n100.lastChild},
+ {type: "childList",
+ removedNodes: [n100.lastChild]},
+ {type: "childList",
+ addedNodes: [f100] }],
+ function() { r100.surroundContents(f100); },
+ "childList Range.surroundContents: children removal and addition mutation");
+
+</script>
+
+
diff --git a/testing/web-platform/tests/dom/nodes/MutationObserver-cross-realm-callback-report-exception.html b/testing/web-platform/tests/dom/nodes/MutationObserver-cross-realm-callback-report-exception.html
new file mode 100644
index 0000000000..7d05c045b3
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/MutationObserver-cross-realm-callback-report-exception.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>MutationObserver 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 target = frames[0].document.body;
+ const mo = new frames[0].MutationObserver(new frames[1].Function(`throw new parent.frames[2].Error("PASS");`));
+
+ mo.observe(target, { childList: true, subtree: true });
+ target.append("foo");
+
+ t.step_timeout(() => {
+ assert_array_equals(onerrorCalls, ["frame1"]);
+ t.done();
+ }, 4);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/MutationObserver-disconnect.html b/testing/web-platform/tests/dom/nodes/MutationObserver-disconnect.html
new file mode 100644
index 0000000000..883edecf74
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/MutationObserver-disconnect.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>MutationObservers: disconnect</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1>MutationObservers: disconnect</h1>
+<div id="log"></div>
+<section style="display: none">
+<p id='n00'></p>
+</section>
+<script>
+var n00 = document.getElementById('n00');
+var parentTest = async_test("subtree mutations");
+function masterMO(sequence, obs) {
+ parentTest.step(function() {
+ assert_equals(sequence.length, 4, "mutation records must match");
+ });
+ parentTest.done();
+}
+parentTest.step(function() {
+ (new MutationObserver(masterMO)).observe(n00.parentNode, {"subtree": true, "attributes": true});
+});
+
+var disconnectTest = async_test("disconnect discarded some mutations");
+function observerCallback(sequence, obs) {
+ disconnectTest.step(function() {
+ assert_equals(sequence.length, 1);
+ assert_equals(sequence[0].type, "attributes");
+ assert_equals(sequence[0].attributeName, "id");
+ assert_equals(sequence[0].oldValue, "latest");
+ disconnectTest.done();
+ });
+}
+
+var observer;
+disconnectTest.step(function() {
+ observer = new MutationObserver(observerCallback);
+ observer.observe(n00, {"attributes": true});
+ n00.id = "foo";
+ n00.id = "bar";
+ observer.disconnect();
+ observer.observe(n00, {"attributes": true, "attributeOldValue": true});
+ n00.id = "latest";
+ observer.disconnect();
+ observer.observe(n00, {"attributes": true, "attributeOldValue": true});
+ n00.id = "n0000";
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/MutationObserver-document.html b/testing/web-platform/tests/dom/nodes/MutationObserver-document.html
new file mode 100644
index 0000000000..4662b23459
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/MutationObserver-document.html
@@ -0,0 +1,167 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>MutationObservers: takeRecords</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="mutationobservers.js"></script>
+<h1>MutationObservers: document mutations</h1>
+<div id="log"></div>
+
+<script id='s001'>
+ var setupTest = async_test("setup test");
+ var insertionTest = async_test("parser insertion mutations");
+ var insertionTest2 = async_test("parser script insertion mutation");
+ var testCounter = 0;
+
+ function masterMO(sequence, obs) {
+ testCounter++;
+ if (testCounter == 1) {
+ insertionTest.step(
+ function () {
+ checkRecords(document, sequence,
+ [{type: "childList",
+ addedNodes: function () {
+ return [ document.getElementById("n00") ];
+ },
+ previousSibling: function () {
+ return document.getElementById("s001");
+ },
+ target: document.body},
+ {type: "childList",
+ addedNodes: function () {
+ return [ document.getElementById("s002") ];
+ },
+ previousSibling: function () {
+ return document.getElementById("n00");
+ },
+ target: document.body},
+ {type: "childList",
+ addedNodes: function () {
+ return [ document.getElementById("s002").firstChild ];
+ },
+ target: function () {
+ return document.getElementById("s002");
+ }}]);
+ });
+ } else if (testCounter == 2) {
+ insertionTest2.step(
+ function () {
+ checkRecords(document, sequence,
+ [{type: "childList",
+ addedNodes: function () {
+ return [ document.getElementById("inserted_script") ];
+ },
+ target: function () {
+ return document.getElementById("n00");
+ }},
+ {type: "childList",
+ addedNodes: function () {
+ return [ document.getElementById("inserted_element") ];
+ },
+ previousSibling: function () {
+ return document.getElementById("s002");
+ },
+ target: document.body}
+ ]);
+ });
+ }
+ }
+ var document_observer;
+ var newElement;
+ setupTest.step(function() {
+ document_observer = new MutationObserver(masterMO);
+ newElement = document.createElement("span");
+ document_observer.observe(document, {subtree:true,childList:true});
+ newElement.id = "inserted_element";
+ newElement.setAttribute("style", "display: none");
+ newElement.textContent = "my new span for n00";
+ });
+</script><p id='n00'></p><script id='s002'>
+ var newScript = document.createElement("script");
+ setupTest.step(function() {
+ newScript.textContent = "document.body.appendChild(newElement);";
+ newScript.id = "inserted_script";
+ document.getElementById("n00").appendChild(newScript);
+ });
+ if (testCounter < 1) {
+ insertionTest.step(
+ function () {
+ assert_unreached("document observer did not trigger");
+ });
+ }
+</script><script id='s003'>
+ setupTest.step(function() {
+ document_observer.disconnect();
+ });
+ if (testCounter < 2) {
+ insertionTest2.step(
+ function () {
+ assert_unreached("document observer did not trigger");
+ });
+ }
+ insertionTest.done();
+ insertionTest2.done();
+</script>
+
+<p id='n012'></p><div id='d01'>
+<script id='s011'>
+ var removalTest = async_test("removal of parent during parsing");
+ var d01 = document.getElementById("d01");
+ testCounter = 0;
+
+ function removalMO(sequence, obs) {
+ testCounter++;
+ if (testCounter == 1) {
+ removalTest.step(
+ function () {
+ checkRecords(document, sequence,
+ [{type: "childList",
+ removedNodes: function () {
+ return [ d01 ];
+ },
+ previousSibling: function () {
+ return document.getElementById("n012");
+ },
+ target: document.body}]);
+ });
+ } else if (testCounter == 2) {
+ removalTest.step(
+ function () {
+ checkRecords(document, sequence,
+ [{type: "childList",
+ addedNodes: function () {
+ return [ document.getElementById("s012") ];
+ },
+ previousSibling: function () {
+ return document.getElementById("n012");
+ },
+ target: document.body},
+ {type: "childList",
+ addedNodes: function () {
+ return [ document.getElementById("s012").firstChild ];
+ },
+ target: function () {
+ return document.getElementById("s012");
+ }}]);
+ });
+ }
+ }
+ var document2_observer;
+ setupTest.step(function() {
+ document2_observer = new MutationObserver(removalMO);
+ document2_observer.observe(document, {subtree:true,childList:true});
+ d01.parentNode.removeChild(d01);
+ });
+</script><p id='n01'></p></div><script id='s012'>
+ setupTest.step(function() {
+ document2_observer.disconnect();
+ });
+ if (testCounter < 2) {
+ removalTest.step(
+ function () {
+ assert_unreached("document observer did not trigger");
+ });
+ }
+ removalTest.done();
+ setupTest.done();
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/MutationObserver-inner-outer.html b/testing/web-platform/tests/dom/nodes/MutationObserver-inner-outer.html
new file mode 100644
index 0000000000..9f6d871417
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/MutationObserver-inner-outer.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>MutationObservers: innerHTML, outerHTML mutations</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="mutationobservers.js"></script>
+<h1>MutationObservers: innerHTML, outerHTML mutations</h1>
+<div id="log"></div>
+
+<section style="display: none">
+
+<p id='n00'>old text</p>
+
+<p id='n01'>old text</p>
+
+<div id='n02'><p>old text</p></div>
+</section>
+
+<script>
+ var n00;
+ var n00oldText;
+ var n01;
+ var n01oldText;
+ var n02;
+
+ setup(function() {
+ n00 = document.getElementById('n00');
+ n00oldText = n00.firstChild;
+ n01 = document.getElementById('n01');
+ n01oldText = n01.firstChild;
+ n02 = document.getElementById('n02');
+ })
+
+ runMutationTest(n00,
+ {childList:true,attributes:true},
+ [{type: "childList",
+ removedNodes: [n00oldText],
+ addedNodes: function() {
+ return [document.getElementById("n00").firstChild];
+ }},
+ {type: "attributes", attributeName: "class"}],
+ function() { n00.innerHTML = "new text"; n00.className = "c01"},
+ "innerHTML mutation");
+
+ runMutationTest(n01,
+ {childList:true},
+ [{type: "childList",
+ removedNodes: [n01oldText],
+ addedNodes: function() {
+ return [document.getElementById("n01").firstChild,
+ document.getElementById("n01").lastChild];
+ }}],
+ function() { n01.innerHTML = "<span>new</span><span>text</span>"; },
+ "innerHTML with 2 children mutation");
+
+ runMutationTest(n02,
+ {childList:true},
+ [{type: "childList",
+ removedNodes: [n02.firstChild],
+ addedNodes: function() {
+ return [n02.firstChild];
+ }}],
+ function() { n02.firstChild.outerHTML = "<p>next text</p>"; },
+ "outerHTML mutation");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/MutationObserver-sanity.html b/testing/web-platform/tests/dom/nodes/MutationObserver-sanity.html
new file mode 100644
index 0000000000..a4f6382b94
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/MutationObserver-sanity.html
@@ -0,0 +1,95 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ test(() => {
+ var m = new MutationObserver(() => {});
+ assert_throws_js(TypeError, () => {
+ m.observe(document, {});
+ });
+ }, "Should throw if none of childList, attributes, characterData are true");
+
+ test(() => {
+ var m = new MutationObserver(() => {});
+ m.observe(document, { childList: true });
+ m.disconnect();
+ }, "Should not throw if childList is true");
+
+ test(() => {
+ var m = new MutationObserver(() => {});
+ m.observe(document, { attributes: true });
+ m.disconnect();
+ }, "Should not throw if attributes is true");
+
+ test(() => {
+ var m = new MutationObserver(() => {});
+ m.observe(document, { characterData: true });
+ m.disconnect();
+ }, "Should not throw if characterData is true");
+
+ test(() => {
+ var m = new MutationObserver(() => {});
+ m.observe(document, { attributeOldValue: true });
+ m.disconnect();
+ }, "Should not throw if attributeOldValue is true and attributes is omitted");
+
+ test(() => {
+ var m = new MutationObserver(() => {});
+ m.observe(document, { characterDataOldValue: true });
+ m.disconnect();
+ }, "Should not throw if characterDataOldValue is true and characterData is omitted");
+
+ test(() => {
+ var m = new MutationObserver(() => {});
+ m.observe(document, { attributes: ["abc"] });
+ m.disconnect();
+ }, "Should not throw if attributeFilter is present and attributes is omitted");
+
+ test(() => {
+ var m = new MutationObserver(() => {});
+ assert_throws_js(TypeError, () => {
+ m.observe(document, { childList: true, attributeOldValue: true,
+ attributes: false });
+ });
+ }, "Should throw if attributeOldValue is true and attributes is false");
+
+ test(() => {
+ var m = new MutationObserver(() => {});
+ m.observe(document, { childList: true, attributeOldValue: true,
+ attributes: true });
+ m.disconnect();
+ }, "Should not throw if attributeOldValue and attributes are both true");
+
+ test(() => {
+ var m = new MutationObserver(() => {});
+ assert_throws_js(TypeError, () => {
+ m.observe(document, { childList: true, attributeFilter: ["abc"],
+ attributes: false });
+ });
+ }, "Should throw if attributeFilter is present and attributes is false");
+
+ test(() => {
+ var m = new MutationObserver(() => {});
+ m.observe(document, { childList: true, attributeFilter: ["abc"],
+ attributes: true });
+ m.disconnect();
+ }, "Should not throw if attributeFilter is present and attributes is true");
+
+ test(() => {
+ var m = new MutationObserver(() => {});
+ assert_throws_js(TypeError, () => {
+ m.observe(document, { childList: true, characterDataOldValue: true,
+ characterData: false });
+ });
+ }, "Should throw if characterDataOldValue is true and characterData is false");
+
+ test(() => {
+ var m = new MutationObserver(() => {});
+ m.observe(document, { childList: true, characterDataOldValue: true,
+ characterData: true });
+ m.disconnect();
+ }, "Should not throw if characterDataOldValue is true and characterData is true");
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/MutationObserver-takeRecords.html b/testing/web-platform/tests/dom/nodes/MutationObserver-takeRecords.html
new file mode 100644
index 0000000000..6a27ef77ec
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/MutationObserver-takeRecords.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>MutationObservers: takeRecords</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="mutationobservers.js"></script>
+<h1>MutationObservers: takeRecords</h1>
+<div id="log"></div>
+
+<section style="display: none">
+
+<p id='n00'></p>
+
+</section>
+
+<script>
+
+ var n00 = document.getElementById('n00');
+
+ var unused = async_test("unreachabled test");
+
+ var observer;
+ unused.step(function () {
+ observer = new MutationObserver(unused.unreached_func("the observer callback should not fire"));
+ observer.observe(n00, { "subtree": true,
+ "childList": true,
+ "attributes": true,
+ "characterData": true,
+ "attributeOldValue": true,
+ "characterDataOldValue": true});
+ n00.id = "foo";
+ n00.id = "bar";
+ n00.className = "bar";
+ n00.textContent = "old data";
+ n00.firstChild.data = "new data";
+ });
+
+ test(function() {
+ checkRecords(n00, observer.takeRecords(), [{type: "attributes", attributeName: "id", oldValue: "n00"},
+ {type: "attributes", attributeName: "id", oldValue: "foo"},
+ {type: "attributes", attributeName: "class"},
+ {type: "childList", addedNodes: [n00.firstChild]},
+ {type: "characterData", oldValue: "old data", target: n00.firstChild}]);
+ }, "All records present");
+
+ test(function() {
+ checkRecords(n00, observer.takeRecords(), []);
+ }, "No more records present");
+</script>
+<script>
+ unused.done();
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-appendChild-cereactions-vs-script.window.js b/testing/web-platform/tests/dom/nodes/Node-appendChild-cereactions-vs-script.window.js
new file mode 100644
index 0000000000..bc0b8ad6dc
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-appendChild-cereactions-vs-script.window.js
@@ -0,0 +1,27 @@
+const results = [];
+test(() => {
+ class Script1 extends HTMLScriptElement {
+ constructor() {
+ super();
+ }
+ connectedCallback() {
+ results.push("ce connected s1");
+ }
+ }
+ class Script2 extends HTMLScriptElement {
+ constructor() {
+ super();
+ }
+ connectedCallback() {
+ results.push("ce connected s2");
+ }
+ }
+ customElements.define("script-1", Script1, { extends: "script" });
+ customElements.define("script-2", Script2, { extends: "script" });
+ const s1 = new Script1();
+ s1.textContent = "results.push('s1')";
+ const s2 = new Script2();
+ s2.textContent = "results.push('s2')";
+ document.body.append(s1, s2);
+ assert_array_equals(results, ["s1", "s2", "ce connected s1", "ce connected s2"]);
+}, "Custom element reactions follow script execution");
diff --git a/testing/web-platform/tests/dom/nodes/Node-appendChild.html b/testing/web-platform/tests/dom/nodes/Node-appendChild.html
new file mode 100644
index 0000000000..8264cb11a5
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-appendChild.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Node.appendChild</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-appendchild">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<iframe src=about:blank></iframe>
+<script>
+// TODO: Exhaustive tests
+function testLeaf(node, desc) {
+ // WebIDL.
+ test(function() {
+ assert_throws_js(TypeError, function() { node.appendChild(null) })
+ }, "Appending null to a " + desc)
+
+ // Pre-insert step 1.
+ test(function() {
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() { node.appendChild(document.createTextNode("fail")) })
+ }, "Appending to a " + desc)
+}
+
+// WebIDL.
+test(function() {
+ assert_throws_js(TypeError, function() { document.body.appendChild(null) })
+ assert_throws_js(TypeError, function() { document.body.appendChild({'a':'b'}) })
+}, "WebIDL tests")
+
+// WebIDL and pre-insert step 1.
+test(function() {
+ testLeaf(document.createTextNode("Foo"), "text node")
+ testLeaf(document.createComment("Foo"), "comment")
+ testLeaf(document.doctype, "doctype")
+}, "Appending to a leaf node.")
+
+// Pre-insert step 5.
+test(function() {
+ var frameDoc = frames[0].document
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() { document.body.appendChild(frameDoc) })
+}, "Appending a document")
+
+// Pre-insert step 8.
+test(function() {
+ var frameDoc = frames[0].document
+ var s = frameDoc.createElement("a")
+ assert_equals(s.ownerDocument, frameDoc)
+ document.body.appendChild(s)
+ assert_equals(s.ownerDocument, document)
+}, "Adopting an orphan")
+test(function() {
+ var frameDoc = frames[0].document
+ var s = frameDoc.createElement("b")
+ assert_equals(s.ownerDocument, frameDoc)
+ frameDoc.body.appendChild(s)
+ assert_equals(s.ownerDocument, frameDoc)
+ document.body.appendChild(s)
+ assert_equals(s.ownerDocument, document)
+}, "Adopting a non-orphan")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-baseURI.html b/testing/web-platform/tests/dom/nodes/Node-baseURI.html
new file mode 100644
index 0000000000..e9e9d76a10
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-baseURI.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<title>Node.baseURI</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+const elementTests = [
+ {
+ name: "elements belonging to document",
+ creator: () => {
+ const element = document.createElement("div");
+ document.body.appendChild(element);
+ return element;
+ }
+ },
+ {
+ name: "elements unassigned to document",
+ creator: () => document.createElement("div")
+ },
+ {
+ name: "elements belonging to document fragments",
+ creator: () => {
+ const fragment = document.createDocumentFragment();
+ const element = document.createElement("div");
+ fragment.appendChild(element);
+ return element;
+ }
+ },
+ {
+ name: "elements belonging to document fragments in document",
+ creator: () => {
+ const fragment = document.createDocumentFragment();
+ const element = document.createElement("div");
+ fragment.appendChild(element);
+ document.body.appendChild(fragment);
+ return element;
+ }
+ },
+];
+
+const attributeTests = [
+ {
+ name: "attributes unassigned to element",
+ creator: () => document.createAttribute("class")
+ },
+ ...elementTests.map(({ name, creator }) => ({
+ name: "attributes in " + name,
+ creator: () => {
+ const element = creator();
+ element.setAttribute("class", "abc");
+ return element.getAttributeNode("class");
+ }
+ }))
+];
+
+for (const { name, creator } of [...elementTests, ...attributeTests]) {
+ test(function() {
+ const node = creator();
+ assert_equals(node.baseURI, document.URL);
+ }, `For ${name}, baseURI should be document URL`)
+}
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-childNodes.html b/testing/web-platform/tests/dom/nodes/Node-childNodes.html
new file mode 100644
index 0000000000..0d38df37b2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-childNodes.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Node.childNodes</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-childnodes">
+<link rel=author title="Tim Taubert" href="mailto:ttaubert@mozilla.com">
+<link rel=author title="Ms2ger" href="mailto:Ms2ger@gmail.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div style="display: none">
+ <ul id='test'><li>1</li><li>2</li><li>3</li><li>4</li></ul>
+</div>
+<script>
+test(function() {
+ var element = document.createElement("p");
+ assert_equals(element.childNodes, element.childNodes);
+}, "Caching of Node.childNodes");
+
+var check_parent_node = function(node) {
+ assert_array_equals(node.childNodes, []);
+
+ var children = node.childNodes;
+ var child = document.createElement("p");
+ node.appendChild(child);
+ assert_equals(node.childNodes, children);
+ assert_array_equals(children, [child]);
+ assert_equals(children.item(0), child);
+
+ var child2 = document.createComment("comment");
+ node.appendChild(child2);
+ assert_array_equals(children, [child, child2]);
+ assert_equals(children.item(0), child);
+ assert_equals(children.item(1), child2);
+
+ assert_false(2 in children);
+ assert_equals(children[2], undefined);
+ assert_equals(children.item(2), null);
+};
+
+test(function() {
+ check_parent_node(document.createElement("p"));
+}, "Node.childNodes on an Element.");
+
+test(function() {
+ check_parent_node(document.createDocumentFragment());
+}, "Node.childNodes on a DocumentFragment.");
+
+test(function() {
+ check_parent_node(new Document());
+}, "Node.childNodes on a Document.");
+
+test(function() {
+ var node = document.createElement("div");
+ var kid1 = document.createElement("p");
+ var kid2 = document.createTextNode("hey");
+ var kid3 = document.createElement("span");
+ node.appendChild(kid1);
+ node.appendChild(kid2);
+ node.appendChild(kid3);
+
+ var list = node.childNodes;
+ assert_array_equals([...list], [kid1, kid2, kid3]);
+
+ var keys = list.keys();
+ assert_false(keys instanceof Array);
+ keys = [...keys];
+ assert_array_equals(keys, [0, 1, 2]);
+
+ var values = list.values();
+ assert_false(values instanceof Array);
+ values = [...values];
+ assert_array_equals(values, [kid1, kid2, kid3]);
+
+ var entries = list.entries();
+ assert_false(entries instanceof Array);
+ entries = [...entries];
+ assert_equals(entries.length, keys.length);
+ assert_equals(entries.length, values.length);
+ for (var i = 0; i < entries.length; ++i) {
+ assert_array_equals(entries[i], [keys[i], values[i]]);
+ }
+
+ var cur = 0;
+ var thisObj = {};
+ list.forEach(function(value, key, listObj) {
+ assert_equals(listObj, list);
+ assert_equals(this, thisObj);
+ assert_equals(value, values[cur]);
+ assert_equals(key, keys[cur]);
+ cur++;
+ }, thisObj);
+ assert_equals(cur, entries.length);
+
+ assert_equals(list[Symbol.iterator], Array.prototype[Symbol.iterator]);
+ assert_equals(list.keys, Array.prototype.keys);
+ if (Array.prototype.values) {
+ assert_equals(list.values, Array.prototype.values);
+ }
+ assert_equals(list.entries, Array.prototype.entries);
+ assert_equals(list.forEach, Array.prototype.forEach);
+}, "Iterator behavior of Node.childNodes");
+
+
+test(() => {
+ var node = document.getElementById("test");
+ var children = node.childNodes;
+ assert_true(children instanceof NodeList);
+ var li = document.createElement("li");
+ assert_equals(children.length, 4);
+
+ node.appendChild(li);
+ assert_equals(children.length, 5);
+
+ node.removeChild(li);
+ assert_equals(children.length, 4);
+}, "Node.childNodes should be a live collection");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-cloneNode-XMLDocument.html b/testing/web-platform/tests/dom/nodes/Node-cloneNode-XMLDocument.html
new file mode 100644
index 0000000000..2c63c77530
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-cloneNode-XMLDocument.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Cloning of an XMLDocument</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-node-clonenode">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-clone">
+
+<!-- This is testing in particular "that implements the same interfaces as node" -->
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+ const doc = document.implementation.createDocument("namespace", "");
+
+ assert_equals(
+ doc.constructor, XMLDocument,
+ "Precondition check: document.implementation.createDocument() creates an XMLDocument"
+ );
+
+ const clone = doc.cloneNode(true);
+
+ assert_equals(clone.constructor, XMLDocument);
+}, "Created with createDocument");
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-cloneNode-document-with-doctype.html b/testing/web-platform/tests/dom/nodes/Node-cloneNode-document-with-doctype.html
new file mode 100644
index 0000000000..21963084d2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-cloneNode-document-with-doctype.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Cloning of a document with a doctype</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-node-clonenode">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-clone">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+ const doctype = document.implementation.createDocumentType("name", "publicId", "systemId");
+ const doc = document.implementation.createDocument("namespace", "", doctype);
+
+ const clone = doc.cloneNode(true);
+
+ assert_equals(clone.childNodes.length, 1, "Only one child node");
+ assert_equals(clone.childNodes[0].nodeType, Node.DOCUMENT_TYPE_NODE, "Is a document fragment");
+ assert_equals(clone.childNodes[0].name, "name");
+ assert_equals(clone.childNodes[0].publicId, "publicId");
+ assert_equals(clone.childNodes[0].systemId, "systemId");
+}, "Created with the createDocument/createDocumentType");
+
+test(() => {
+ const doc = document.implementation.createHTMLDocument();
+
+ const clone = doc.cloneNode(true);
+
+ assert_equals(clone.childNodes.length, 2, "Two child nodes");
+ assert_equals(clone.childNodes[0].nodeType, Node.DOCUMENT_TYPE_NODE, "Is a document fragment");
+ assert_equals(clone.childNodes[0].name, "html");
+ assert_equals(clone.childNodes[0].publicId, "");
+ assert_equals(clone.childNodes[0].systemId, "");
+}, "Created with the createHTMLDocument");
+
+test(() => {
+ const parser = new window.DOMParser();
+ const doc = parser.parseFromString("<!DOCTYPE html><html></html>", "text/html");
+
+ const clone = doc.cloneNode(true);
+
+ assert_equals(clone.childNodes.length, 2, "Two child nodes");
+ assert_equals(clone.childNodes[0].nodeType, Node.DOCUMENT_TYPE_NODE, "Is a document fragment");
+ assert_equals(clone.childNodes[0].name, "html");
+ assert_equals(clone.childNodes[0].publicId, "");
+ assert_equals(clone.childNodes[0].systemId, "");
+}, "Created with DOMParser");
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-cloneNode-external-stylesheet-no-bc.sub.html b/testing/web-platform/tests/dom/nodes/Node-cloneNode-external-stylesheet-no-bc.sub.html
new file mode 100644
index 0000000000..bce6074aad
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-cloneNode-external-stylesheet-no-bc.sub.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>cloneNode on a stylesheet link in a browsing-context-less document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!-- Regression test for https://github.com/jsdom/jsdom/issues/2497 -->
+
+<script>
+"use strict";
+
+setup({ single_test: true });
+
+const doc = document.implementation.createHTMLDocument();
+
+// Bug was only triggered by absolute URLs, for some reason...
+const absoluteURL = new URL("/common/canvas-frame.css", location.href);
+doc.head.innerHTML = `<link rel="stylesheet" href="${absoluteURL}">`;
+
+// Test passes if this does not throw/crash
+doc.cloneNode(true);
+
+done();
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-cloneNode-on-inactive-document-crash.html b/testing/web-platform/tests/dom/nodes/Node-cloneNode-on-inactive-document-crash.html
new file mode 100644
index 0000000000..cbd7a1e6a5
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-cloneNode-on-inactive-document-crash.html
@@ -0,0 +1,6 @@
+<iframe id="i"></iframe>
+<script>
+var doc = i.contentDocument;
+i.remove();
+doc.cloneNode();
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-cloneNode-svg.html b/testing/web-platform/tests/dom/nodes/Node-cloneNode-svg.html
new file mode 100644
index 0000000000..9d4704b074
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-cloneNode-svg.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Cloning of SVG elements and attributes</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-node-clonenode">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-clone">
+<!-- regression test for https://github.com/jsdom/jsdom/issues/1601 -->
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<svg xmlns:xlink='http://www.w3.org/1999/xlink'><use xlink:href='#test'></use></svg>
+
+<script>
+"use strict";
+
+const svg = document.querySelector("svg");
+const clone = svg.cloneNode(true);
+
+test(() => {
+
+ assert_equals(clone.namespaceURI, "http://www.w3.org/2000/svg");
+ assert_equals(clone.prefix, null);
+ assert_equals(clone.localName, "svg");
+ assert_equals(clone.tagName, "svg");
+
+}, "cloned <svg> should have the right properties");
+
+test(() => {
+
+ const attr = clone.attributes[0];
+
+ assert_equals(attr.namespaceURI, "http://www.w3.org/2000/xmlns/");
+ assert_equals(attr.prefix, "xmlns");
+ assert_equals(attr.localName, "xlink");
+ assert_equals(attr.name, "xmlns:xlink");
+ assert_equals(attr.value, "http://www.w3.org/1999/xlink");
+
+}, "cloned <svg>'s xmlns:xlink attribute should have the right properties");
+
+test(() => {
+
+ const use = clone.firstElementChild;
+ assert_equals(use.namespaceURI, "http://www.w3.org/2000/svg");
+ assert_equals(use.prefix, null);
+ assert_equals(use.localName, "use");
+ assert_equals(use.tagName, "use");
+
+}, "cloned <use> should have the right properties");
+
+test(() => {
+
+ const use = clone.firstElementChild;
+ const attr = use.attributes[0];
+
+ assert_equals(attr.namespaceURI, "http://www.w3.org/1999/xlink");
+ assert_equals(attr.prefix, "xlink");
+ assert_equals(attr.localName, "href");
+ assert_equals(attr.name, "xlink:href");
+ assert_equals(attr.value, "#test");
+
+}, "cloned <use>'s xlink:href attribute should have the right properties");
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-cloneNode.html b/testing/web-platform/tests/dom/nodes/Node-cloneNode.html
new file mode 100644
index 0000000000..e97259dace
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-cloneNode.html
@@ -0,0 +1,346 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Node.cloneNode</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-clonenode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+function assert_equal_node(nodeA, nodeB) {
+ assert_equals(nodeB.nodeType, nodeA.nodeType, "nodeType");
+ assert_equals(nodeB.nodeName, nodeA.nodeName, "nodeName");
+
+ if (nodeA.nodeType === Node.ELEMENT_NODE) {
+ assert_equals(nodeB.prefix, nodeA.prefix, "prefix");
+ assert_equals(nodeB.namespaceURI, nodeA.namespaceURI, "namespaceURI");
+ assert_equals(nodeB.localName, nodeA.localName, "localName");
+ assert_equals(nodeB.tagName, nodeA.tagName, "tagName");
+ assert_not_equals(nodeB.attributes != nodeA.attributes, "attributes");
+ assert_equals(nodeB.attributes.length, nodeA.attributes.length,
+ "attributes.length");
+ for (var i = 0, il = nodeA.attributes.length; i < il; ++i) {
+ assert_not_equals(nodeB.attributes[i], nodeA.attributes[i],
+ "attributes[" + i + "]");
+ assert_equals(nodeB.attributes[i].name, nodeA.attributes[i].name,
+ "attributes[" + i + "].name");
+ assert_equals(nodeB.attributes[i].prefix, nodeA.attributes[i].prefix,
+ "attributes[" + i + "].prefix");
+ assert_equals(nodeB.attributes[i].namespaceURI, nodeA.attributes[i].namespaceURI,
+ "attributes[" + i + "].namespaceURI");
+ assert_equals(nodeB.attributes[i].value, nodeA.attributes[i].value,
+ "attributes[" + i + "].value");
+ }
+ }
+}
+
+function check_copy(orig, copy, type) {
+ assert_not_equals(orig, copy, "Object equality");
+ assert_equal_node(orig, copy, "Node equality");
+ assert_true(orig instanceof type, "original instanceof " + type);
+ assert_true(copy instanceof type, "copy instanceof " + type);
+}
+
+function create_element_and_check(localName, typeName) {
+ test(function() {
+ assert_true(typeName in window, typeName + " is not supported");
+ var element = document.createElement(localName);
+ var copy = element.cloneNode();
+ check_copy(element, copy, window[typeName]);
+ }, "createElement(" + localName + ")");
+}
+
+// test1: createElement
+create_element_and_check("a", "HTMLAnchorElement");
+create_element_and_check("abbr", "HTMLElement");
+create_element_and_check("acronym", "HTMLElement");
+create_element_and_check("address", "HTMLElement");
+create_element_and_check("area", "HTMLAreaElement");
+create_element_and_check("article", "HTMLElement");
+create_element_and_check("aside", "HTMLElement");
+create_element_and_check("audio", "HTMLAudioElement");
+create_element_and_check("b", "HTMLElement");
+create_element_and_check("base", "HTMLBaseElement");
+create_element_and_check("bdi", "HTMLElement");
+create_element_and_check("bdo", "HTMLElement");
+create_element_and_check("bgsound", "HTMLElement");
+create_element_and_check("big", "HTMLElement");
+create_element_and_check("blockquote","HTMLElement");
+create_element_and_check("body", "HTMLBodyElement");
+create_element_and_check("br", "HTMLBRElement");
+create_element_and_check("button", "HTMLButtonElement");
+create_element_and_check("canvas", "HTMLCanvasElement");
+create_element_and_check("caption", "HTMLTableCaptionElement");
+create_element_and_check("center", "HTMLElement");
+create_element_and_check("cite", "HTMLElement");
+create_element_and_check("code", "HTMLElement");
+create_element_and_check("col", "HTMLTableColElement");
+create_element_and_check("colgroup", "HTMLTableColElement");
+create_element_and_check("data", "HTMLDataElement");
+create_element_and_check("datalist", "HTMLDataListElement");
+create_element_and_check("dialog", "HTMLDialogElement");
+create_element_and_check("dd", "HTMLElement");
+create_element_and_check("del", "HTMLModElement");
+create_element_and_check("details", "HTMLElement");
+create_element_and_check("dfn", "HTMLElement");
+create_element_and_check("dir", "HTMLDirectoryElement");
+create_element_and_check("div", "HTMLDivElement");
+create_element_and_check("dl", "HTMLDListElement");
+create_element_and_check("dt", "HTMLElement");
+create_element_and_check("embed", "HTMLEmbedElement");
+create_element_and_check("fieldset", "HTMLFieldSetElement");
+create_element_and_check("figcaption","HTMLElement");
+create_element_and_check("figure", "HTMLElement");
+create_element_and_check("font", "HTMLFontElement");
+create_element_and_check("footer", "HTMLElement");
+create_element_and_check("form", "HTMLFormElement");
+create_element_and_check("frame", "HTMLFrameElement");
+create_element_and_check("frameset", "HTMLFrameSetElement");
+create_element_and_check("h1", "HTMLHeadingElement");
+create_element_and_check("h2", "HTMLHeadingElement");
+create_element_and_check("h3", "HTMLHeadingElement");
+create_element_and_check("h4", "HTMLHeadingElement");
+create_element_and_check("h5", "HTMLHeadingElement");
+create_element_and_check("h6", "HTMLHeadingElement");
+create_element_and_check("head", "HTMLHeadElement");
+create_element_and_check("header", "HTMLElement");
+create_element_and_check("hgroup", "HTMLElement");
+create_element_and_check("hr", "HTMLHRElement");
+create_element_and_check("html", "HTMLHtmlElement");
+create_element_and_check("i", "HTMLElement");
+create_element_and_check("iframe", "HTMLIFrameElement");
+create_element_and_check("img", "HTMLImageElement");
+create_element_and_check("input", "HTMLInputElement");
+create_element_and_check("ins", "HTMLModElement");
+create_element_and_check("isindex", "HTMLElement");
+create_element_and_check("kbd", "HTMLElement");
+create_element_and_check("label", "HTMLLabelElement");
+create_element_and_check("legend", "HTMLLegendElement");
+create_element_and_check("li", "HTMLLIElement");
+create_element_and_check("link", "HTMLLinkElement");
+create_element_and_check("main", "HTMLElement");
+create_element_and_check("map", "HTMLMapElement");
+create_element_and_check("mark", "HTMLElement");
+create_element_and_check("marquee", "HTMLElement");
+create_element_and_check("meta", "HTMLMetaElement");
+create_element_and_check("meter", "HTMLMeterElement");
+create_element_and_check("nav", "HTMLElement");
+create_element_and_check("nobr", "HTMLElement");
+create_element_and_check("noframes", "HTMLElement");
+create_element_and_check("noscript", "HTMLElement");
+create_element_and_check("object", "HTMLObjectElement");
+create_element_and_check("ol", "HTMLOListElement");
+create_element_and_check("optgroup", "HTMLOptGroupElement");
+create_element_and_check("option", "HTMLOptionElement");
+create_element_and_check("output", "HTMLOutputElement");
+create_element_and_check("p", "HTMLParagraphElement");
+create_element_and_check("param", "HTMLParamElement");
+create_element_and_check("pre", "HTMLPreElement");
+create_element_and_check("progress", "HTMLProgressElement");
+create_element_and_check("q", "HTMLQuoteElement");
+create_element_and_check("rp", "HTMLElement");
+create_element_and_check("rt", "HTMLElement");
+create_element_and_check("ruby", "HTMLElement");
+create_element_and_check("s", "HTMLElement");
+create_element_and_check("samp", "HTMLElement");
+create_element_and_check("script", "HTMLScriptElement");
+create_element_and_check("section", "HTMLElement");
+create_element_and_check("select", "HTMLSelectElement");
+create_element_and_check("small", "HTMLElement");
+create_element_and_check("source", "HTMLSourceElement");
+create_element_and_check("spacer", "HTMLElement");
+create_element_and_check("span", "HTMLSpanElement");
+create_element_and_check("strike", "HTMLElement");
+create_element_and_check("style", "HTMLStyleElement");
+create_element_and_check("sub", "HTMLElement");
+create_element_and_check("summary", "HTMLElement");
+create_element_and_check("sup", "HTMLElement");
+create_element_and_check("table", "HTMLTableElement");
+create_element_and_check("tbody", "HTMLTableSectionElement");
+create_element_and_check("td", "HTMLTableCellElement");
+create_element_and_check("template", "HTMLTemplateElement");
+create_element_and_check("textarea", "HTMLTextAreaElement");
+create_element_and_check("th", "HTMLTableCellElement");
+create_element_and_check("time", "HTMLTimeElement");
+create_element_and_check("title", "HTMLTitleElement");
+create_element_and_check("tr", "HTMLTableRowElement");
+create_element_and_check("tt", "HTMLElement");
+create_element_and_check("track", "HTMLTrackElement");
+create_element_and_check("u", "HTMLElement");
+create_element_and_check("ul", "HTMLUListElement");
+create_element_and_check("var", "HTMLElement");
+create_element_and_check("video", "HTMLVideoElement");
+create_element_and_check("unknown", "HTMLUnknownElement");
+create_element_and_check("wbr", "HTMLElement");
+
+test(function() {
+ var fragment = document.createDocumentFragment();
+ var copy = fragment.cloneNode();
+ check_copy(fragment, copy, DocumentFragment);
+}, "createDocumentFragment");
+
+test(function() {
+ var text = document.createTextNode("hello world");
+ var copy = text.cloneNode();
+ check_copy(text, copy, Text);
+ assert_equals(text.data, copy.data);
+ assert_equals(text.wholeText, copy.wholeText);
+}, "createTextNode");
+
+test(function() {
+ var comment = document.createComment("a comment");
+ var copy = comment.cloneNode();
+ check_copy(comment, copy, Comment);
+ assert_equals(comment.data, copy.data);
+}, "createComment");
+
+test(function() {
+ var el = document.createElement("foo");
+ el.setAttribute("a", "b");
+ el.setAttribute("c", "d");
+ var c = el.cloneNode();
+ check_copy(el, c, Element);
+}, "createElement with attributes")
+
+test(function() {
+ var el = document.createElementNS("http://www.w3.org/1999/xhtml", "foo:div");
+ var c = el.cloneNode();
+ check_copy(el, c, HTMLDivElement);
+}, "createElementNS HTML")
+
+test(function() {
+ var el = document.createElementNS("http://www.example.com/", "foo:div");
+ var c = el.cloneNode();
+ check_copy(el, c, Element);
+}, "createElementNS non-HTML")
+
+test(function() {
+ var pi = document.createProcessingInstruction("target", "data");
+ var copy = pi.cloneNode();
+ check_copy(pi, copy, ProcessingInstruction);
+ assert_equals(pi.data, copy.data, "data");
+ assert_equals(pi.target, pi.target, "target");
+}, "createProcessingInstruction");
+
+test(function() {
+ var attr = document.createAttribute("class");
+ var copy = attr.cloneNode();
+ check_copy(attr, copy, Attr);
+ assert_equals(attr.namespaceURI, copy.namespaceURI);
+ assert_equals(attr.prefix, copy.prefix);
+ assert_equals(attr.localName, copy.localName);
+ assert_equals(attr.value, copy.value);
+
+ attr.value = "abc";
+ assert_equals(attr.namespaceURI, copy.namespaceURI);
+ assert_equals(attr.prefix, copy.prefix);
+ assert_equals(attr.localName, copy.localName);
+ assert_not_equals(attr.value, copy.value);
+
+ var copy2 = attr.cloneNode();
+ check_copy(attr, copy2, Attr);
+ assert_equals(attr.namespaceURI, copy.namespaceURI);
+ assert_equals(attr.prefix, copy.prefix);
+ assert_equals(attr.localName, copy2.localName);
+ assert_equals(attr.value, copy2.value);
+}, "createAttribute");
+
+test(function() {
+ var attr = document.createAttributeNS("http://www.w3.org/1999/xhtml", "foo:class");
+ var copy = attr.cloneNode();
+ check_copy(attr, copy, Attr);
+ assert_equals(attr.namespaceURI, copy.namespaceURI);
+ assert_equals(attr.prefix, copy.prefix);
+ assert_equals(attr.localName, copy.localName);
+ assert_equals(attr.value, copy.value);
+
+ attr.value = "abc";
+ assert_equals(attr.namespaceURI, copy.namespaceURI);
+ assert_equals(attr.prefix, copy.prefix);
+ assert_equals(attr.localName, copy.localName);
+ assert_not_equals(attr.value, copy.value);
+
+ var copy2 = attr.cloneNode();
+ check_copy(attr, copy2, Attr);
+ assert_equals(attr.namespaceURI, copy.namespaceURI);
+ assert_equals(attr.prefix, copy.prefix);
+ assert_equals(attr.localName, copy2.localName);
+ assert_equals(attr.value, copy2.value);
+}, "createAttributeNS");
+
+test(function() {
+ var doctype = document.implementation.createDocumentType("html", "public", "system");
+ var copy = doctype.cloneNode();
+ check_copy(doctype, copy, DocumentType);
+ assert_equals(doctype.name, copy.name, "name");
+ assert_equals(doctype.publicId, copy.publicId, "publicId");
+ assert_equals(doctype.systemId, copy.systemId, "systemId");
+}, "implementation.createDocumentType");
+
+test(function() {
+ var doc = document.implementation.createDocument(null, null);
+ var copy = doc.cloneNode();
+ check_copy(doc, copy, Document);
+ assert_equals(doc.charset, "UTF-8", "charset value");
+ assert_equals(doc.charset, copy.charset, "charset equality");
+ assert_equals(doc.contentType, "application/xml", "contentType value");
+ assert_equals(doc.contentType, copy.contentType, "contentType equality");
+ assert_equals(doc.URL, "about:blank", "URL value")
+ assert_equals(doc.URL, copy.URL, "URL equality");
+ assert_equals(doc.compatMode, "CSS1Compat", "compatMode value");
+ assert_equals(doc.compatMode, copy.compatMode, "compatMode equality");
+}, "implementation.createDocument");
+
+test(function() {
+ var html = document.implementation.createHTMLDocument("title");
+ var copy = html.cloneNode();
+ check_copy(html, copy, Document);
+ assert_equals(copy.title, "", "title value");
+}, "implementation.createHTMLDocument");
+
+test(function() {
+ var parent = document.createElement("div");
+ var child1 = document.createElement("div");
+ var child2 = document.createElement("div");
+ var grandChild = document.createElement("div");
+
+ child2.appendChild(grandChild);
+ parent.appendChild(child1);
+ parent.appendChild(child2);
+
+ var deep = true;
+ var copy = parent.cloneNode(deep);
+
+ check_copy(parent, copy, HTMLDivElement);
+ assert_equals(copy.childNodes.length, 2,
+ "copy.childNodes.length with deep copy");
+
+ check_copy(child1, copy.childNodes[0], HTMLDivElement);
+ assert_equals(copy.childNodes[0].childNodes.length, 0,
+ "copy.childNodes[0].childNodes.length");
+
+ check_copy(child2, copy.childNodes[1], HTMLDivElement);
+ assert_equals(copy.childNodes[1].childNodes.length, 1,
+ "copy.childNodes[1].childNodes.length");
+ check_copy(grandChild, copy.childNodes[1].childNodes[0], HTMLDivElement);
+
+ deep = false;
+ copy = parent.cloneNode(deep);
+
+ check_copy(parent, copy, HTMLDivElement);
+ assert_equals(copy.childNodes.length, 0,
+ "copy.childNodes.length with non-deep copy");
+}, "node with children");
+
+test(() => {
+ const proto = Object.create(HTMLElement.prototype),
+ node = document.createElement("hi");
+ Object.setPrototypeOf(node, proto);
+ assert_true(proto.isPrototypeOf(node));
+ const clone = node.cloneNode();
+ assert_false(proto.isPrototypeOf(clone));
+ assert_true(HTMLUnknownElement.prototype.isPrototypeOf(clone));
+ const deepClone = node.cloneNode(true);
+ assert_false(proto.isPrototypeOf(deepClone));
+ assert_true(HTMLUnknownElement.prototype.isPrototypeOf(deepClone));
+}, "Node with custom prototype")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-compareDocumentPosition.html b/testing/web-platform/tests/dom/nodes/Node-compareDocumentPosition.html
new file mode 100644
index 0000000000..afae60aad1
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-compareDocumentPosition.html
@@ -0,0 +1,87 @@
+<!doctype html>
+<title>Node.compareDocumentPosition() tests</title>
+<link rel=author title="Aryeh Gregor" href=ayg@aryeh.name>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+testNodes.forEach(function(referenceName) {
+ var reference = eval(referenceName);
+ testNodes.forEach(function(otherName) {
+ var other = eval(otherName);
+ test(function() {
+ var result = reference.compareDocumentPosition(other);
+
+ // "If other and reference are the same object, return zero and
+ // terminate these steps."
+ if (other === reference) {
+ assert_equals(result, 0);
+ return;
+ }
+
+ // "If other and reference are not in the same tree, return the result of
+ // adding DOCUMENT_POSITION_DISCONNECTED,
+ // DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC, and either
+ // DOCUMENT_POSITION_PRECEDING or DOCUMENT_POSITION_FOLLOWING, with the
+ // constraint that this is to be consistent, together and terminate these
+ // steps."
+ if (furthestAncestor(reference) !== furthestAncestor(other)) {
+ // TODO: Test that it's consistent.
+ assert_in_array(result, [Node.DOCUMENT_POSITION_DISCONNECTED +
+ Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC +
+ Node.DOCUMENT_POSITION_PRECEDING,
+ Node.DOCUMENT_POSITION_DISCONNECTED +
+ Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC +
+ Node.DOCUMENT_POSITION_FOLLOWING]);
+ return;
+ }
+
+ // "If other is an ancestor of reference, return the result of
+ // adding DOCUMENT_POSITION_CONTAINS to DOCUMENT_POSITION_PRECEDING
+ // and terminate these steps."
+ var ancestor = reference.parentNode;
+ while (ancestor && ancestor !== other) {
+ ancestor = ancestor.parentNode;
+ }
+ if (ancestor === other) {
+ assert_equals(result, Node.DOCUMENT_POSITION_CONTAINS +
+ Node.DOCUMENT_POSITION_PRECEDING);
+ return;
+ }
+
+ // "If other is a descendant of reference, return the result of adding
+ // DOCUMENT_POSITION_CONTAINED_BY to DOCUMENT_POSITION_FOLLOWING and
+ // terminate these steps."
+ ancestor = other.parentNode;
+ while (ancestor && ancestor !== reference) {
+ ancestor = ancestor.parentNode;
+ }
+ if (ancestor === reference) {
+ assert_equals(result, Node.DOCUMENT_POSITION_CONTAINED_BY +
+ Node.DOCUMENT_POSITION_FOLLOWING);
+ return;
+ }
+
+ // "If other is preceding reference return DOCUMENT_POSITION_PRECEDING
+ // and terminate these steps."
+ var prev = previousNode(reference);
+ while (prev && prev !== other) {
+ prev = previousNode(prev);
+ }
+ if (prev === other) {
+ assert_equals(result, Node.DOCUMENT_POSITION_PRECEDING);
+ return;
+ }
+
+ // "Return DOCUMENT_POSITION_FOLLOWING."
+ assert_equals(result, Node.DOCUMENT_POSITION_FOLLOWING);
+ }, referenceName + ".compareDocumentPosition(" + otherName + ")");
+ });
+});
+
+testDiv.parentNode.removeChild(testDiv);
+</script>
+<!-- vim: set expandtab tabstop=2 shiftwidth=2: -->
diff --git a/testing/web-platform/tests/dom/nodes/Node-constants.html b/testing/web-platform/tests/dom/nodes/Node-constants.html
new file mode 100644
index 0000000000..33e7c10e73
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-constants.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<title>Node constants</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../constants.js"></script>
+<div id="log"></div>
+<script>
+var objects;
+setup(function() {
+ objects = [
+ [Node, "Node interface object"],
+ [Node.prototype, "Node prototype object"],
+ [document.createElement("foo"), "Element object"],
+ [document.createTextNode("bar"), "Text object"]
+ ]
+})
+testConstants(objects, [
+ ["ELEMENT_NODE", 1],
+ ["ATTRIBUTE_NODE", 2],
+ ["TEXT_NODE", 3],
+ ["CDATA_SECTION_NODE", 4],
+ ["ENTITY_REFERENCE_NODE", 5],
+ ["ENTITY_NODE", 6],
+ ["PROCESSING_INSTRUCTION_NODE", 7],
+ ["COMMENT_NODE", 8],
+ ["DOCUMENT_NODE", 9],
+ ["DOCUMENT_TYPE_NODE", 10],
+ ["DOCUMENT_FRAGMENT_NODE", 11],
+ ["NOTATION_NODE", 12]
+], "nodeType")
+testConstants(objects, [
+ ["DOCUMENT_POSITION_DISCONNECTED", 0x01],
+ ["DOCUMENT_POSITION_PRECEDING", 0x02],
+ ["DOCUMENT_POSITION_FOLLOWING", 0x04],
+ ["DOCUMENT_POSITION_CONTAINS", 0x08],
+ ["DOCUMENT_POSITION_CONTAINED_BY", 0x10],
+ ["DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC", 0x20]
+], "createDocumentPosition")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-contains-xml.xml b/testing/web-platform/tests/dom/nodes/Node-contains-xml.xml
new file mode 100644
index 0000000000..f9b20d68d6
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-contains-xml.xml
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Node.nodeName</title>
+<link rel="author" title="Olli Pettay" href="mailto:Olli@Pettay.fi"/>
+<link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"/>
+<div id="test">
+ <input type="button" id="testbutton"/>
+ <a id="link">Link text</a>
+</div>
+<script>
+<![CDATA[
+test(function() {
+ assert_throws_js(TypeError, function() {
+ document.contains();
+ });
+ assert_throws_js(TypeError, function() {
+ document.contains(9);
+ });
+}, "Should throw TypeError if the arguments are wrong.");
+
+test(function() {
+ assert_equals(document.contains(null), false, "Document shouldn't contain null.");
+}, "contains(null) should be false");
+
+test(function() {
+ assert_equals(document.contains(document), true, "Document should contain itself!");
+ assert_equals(document.contains(document.createElement("foo")), false, "Document shouldn't contain element which is't in the document");
+ assert_equals(document.contains(document.createTextNode("foo")), false, "Document shouldn't contain text node which is't in the document");
+}, "document.contains");
+
+test(function() {
+ var tb = document.getElementById("testbutton");
+ assert_equals(tb.contains(tb), true, "Element should contain itself.")
+ assert_equals(document.contains(tb), true, "Document should contain element in it!");
+ assert_equals(document.documentElement.contains(tb), true, "Element should contain element in it!");
+}, "contains with a button");
+
+test(function() {
+ var link = document.getElementById("link");
+ var text = link.firstChild;
+ assert_equals(document.contains(text), true, "Document should contain a text node in it.");
+ assert_equals(link.contains(text), true, "Element should contain a text node in it.");
+ assert_equals(text.contains(text), true, "Text node should contain itself.");
+ assert_equals(text.contains(link), false, "text node shouldn't contain its parent.");
+}, "contains with a text node");
+
+test(function() {
+ var pi = document.createProcessingInstruction("adf", "asd");
+ assert_equals(pi.contains(document), false, "Processing instruction shouldn't contain document");
+ assert_equals(document.contains(pi), false, "Document shouldn't contain newly created processing instruction");
+ document.documentElement.appendChild(pi);
+ assert_equals(document.contains(pi), true, "Document should contain processing instruction");
+}, "contains with a processing instruction");
+
+test(function() {
+ if ("createContextualFragment" in document.createRange()) {
+ var df = document.createRange().createContextualFragment("<div>foo</div>");
+ assert_equals(df.contains(df.firstChild), true, "Document fragment should contain its child");
+ assert_equals(df.contains(df.firstChild.firstChild), true,
+ "Document fragment should contain its descendant");
+ assert_equals(df.contains(df), true, "Document fragment should contain itself.");
+ }
+}, "contains with a document fragment");
+
+test(function() {
+ var d = document.implementation.createHTMLDocument("");
+ assert_equals(document.contains(d), false,
+ "Document shouldn't contain another document.");
+ assert_equals(document.contains(d.createElement("div")), false,
+ "Document shouldn't contain an element from another document.");
+ assert_equals(document.contains(d.documentElement), false,
+ "Document shouldn't contain an element from another document.");
+}, "contaibs with another document");
+]]>
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Node-contains.html b/testing/web-platform/tests/dom/nodes/Node-contains.html
new file mode 100644
index 0000000000..c44f072b11
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-contains.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<title>Node.contains() tests</title>
+<link rel=author title="Aryeh Gregor" href=ayg@aryeh.name>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+testNodes.forEach(function(referenceName) {
+ var reference = eval(referenceName);
+
+ test(function() {
+ assert_false(reference.contains(null));
+ }, referenceName + ".contains(null)");
+
+ testNodes.forEach(function(otherName) {
+ var other = eval(otherName);
+ test(function() {
+ var ancestor = other;
+ while (ancestor && ancestor !== reference) {
+ ancestor = ancestor.parentNode;
+ }
+ if (ancestor === reference) {
+ assert_true(reference.contains(other));
+ } else {
+ assert_false(reference.contains(other));
+ }
+ }, referenceName + ".contains(" + otherName + ")");
+ });
+});
+
+testDiv.parentNode.removeChild(testDiv);
+</script>
+<!-- vim: set expandtab tabstop=2 shiftwidth=2: -->
diff --git a/testing/web-platform/tests/dom/nodes/Node-insertBefore.html b/testing/web-platform/tests/dom/nodes/Node-insertBefore.html
new file mode 100644
index 0000000000..ecb4d18314
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-insertBefore.html
@@ -0,0 +1,297 @@
+<!DOCTYPE html>
+<title>Node.insertBefore</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<!-- First test shared pre-insertion checks that work similarly for replaceChild
+ and insertBefore -->
+<script>
+ var insertFunc = Node.prototype.insertBefore;
+</script>
+<script src="pre-insertion-validation-notfound.js"></script>
+<script src="pre-insertion-validation-hierarchy.js"></script>
+<script>
+preInsertionValidateHierarchy("insertBefore");
+
+function testLeafNode(nodeName, createNodeFunction) {
+ test(function() {
+ var node = createNodeFunction();
+ assert_throws_js(TypeError, function() { node.insertBefore(null, null) })
+ }, "Calling insertBefore with a non-Node first argument on a leaf node " + nodeName + " must throw TypeError.")
+ test(function() {
+ var node = createNodeFunction();
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() { node.insertBefore(document.createTextNode("fail"), null) })
+ // Would be step 2.
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() { node.insertBefore(node, null) })
+ // Would be step 3.
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() { node.insertBefore(node, document.createTextNode("child")) })
+ }, "Calling insertBefore an a leaf node " + nodeName + " must throw HIERARCHY_REQUEST_ERR.")
+}
+
+test(function() {
+ // WebIDL: first argument.
+ assert_throws_js(TypeError, function() { document.body.insertBefore(null, null) })
+ assert_throws_js(TypeError, function() { document.body.insertBefore(null, document.body.firstChild) })
+ assert_throws_js(TypeError, function() { document.body.insertBefore({'a':'b'}, document.body.firstChild) })
+}, "Calling insertBefore with a non-Node first argument must throw TypeError.")
+
+test(function() {
+ // WebIDL: second argument.
+ assert_throws_js(TypeError, function() { document.body.insertBefore(document.createTextNode("child")) })
+ assert_throws_js(TypeError, function() { document.body.insertBefore(document.createTextNode("child"), {'a':'b'}) })
+}, "Calling insertBefore with second argument missing, or other than Node, null, or undefined, must throw TypeError.")
+
+testLeafNode("DocumentType", function () { return document.doctype; } )
+testLeafNode("Text", function () { return document.createTextNode("Foo") })
+testLeafNode("Comment", function () { return document.createComment("Foo") })
+testLeafNode("ProcessingInstruction", function () { return document.createProcessingInstruction("foo", "bar") })
+
+test(function() {
+ // Step 2.
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() { document.body.insertBefore(document.body, document.getElementById("log")) })
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() { document.body.insertBefore(document.documentElement, document.getElementById("log")) })
+}, "Calling insertBefore with an inclusive ancestor of the context object must throw HIERARCHY_REQUEST_ERR.")
+
+// Step 3.
+test(function() {
+ var a = document.createElement("div");
+ var b = document.createElement("div");
+ var c = document.createElement("div");
+ assert_throws_dom("NotFoundError", function() {
+ a.insertBefore(b, c);
+ });
+}, "Calling insertBefore with a reference child whose parent is not the context node must throw a NotFoundError.")
+
+// Step 4.1.
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var doc2 = document.implementation.createHTMLDocument("title2");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(doc2, doc.documentElement);
+ });
+
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(doc.createTextNode("text"), doc.documentElement);
+ });
+}, "If the context node is a document, inserting a document or text node should throw a HierarchyRequestError.")
+
+// Step 4.2.1.
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ doc.removeChild(doc.documentElement);
+
+ var df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ df.appendChild(doc.createElement("b"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(df, doc.firstChild);
+ });
+
+ df = doc.createDocumentFragment();
+ df.appendChild(doc.createTextNode("text"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(df, doc.firstChild);
+ });
+
+ df = doc.createDocumentFragment();
+ df.appendChild(doc.createComment("comment"));
+ df.appendChild(doc.createTextNode("text"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(df, doc.firstChild);
+ });
+}, "If the context node is a document, inserting a DocumentFragment that contains a text node or too many elements should throw a HierarchyRequestError.")
+
+// Step 4.2.2.
+test(function() {
+ // The context node has an element child.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.appendChild(doc.createComment("foo"));
+ assert_array_equals(doc.childNodes, [doc.doctype, doc.documentElement, comment]);
+
+ var df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(df, doc.doctype);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(df, doc.documentElement);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(df, comment);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(df, null);
+ });
+}, "If the context node is a document, inserting a DocumentFragment with an element if there already is an element child should throw a HierarchyRequestError.")
+test(function() {
+ // /child/ is a doctype.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.insertBefore(doc.createComment("foo"), doc.firstChild);
+ doc.removeChild(doc.documentElement);
+ assert_array_equals(doc.childNodes, [comment, doc.doctype]);
+
+ var df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(df, doc.doctype);
+ });
+}, "If the context node is a document and a doctype is following the reference child, inserting a DocumentFragment with an element should throw a HierarchyRequestError.")
+test(function() {
+ // /child/ is not null and a doctype is following /child/.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.insertBefore(doc.createComment("foo"), doc.firstChild);
+ doc.removeChild(doc.documentElement);
+ assert_array_equals(doc.childNodes, [comment, doc.doctype]);
+
+ var df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(df, comment);
+ });
+}, "If the context node is a document, inserting a DocumentFragment with an element before the doctype should throw a HierarchyRequestError.")
+
+// Step 4.3.
+test(function() {
+ // The context node has an element child.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.appendChild(doc.createComment("foo"));
+ assert_array_equals(doc.childNodes, [doc.doctype, doc.documentElement, comment]);
+
+ var a = doc.createElement("a");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(a, doc.doctype);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(a, doc.documentElement);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(a, comment);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(a, null);
+ });
+}, "If the context node is a document, inserting an element if there already is an element child should throw a HierarchyRequestError.")
+test(function() {
+ // /child/ is a doctype.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.insertBefore(doc.createComment("foo"), doc.firstChild);
+ doc.removeChild(doc.documentElement);
+ assert_array_equals(doc.childNodes, [comment, doc.doctype]);
+
+ var a = doc.createElement("a");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(a, doc.doctype);
+ });
+}, "If the context node is a document, inserting an element before the doctype should throw a HierarchyRequestError.")
+test(function() {
+ // /child/ is not null and a doctype is following /child/.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.insertBefore(doc.createComment("foo"), doc.firstChild);
+ doc.removeChild(doc.documentElement);
+ assert_array_equals(doc.childNodes, [comment, doc.doctype]);
+
+ var a = doc.createElement("a");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(a, comment);
+ });
+}, "If the context node is a document and a doctype is following the reference child, inserting an element should throw a HierarchyRequestError.")
+
+// Step 4.4.
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.insertBefore(doc.createComment("foo"), doc.firstChild);
+ assert_array_equals(doc.childNodes, [comment, doc.doctype, doc.documentElement]);
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(doctype, comment);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(doctype, doc.doctype);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(doctype, doc.documentElement);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(doctype, null);
+ });
+}, "If the context node is a document, inserting a doctype if there already is a doctype child should throw a HierarchyRequestError.")
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.appendChild(doc.createComment("foo"));
+ doc.removeChild(doc.doctype);
+ assert_array_equals(doc.childNodes, [doc.documentElement, comment]);
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(doctype, comment);
+ });
+}, "If the context node is a document, inserting a doctype after the document element should throw a HierarchyRequestError.")
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.appendChild(doc.createComment("foo"));
+ doc.removeChild(doc.doctype);
+ assert_array_equals(doc.childNodes, [doc.documentElement, comment]);
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.insertBefore(doctype, null);
+ });
+}, "If the context node is a document with and element child, appending a doctype should throw a HierarchyRequestError.")
+
+// Step 5.
+test(function() {
+ var df = document.createDocumentFragment();
+ var a = df.appendChild(document.createElement("a"));
+
+ var doc = document.implementation.createHTMLDocument("title");
+ assert_throws_dom("HierarchyRequestError", function() {
+ df.insertBefore(doc, a);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ df.insertBefore(doc, null);
+ });
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ df.insertBefore(doctype, a);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ df.insertBefore(doctype, null);
+ });
+}, "If the context node is a DocumentFragment, inserting a document or a doctype should throw a HierarchyRequestError.")
+test(function() {
+ var el = document.createElement("div");
+ var a = el.appendChild(document.createElement("a"));
+
+ var doc = document.implementation.createHTMLDocument("title");
+ assert_throws_dom("HierarchyRequestError", function() {
+ el.insertBefore(doc, a);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ el.insertBefore(doc, null);
+ });
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ el.insertBefore(doctype, a);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ el.insertBefore(doctype, null);
+ });
+}, "If the context node is an element, inserting a document or a doctype should throw a HierarchyRequestError.")
+
+// Step 7.
+test(function() {
+ var a = document.createElement("div");
+ var b = document.createElement("div");
+ var c = document.createElement("div");
+ a.appendChild(b);
+ a.appendChild(c);
+ assert_array_equals(a.childNodes, [b, c]);
+ assert_equals(a.insertBefore(b, b), b);
+ assert_array_equals(a.childNodes, [b, c]);
+ assert_equals(a.insertBefore(c, c), c);
+ assert_array_equals(a.childNodes, [b, c]);
+}, "Inserting a node before itself should not move the node");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-isConnected-shadow-dom.html b/testing/web-platform/tests/dom/nodes/Node-isConnected-shadow-dom.html
new file mode 100644
index 0000000000..7d04dc32f2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-isConnected-shadow-dom.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test of Node.isConnected in a shadow tree</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#connected">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+function testIsConnected(mode) {
+ test(() => {
+ const host = document.createElement("div");
+ document.body.appendChild(host);
+
+ const root = host.attachShadow({ mode });
+
+ const node = document.createElement("div");
+ root.appendChild(node);
+
+ assert_true(node.isConnected);
+ }, `Node.isConnected in a ${mode} shadow tree`);
+}
+
+for (const mode of ["closed", "open"]) {
+ testIsConnected(mode);
+}
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-isConnected.html b/testing/web-platform/tests/dom/nodes/Node-isConnected.html
new file mode 100644
index 0000000000..da0b460de4
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-isConnected.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<head>
+<title>Node.prototype.isConnected</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-isconnected">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+
+"use strict";
+
+test(function() {
+ var nodes = [document.createElement("div"),
+ document.createElement("div"),
+ document.createElement("div")];
+ checkNodes([], nodes);
+
+ // Append nodes[0].
+ document.body.appendChild(nodes[0]);
+ checkNodes([nodes[0]],
+ [nodes[1], nodes[2]]);
+
+ // Append nodes[1] and nodes[2] together.
+ nodes[1].appendChild(nodes[2]);
+ checkNodes([nodes[0]],
+ [nodes[1], nodes[2]]);
+
+ nodes[0].appendChild(nodes[1]);
+ checkNodes(nodes, []);
+
+ // Remove nodes[2].
+ nodes[2].remove();
+ checkNodes([nodes[0], nodes[1]],
+ [nodes[2]]);
+
+ // Remove nodes[0] and nodes[1] together.
+ nodes[0].remove();
+ checkNodes([], nodes);
+}, "Test with ordinary child nodes");
+
+test(function() {
+ var nodes = [document.createElement("iframe"),
+ document.createElement("iframe"),
+ document.createElement("iframe"),
+ document.createElement("iframe"),
+ document.createElement("div")];
+ var frames = [nodes[0],
+ nodes[1],
+ nodes[2],
+ nodes[3]];
+ checkNodes([], nodes);
+
+ // Since we cannot append anything to the contentWindow of an iframe before it
+ // is appended to the main DOM tree, we append the iframes one after another.
+ document.body.appendChild(nodes[0]);
+ checkNodes([nodes[0]],
+ [nodes[1], nodes[2], nodes[3], nodes[4]]);
+
+ frames[0].contentDocument.body.appendChild(nodes[1]);
+ checkNodes([nodes[0], nodes[1]],
+ [nodes[2], nodes[3], nodes[4]]);
+
+ frames[1].contentDocument.body.appendChild(nodes[2]);
+ checkNodes([nodes[0], nodes[1], nodes[2]],
+ [nodes[3], nodes[4]]);
+
+ frames[2].contentDocument.body.appendChild(nodes[3]);
+ checkNodes([nodes[0], nodes[1], nodes[2], nodes[3]],
+ [nodes[4]]);
+
+ frames[3].contentDocument.body.appendChild(nodes[4]);
+ checkNodes(nodes, []);
+
+ frames[3].remove();
+ // Since node[4] is still under the doument of frame[3], it's still connected.
+ checkNodes([nodes[0], nodes[1], nodes[2], nodes[4]],
+ [nodes[3]]);
+
+ frames[0].remove();
+ // Since node[1] and node[2] are still under the doument of frame[0], they are
+ // still connected.
+ checkNodes([nodes[1], nodes[2], nodes[4]],
+ [nodes[0], nodes[3]]);
+}, "Test with iframes");
+
+// This helper function is used to check whether nodes should be connected.
+function checkNodes(aConnectedNodes, aDisconnectedNodes) {
+ aConnectedNodes.forEach(node => assert_true(node.isConnected));
+ aDisconnectedNodes.forEach(node => assert_false(node.isConnected));
+}
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/dom/nodes/Node-isEqualNode-iframe1.xml b/testing/web-platform/tests/dom/nodes/Node-isEqualNode-iframe1.xml
new file mode 100644
index 0000000000..8077e73c27
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-isEqualNode-iframe1.xml
@@ -0,0 +1 @@
+<!DOCTYPE foo [ <!ELEMENT foo (#PCDATA)> ]><foo/>
diff --git a/testing/web-platform/tests/dom/nodes/Node-isEqualNode-iframe2.xml b/testing/web-platform/tests/dom/nodes/Node-isEqualNode-iframe2.xml
new file mode 100644
index 0000000000..eacc9d17af
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-isEqualNode-iframe2.xml
@@ -0,0 +1 @@
+<!DOCTYPE foo [ <!ELEMENT foo EMPTY> ]><foo/>
diff --git a/testing/web-platform/tests/dom/nodes/Node-isEqualNode-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Node-isEqualNode-xhtml.xhtml
new file mode 100644
index 0000000000..3170643d2f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-isEqualNode-xhtml.xhtml
@@ -0,0 +1,84 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Node.isEqualNode</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"/>
+<script>
+function testNullHandling(node) {
+ test(function() {
+ assert_false(node.isEqualNode(null))
+ assert_false(node.isEqualNode(undefined))
+ })
+}
+[
+ document.createElement("foo"),
+ document.createTextNode("foo"),
+ document.createProcessingInstruction("foo", "bar"),
+ document.createComment("foo"),
+ document,
+ document.implementation.createDocumentType("html", "", ""),
+ document.createDocumentFragment()
+].forEach(testNullHandling)
+
+test(function() {
+ var a = document.createElement("foo")
+ a.setAttribute("a", "bar")
+ a.setAttribute("b", "baz")
+ var b = document.createElement("foo")
+ b.setAttribute("b", "baz")
+ b.setAttribute("a", "bar")
+ assert_true(a.isEqualNode(b))
+}, "isEqualNode should return true when the attributes are in a different order")
+
+test(function() {
+ var a = document.createElementNS("ns", "prefix:foo")
+ var b = document.createElementNS("ns", "prefix:foo")
+ assert_true(a.isEqualNode(b))
+}, "isEqualNode should return true if elements have same namespace, prefix, and local name")
+
+test(function() {
+ var a = document.createElementNS("ns1", "prefix:foo")
+ var b = document.createElementNS("ns2", "prefix:foo")
+ assert_false(a.isEqualNode(b))
+}, "isEqualNode should return false if elements have different namespace")
+
+test(function() {
+ var a = document.createElementNS("ns", "prefix1:foo")
+ var b = document.createElementNS("ns", "prefix2:foo")
+ assert_false(a.isEqualNode(b))
+}, "isEqualNode should return false if elements have different prefix")
+
+test(function() {
+ var a = document.createElementNS("ns", "prefix:foo1")
+ var b = document.createElementNS("ns", "prefix:foo2")
+ assert_false(a.isEqualNode(b))
+}, "isEqualNode should return false if elements have different local name")
+
+test(function() {
+ var a = document.createElement("foo")
+ a.setAttributeNS("ns", "x:a", "bar")
+ var b = document.createElement("foo")
+ b.setAttributeNS("ns", "y:a", "bar")
+ assert_true(a.isEqualNode(b))
+}, "isEqualNode should return true when the attributes have different prefixes")
+var internalSubset = async_test("isEqualNode should return true when only the internal subsets of DocumentTypes differ.")
+var wait = 2;
+function iframeLoaded() {
+ if (!--wait) {
+ internalSubset.step(function() {
+ var doc1 = document.getElementById("subset1").contentDocument
+ var doc2 = document.getElementById("subset2").contentDocument
+ assert_true(doc1.doctype.isEqualNode(doc2.doctype), "doc1.doctype.isEqualNode(doc2.doctype)")
+ assert_true(doc1.isEqualNode(doc2), "doc1.isEqualNode(doc2)")
+ })
+ internalSubset.done()
+ }
+}
+</script>
+<iframe id="subset1" onload="iframeLoaded()" src="Node-isEqualNode-iframe1.xml" />
+<iframe id="subset2" onload="iframeLoaded()" src="Node-isEqualNode-iframe2.xml" />
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Node-isEqualNode.html b/testing/web-platform/tests/dom/nodes/Node-isEqualNode.html
new file mode 100644
index 0000000000..9ff4c5b03c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-isEqualNode.html
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Node.prototype.isEqualNode</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-isequalnode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(function() {
+
+ var doctype1 = document.implementation.createDocumentType("qualifiedName", "publicId", "systemId");
+ var doctype2 = document.implementation.createDocumentType("qualifiedName", "publicId", "systemId");
+ var doctype3 = document.implementation.createDocumentType("qualifiedName2", "publicId", "systemId");
+ var doctype4 = document.implementation.createDocumentType("qualifiedName", "publicId2", "systemId");
+ var doctype5 = document.implementation.createDocumentType("qualifiedName", "publicId", "systemId3");
+
+ assert_true(doctype1.isEqualNode(doctype1), "self-comparison");
+ assert_true(doctype1.isEqualNode(doctype2), "same properties");
+ assert_false(doctype1.isEqualNode(doctype3), "different name");
+ assert_false(doctype1.isEqualNode(doctype4), "different public ID");
+ assert_false(doctype1.isEqualNode(doctype5), "different system ID");
+
+}, "doctypes should be compared on name, public ID, and system ID");
+
+test(function() {
+
+ var element1 = document.createElementNS("namespace", "prefix:localName");
+ var element2 = document.createElementNS("namespace", "prefix:localName");
+ var element3 = document.createElementNS("namespace2", "prefix:localName");
+ var element4 = document.createElementNS("namespace", "prefix2:localName");
+ var element5 = document.createElementNS("namespace", "prefix:localName2");
+
+ var element6 = document.createElementNS("namespace", "prefix:localName");
+ element6.setAttribute("foo", "bar");
+
+ assert_true(element1.isEqualNode(element1), "self-comparison");
+ assert_true(element1.isEqualNode(element2), "same properties");
+ assert_false(element1.isEqualNode(element3), "different namespace");
+ assert_false(element1.isEqualNode(element4), "different prefix");
+ assert_false(element1.isEqualNode(element5), "different local name");
+ assert_false(element1.isEqualNode(element6), "different number of attributes");
+
+}, "elements should be compared on namespace, namespace prefix, local name, and number of attributes");
+
+test(function() {
+
+ var element1 = document.createElement("element");
+ element1.setAttributeNS("namespace", "prefix:localName", "value");
+
+ var element2 = document.createElement("element");
+ element2.setAttributeNS("namespace", "prefix:localName", "value");
+
+ var element3 = document.createElement("element");
+ element3.setAttributeNS("namespace2", "prefix:localName", "value");
+
+ var element4 = document.createElement("element");
+ element4.setAttributeNS("namespace", "prefix2:localName", "value");
+
+ var element5 = document.createElement("element");
+ element5.setAttributeNS("namespace", "prefix:localName2", "value");
+
+ var element6 = document.createElement("element");
+ element6.setAttributeNS("namespace", "prefix:localName", "value2");
+
+ assert_true(element1.isEqualNode(element1), "self-comparison");
+ assert_true(element1.isEqualNode(element2), "attribute with same properties");
+ assert_false(element1.isEqualNode(element3), "attribute with different namespace");
+ assert_true(element1.isEqualNode(element4), "attribute with different prefix");
+ assert_false(element1.isEqualNode(element5), "attribute with different local name");
+ assert_false(element1.isEqualNode(element6), "attribute with different value");
+
+}, "elements should be compared on attribute namespace, local name, and value");
+
+test(function() {
+
+ var pi1 = document.createProcessingInstruction("target", "data");
+ var pi2 = document.createProcessingInstruction("target", "data");
+ var pi3 = document.createProcessingInstruction("target2", "data");
+ var pi4 = document.createProcessingInstruction("target", "data2");
+
+ assert_true(pi1.isEqualNode(pi1), "self-comparison");
+ assert_true(pi1.isEqualNode(pi2), "same properties");
+ assert_false(pi1.isEqualNode(pi3), "different target");
+ assert_false(pi1.isEqualNode(pi4), "different data");
+
+}, "processing instructions should be compared on target and data");
+
+test(function() {
+
+ var text1 = document.createTextNode("data");
+ var text2 = document.createTextNode("data");
+ var text3 = document.createTextNode("data2");
+
+ assert_true(text1.isEqualNode(text1), "self-comparison");
+ assert_true(text1.isEqualNode(text2), "same properties");
+ assert_false(text1.isEqualNode(text3), "different data");
+
+}, "text nodes should be compared on data");
+
+test(function() {
+
+ var comment1 = document.createComment("data");
+ var comment2 = document.createComment("data");
+ var comment3 = document.createComment("data2");
+
+ assert_true(comment1.isEqualNode(comment1), "self-comparison");
+ assert_true(comment1.isEqualNode(comment2), "same properties");
+ assert_false(comment1.isEqualNode(comment3), "different data");
+
+}, "comments should be compared on data");
+
+test(function() {
+
+ var documentFragment1 = document.createDocumentFragment();
+ var documentFragment2 = document.createDocumentFragment();
+
+ assert_true(documentFragment1.isEqualNode(documentFragment1), "self-comparison");
+ assert_true(documentFragment1.isEqualNode(documentFragment2), "same properties");
+
+}, "document fragments should not be compared based on properties");
+
+test(function() {
+
+ var document1 = document.implementation.createDocument("", "");
+ var document2 = document.implementation.createDocument("", "");
+
+ assert_true(document1.isEqualNode(document1), "self-comparison");
+ assert_true(document1.isEqualNode(document2), "another empty XML document");
+
+ var htmlDoctype = document.implementation.createDocumentType("html", "", "");
+ var document3 = document.implementation.createDocument("http://www.w3.org/1999/xhtml", "html", htmlDoctype);
+ document3.documentElement.appendChild(document3.createElement("head"));
+ document3.documentElement.appendChild(document3.createElement("body"));
+ var document4 = document.implementation.createHTMLDocument();
+ assert_true(document3.isEqualNode(document4), "default HTML documents, created different ways");
+
+}, "documents should not be compared based on properties");
+
+test(function() {
+
+ testDeepEquality(function() { return document.createElement("foo") });
+ testDeepEquality(function() { return document.createDocumentFragment() });
+ testDeepEquality(function() { return document.implementation.createDocument("", "") });
+ testDeepEquality(function() { return document.implementation.createHTMLDocument() });
+
+ function testDeepEquality(parentFactory) {
+ // Some ad-hoc tests of deep equality
+
+ var parentA = parentFactory();
+ var parentB = parentFactory();
+
+ parentA.appendChild(document.createComment("data"));
+ assert_false(parentA.isEqualNode(parentB));
+ parentB.appendChild(document.createComment("data"));
+ assert_true(parentA.isEqualNode(parentB));
+ }
+
+}, "node equality testing should test descendant equality too");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-isSameNode.html b/testing/web-platform/tests/dom/nodes/Node-isSameNode.html
new file mode 100644
index 0000000000..b37442ac80
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-isSameNode.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Node.prototype.isSameNode</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-issamenode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+
+test(function() {
+
+ var doctype1 = document.implementation.createDocumentType("qualifiedName", "publicId", "systemId");
+ var doctype2 = document.implementation.createDocumentType("qualifiedName", "publicId", "systemId");
+
+ assert_true(doctype1.isSameNode(doctype1), "self-comparison");
+ assert_false(doctype1.isSameNode(doctype2), "same properties");
+ assert_false(doctype1.isSameNode(null), "with null other node");
+}, "doctypes should be compared on reference");
+
+test(function() {
+
+ var element1 = document.createElementNS("namespace", "prefix:localName");
+ var element2 = document.createElementNS("namespace", "prefix:localName");
+
+ assert_true(element1.isSameNode(element1), "self-comparison");
+ assert_false(element1.isSameNode(element2), "same properties");
+ assert_false(element1.isSameNode(null), "with null other node");
+
+}, "elements should be compared on reference (namespaced element)");
+
+test(function() {
+
+ var element1 = document.createElement("element");
+ element1.setAttributeNS("namespace", "prefix:localName", "value");
+
+ var element2 = document.createElement("element");
+ element2.setAttributeNS("namespace", "prefix:localName", "value");
+
+ assert_true(element1.isSameNode(element1), "self-comparison");
+ assert_false(element1.isSameNode(element2), "same properties");
+ assert_false(element1.isSameNode(null), "with null other node");
+
+}, "elements should be compared on reference (namespaced attribute)");
+
+test(function() {
+
+ var pi1 = document.createProcessingInstruction("target", "data");
+ var pi2 = document.createProcessingInstruction("target", "data");
+
+ assert_true(pi1.isSameNode(pi1), "self-comparison");
+ assert_false(pi1.isSameNode(pi2), "different target");
+ assert_false(pi1.isSameNode(null), "with null other node");
+
+}, "processing instructions should be compared on reference");
+
+test(function() {
+
+ var text1 = document.createTextNode("data");
+ var text2 = document.createTextNode("data");
+
+ assert_true(text1.isSameNode(text1), "self-comparison");
+ assert_false(text1.isSameNode(text2), "same properties");
+ assert_false(text1.isSameNode(null), "with null other node");
+
+}, "text nodes should be compared on reference");
+
+test(function() {
+
+ var comment1 = document.createComment("data");
+ var comment2 = document.createComment("data");
+
+ assert_true(comment1.isSameNode(comment1), "self-comparison");
+ assert_false(comment1.isSameNode(comment2), "same properties");
+ assert_false(comment1.isSameNode(null), "with null other node");
+
+}, "comments should be compared on reference");
+
+test(function() {
+
+ var documentFragment1 = document.createDocumentFragment();
+ var documentFragment2 = document.createDocumentFragment();
+
+ assert_true(documentFragment1.isSameNode(documentFragment1), "self-comparison");
+ assert_false(documentFragment1.isSameNode(documentFragment2), "same properties");
+ assert_false(documentFragment1.isSameNode(null), "with null other node");
+
+}, "document fragments should be compared on reference");
+
+test(function() {
+
+ var document1 = document.implementation.createDocument("", "");
+ var document2 = document.implementation.createDocument("", "");
+
+ assert_true(document1.isSameNode(document1), "self-comparison");
+ assert_false(document1.isSameNode(document2), "another empty XML document");
+ assert_false(document1.isSameNode(null), "with null other node");
+
+}, "documents should be compared on reference");
+
+test(function() {
+
+ var attr1 = document.createAttribute('href');
+ var attr2 = document.createAttribute('href');
+
+ assert_true(attr1.isSameNode(attr1), "self-comparison");
+ assert_false(attr1.isSameNode(attr2), "same name");
+ assert_false(attr1.isSameNode(null), "with null other node");
+
+}, "attributes should be compared on reference");
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-lookupNamespaceURI.html b/testing/web-platform/tests/dom/nodes/Node-lookupNamespaceURI.html
new file mode 100644
index 0000000000..925c528362
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-lookupNamespaceURI.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>LookupNamespaceURI and IsDefaultNamespace tests</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+</head>
+<body>
+<h1>LookupNamespaceURI and IsDefaultNamespace</h1>
+<div id="log"/>
+<script>
+function lookupNamespaceURI(node, prefix, expected, name) {
+ test(function() {
+ assert_equals(node.lookupNamespaceURI(prefix), expected);
+ }, name);
+}
+
+function isDefaultNamespace(node, namespace, expected, name) {
+ test(function() {
+ assert_equals(node.isDefaultNamespace(namespace), expected);
+ }, name);
+}
+
+
+var frag = document.createDocumentFragment();
+lookupNamespaceURI(frag, null, null, 'DocumentFragment should have null namespace, prefix null');
+lookupNamespaceURI(frag, '', null, 'DocumentFragment should have null namespace, prefix ""');
+lookupNamespaceURI(frag, 'foo', null, 'DocumentFragment should have null namespace, prefix "foo"');
+lookupNamespaceURI(frag, 'xml', null, 'DocumentFragment should have null namespace, prefix "xml"');
+lookupNamespaceURI(frag, 'xmlns', null, 'DocumentFragment should have null namespace, prefix "xmlns"');
+isDefaultNamespace(frag, null, true, 'DocumentFragment is in default namespace, prefix null');
+isDefaultNamespace(frag, '', true, 'DocumentFragment is in default namespace, prefix ""');
+isDefaultNamespace(frag, 'foo', false, 'DocumentFragment is in default namespace, prefix "foo"');
+isDefaultNamespace(frag, 'xmlns', false, 'DocumentFragment is in default namespace, prefix "xmlns"');
+
+var docType = document.doctype;
+lookupNamespaceURI(docType, null, null, 'DocumentType should have null namespace, prefix null');
+lookupNamespaceURI(docType, '', null, 'DocumentType should have null namespace, prefix ""');
+lookupNamespaceURI(docType, 'foo', null, 'DocumentType should have null namespace, prefix "foo"');
+lookupNamespaceURI(docType, 'xml', null, 'DocumentType should have null namespace, prefix "xml"');
+lookupNamespaceURI(docType, 'xmlns', null, 'DocumentType should have null namespace, prefix "xmlns"');
+isDefaultNamespace(docType, null, true, 'DocumentType is in default namespace, prefix null');
+isDefaultNamespace(docType, '', true, 'DocumentType is in default namespace, prefix ""');
+isDefaultNamespace(docType, 'foo', false, 'DocumentType is in default namespace, prefix "foo"');
+isDefaultNamespace(docType, 'xmlns', false, 'DocumentType is in default namespace, prefix "xmlns"');
+
+var fooElem = document.createElementNS('fooNamespace', 'prefix:elem');
+fooElem.setAttribute('bar', 'value');
+const XMLNS_NS = 'http://www.w3.org/2000/xmlns/';
+const XML_NS = 'http://www.w3.org/XML/1998/namespace';
+lookupNamespaceURI(fooElem, null, null, 'Element should have null namespace, prefix null');
+lookupNamespaceURI(fooElem, '', null, 'Element should have null namespace, prefix ""');
+lookupNamespaceURI(fooElem, 'fooNamespace', null, 'Element should not have namespace matching prefix with namespaceURI value');
+lookupNamespaceURI(fooElem, 'xml', XML_NS, 'Element should have XML namespace');
+lookupNamespaceURI(fooElem, 'xmlns', XMLNS_NS, 'Element should have XMLNS namespace');
+lookupNamespaceURI(fooElem, 'prefix', 'fooNamespace', 'Element has namespace URI matching prefix');
+isDefaultNamespace(fooElem, null, true, 'Empty namespace is not default, prefix null');
+isDefaultNamespace(fooElem, '', true, 'Empty namespace is not default, prefix ""');
+isDefaultNamespace(fooElem, 'fooNamespace', false, 'fooNamespace is not default');
+isDefaultNamespace(fooElem, XMLNS_NS, false, 'xmlns namespace is not default');
+
+fooElem.setAttributeNS(XMLNS_NS, 'xmlns:bar', 'barURI');
+fooElem.setAttributeNS(XMLNS_NS, 'xmlns', 'bazURI');
+
+lookupNamespaceURI(fooElem, null, 'bazURI', 'Element should have baz namespace, prefix null');
+lookupNamespaceURI(fooElem, '', 'bazURI', 'Element should have baz namespace, prefix ""');
+lookupNamespaceURI(fooElem, 'xmlns', XMLNS_NS, 'Element should have namespace with xmlns prefix');
+lookupNamespaceURI(fooElem, 'bar', 'barURI', 'Element has bar namespace');
+
+isDefaultNamespace(fooElem, null, false, 'Empty namespace is not default on fooElem, prefix null');
+isDefaultNamespace(fooElem, '', false, 'Empty namespace is not default on fooElem, prefix ""');
+isDefaultNamespace(fooElem, 'barURI', false, 'bar namespace is not default');
+isDefaultNamespace(fooElem, 'bazURI', true, 'baz namespace is default');
+
+var comment = document.createComment('comment');
+fooElem.appendChild(comment);
+
+lookupNamespaceURI(comment, null, 'bazURI', 'Comment should inherit baz namespace');
+lookupNamespaceURI(comment, '', 'bazURI', 'Comment should inherit baz namespace');
+lookupNamespaceURI(comment, 'prefix', 'fooNamespace', 'Comment should inherit namespace URI matching prefix');
+lookupNamespaceURI(comment, 'bar', 'barURI', 'Comment should inherit bar namespace');
+
+isDefaultNamespace(comment, null, false, 'For comment, empty namespace is not default, prefix null');
+isDefaultNamespace(comment, '', false, 'For comment, empty namespace is not default, prefix ""');
+isDefaultNamespace(comment, 'fooNamespace', false, 'For comment, fooNamespace is not default');
+isDefaultNamespace(comment, XMLNS_NS, false, 'For comment, xmlns namespace is not default');
+isDefaultNamespace(comment, 'barURI', false, 'For comment, inherited bar namespace is not default');
+isDefaultNamespace(comment, 'bazURI', true, 'For comment, inherited baz namespace is default');
+
+var fooChild = document.createElementNS('childNamespace', 'childElem');
+fooElem.appendChild(fooChild);
+
+lookupNamespaceURI(fooChild, null, 'childNamespace', 'Child element should inherit baz namespace');
+lookupNamespaceURI(fooChild, '', 'childNamespace', 'Child element should have null namespace');
+lookupNamespaceURI(fooChild, 'xmlns', XMLNS_NS, 'Child element should have XMLNS namespace');
+lookupNamespaceURI(fooChild, 'prefix', 'fooNamespace', 'Child element has namespace URI matching prefix');
+
+isDefaultNamespace(fooChild, null, false, 'Empty namespace is not default for child, prefix null');
+isDefaultNamespace(fooChild, '', false, 'Empty namespace is not default for child, prefix ""');
+isDefaultNamespace(fooChild, 'fooNamespace', false, 'fooNamespace is not default for child');
+isDefaultNamespace(fooChild, XMLNS_NS, false, 'xmlns namespace is not default for child');
+isDefaultNamespace(fooChild, 'barURI', false, 'bar namespace is not default for child');
+isDefaultNamespace(fooChild, 'bazURI', false, 'baz namespace is default for child');
+isDefaultNamespace(fooChild, 'childNamespace', true, 'childNamespace is default for child');
+
+document.documentElement.setAttributeNS(XMLNS_NS, 'xmlns:bar', 'barURI');
+document.documentElement.setAttributeNS(XMLNS_NS, 'xmlns', 'bazURI');
+
+lookupNamespaceURI(document, null, 'http://www.w3.org/1999/xhtml', 'Document should have xhtml namespace, prefix null');
+lookupNamespaceURI(document, '', 'http://www.w3.org/1999/xhtml', 'Document should have xhtml namespace, prefix ""');
+lookupNamespaceURI(document, 'prefix', null, 'Document has no namespace URI matching prefix');
+lookupNamespaceURI(document, 'bar', 'barURI', 'Document has bar namespace');
+
+isDefaultNamespace(document, null, false, 'For document, empty namespace is not default, prefix null');
+isDefaultNamespace(document, '', false, 'For document, empty namespace is not default, prefix ""');
+isDefaultNamespace(document, 'fooNamespace', false, 'For document, fooNamespace is not default');
+isDefaultNamespace(document, XMLNS_NS, false, 'For document, xmlns namespace is not default');
+isDefaultNamespace(document, 'barURI', false, 'For document, bar namespace is not default');
+isDefaultNamespace(document, 'bazURI', false, 'For document, baz namespace is not default');
+isDefaultNamespace(document, 'http://www.w3.org/1999/xhtml', true, 'For document, xhtml namespace is default');
+
+const doc = new Document();
+lookupNamespaceURI(doc, 'xml', null, 'Document without documentElement has no namespace URI matching "xml"');
+lookupNamespaceURI(doc, 'xmlns', null, 'Document without documentElement has no namespace URI matching "xmlns"');
+
+const attr = document.createAttribute('foo');
+lookupNamespaceURI(attr, 'xml', null, 'Disconnected Attr has no namespace URI matching "xml"');
+lookupNamespaceURI(attr, 'xmlns', null, 'Disconnected Attr has no namespace URI matching "xmlns"');
+document.body.setAttributeNode(attr);
+lookupNamespaceURI(attr, 'xml', XML_NS, 'Connected Attr has namespace URI matching "xml"');
+lookupNamespaceURI(attr, 'xmlns', XMLNS_NS, 'Connected Attr no namespace URI matching "xmlns"');
+
+var comment = document.createComment('comment');
+document.appendChild(comment);
+lookupNamespaceURI(comment, 'bar', null, 'Comment does not have bar namespace');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Node-lookupPrefix.xhtml b/testing/web-platform/tests/dom/nodes/Node-lookupPrefix.xhtml
new file mode 100644
index 0000000000..50a487c58c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-lookupPrefix.xhtml
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:x="test">
+<head>
+<title>Node.lookupPrefix</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body xmlns:s="test">
+<div id="log"/>
+<x xmlns:t="test"><!--comment--><?test test?>TEST<x/></x>
+<script>
+function lookupPrefix(node, ns, prefix) {
+ test(function() {
+ assert_equals(node.lookupPrefix(ns), prefix)
+ })
+}
+var x = document.getElementsByTagName("x")[0];
+lookupPrefix(document, "test", "x") // XXX add test for when there is no documentElement
+lookupPrefix(document, null, null)
+lookupPrefix(x, "http://www.w3.org/1999/xhtml", null)
+lookupPrefix(x, "something", null)
+lookupPrefix(x, null, null)
+lookupPrefix(x, "test", "t")
+lookupPrefix(x.parentNode, "test", "s")
+lookupPrefix(x.firstChild, "test", "t")
+lookupPrefix(x.childNodes[1], "test", "t")
+lookupPrefix(x.childNodes[2], "test", "t")
+lookupPrefix(x.lastChild, "test", "t")
+x.parentNode.removeChild(x)
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Node-mutation-adoptNode.html b/testing/web-platform/tests/dom/nodes/Node-mutation-adoptNode.html
new file mode 100644
index 0000000000..9c9594c07b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-mutation-adoptNode.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Node-manipulation-adopted</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument">
+<link rel=help href="https://dom.spec.whatwg.org/#mutation-algorithms">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+"use strict";
+
+test(() => {
+ const old = document.implementation.createHTMLDocument("");
+ const div = old.createElement("div");
+ div.appendChild(old.createTextNode("text"));
+ assert_equals(div.ownerDocument, old);
+ assert_equals(div.firstChild.ownerDocument, old);
+ document.body.appendChild(div);
+ assert_equals(div.ownerDocument, document);
+ assert_equals(div.firstChild.ownerDocument, document);
+}, "simple append of foreign div with text");
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-nodeName-xhtml.xhtml b/testing/web-platform/tests/dom/nodes/Node-nodeName-xhtml.xhtml
new file mode 100644
index 0000000000..bc478af8b6
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-nodeName-xhtml.xhtml
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Node.nodeName</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"/>
+<script>
+test(function() {
+ var HTMLNS = "http://www.w3.org/1999/xhtml",
+ SVGNS = "http://www.w3.org/2000/svg"
+ assert_equals(document.createElementNS(HTMLNS, "I").nodeName, "I")
+ assert_equals(document.createElementNS(HTMLNS, "i").nodeName, "i")
+ assert_equals(document.createElementNS(SVGNS, "svg").nodeName, "svg")
+ assert_equals(document.createElementNS(SVGNS, "SVG").nodeName, "SVG")
+ assert_equals(document.createElementNS(HTMLNS, "x:b").nodeName, "x:b")
+}, "For Element nodes, nodeName should return the same as tagName.")
+test(function() {
+ assert_equals(document.createTextNode("foo").nodeName, "#text")
+}, "For Text nodes, nodeName should return \"#text\".")
+test(function() {
+ assert_equals(document.createProcessingInstruction("foo", "bar").nodeName,
+ "foo")
+}, "For ProcessingInstruction nodes, nodeName should return the target.")
+test(function() {
+ assert_equals(document.createComment("foo").nodeName, "#comment")
+}, "For Comment nodes, nodeName should return \"#comment\".")
+test(function() {
+ assert_equals(document.nodeName, "#document")
+}, "For Document nodes, nodeName should return \"#document\".")
+test(function() {
+ assert_equals(document.doctype.nodeName, "html")
+}, "For DocumentType nodes, nodeName should return the name.")
+test(function() {
+ assert_equals(document.createDocumentFragment().nodeName,
+ "#document-fragment")
+}, "For DocumentFragment nodes, nodeName should return \"#document-fragment\".")
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Node-nodeName.html b/testing/web-platform/tests/dom/nodes/Node-nodeName.html
new file mode 100644
index 0000000000..911f93455c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-nodeName.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Node.nodeName</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var HTMLNS = "http://www.w3.org/1999/xhtml",
+ SVGNS = "http://www.w3.org/2000/svg"
+ assert_equals(document.createElementNS(HTMLNS, "I").nodeName, "I")
+ assert_equals(document.createElementNS(HTMLNS, "i").nodeName, "I")
+ assert_equals(document.createElementNS(SVGNS, "svg").nodeName, "svg")
+ assert_equals(document.createElementNS(SVGNS, "SVG").nodeName, "SVG")
+ assert_equals(document.createElementNS(HTMLNS, "x:b").nodeName, "X:B")
+}, "For Element nodes, nodeName should return the same as tagName.")
+test(function() {
+ assert_equals(document.createTextNode("foo").nodeName, "#text")
+}, "For Text nodes, nodeName should return \"#text\".")
+test(function() {
+ assert_equals(document.createComment("foo").nodeName, "#comment")
+}, "For Comment nodes, nodeName should return \"#comment\".")
+test(function() {
+ assert_equals(document.nodeName, "#document")
+}, "For Document nodes, nodeName should return \"#document\".")
+test(function() {
+ assert_equals(document.doctype.nodeName, "html")
+}, "For DocumentType nodes, nodeName should return the name.")
+test(function() {
+ assert_equals(document.createDocumentFragment().nodeName,
+ "#document-fragment")
+}, "For DocumentFragment nodes, nodeName should return \"#document-fragment\".")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-nodeValue.html b/testing/web-platform/tests/dom/nodes/Node-nodeValue.html
new file mode 100644
index 0000000000..79ce80b875
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-nodeValue.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.nodeValue</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-node-nodevalue">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ var the_text = document.createTextNode("A span!");
+ assert_equals(the_text.nodeValue, "A span!");
+ assert_equals(the_text.data, "A span!");
+ the_text.nodeValue = "test again";
+ assert_equals(the_text.nodeValue, "test again");
+ assert_equals(the_text.data, "test again");
+ the_text.nodeValue = null;
+ assert_equals(the_text.nodeValue, "");
+ assert_equals(the_text.data, "");
+}, "Text.nodeValue");
+
+test(function() {
+ var the_comment = document.createComment("A comment!");
+ assert_equals(the_comment.nodeValue, "A comment!");
+ assert_equals(the_comment.data, "A comment!");
+ the_comment.nodeValue = "test again";
+ assert_equals(the_comment.nodeValue, "test again");
+ assert_equals(the_comment.data, "test again");
+ the_comment.nodeValue = null;
+ assert_equals(the_comment.nodeValue, "");
+ assert_equals(the_comment.data, "");
+}, "Comment.nodeValue");
+
+test(function() {
+ var the_pi = document.createProcessingInstruction("pi", "A PI!");
+ assert_equals(the_pi.nodeValue, "A PI!");
+ assert_equals(the_pi.data, "A PI!");
+ the_pi.nodeValue = "test again";
+ assert_equals(the_pi.nodeValue, "test again");
+ assert_equals(the_pi.data, "test again");
+ the_pi.nodeValue = null;
+ assert_equals(the_pi.nodeValue, "");
+ assert_equals(the_pi.data, "");
+}, "ProcessingInstruction.nodeValue");
+
+test(function() {
+ var the_link = document.createElement("a");
+ assert_equals(the_link.nodeValue, null);
+ the_link.nodeValue = "foo";
+ assert_equals(the_link.nodeValue, null);
+}, "Element.nodeValue");
+
+test(function() {
+ assert_equals(document.nodeValue, null);
+ document.nodeValue = "foo";
+ assert_equals(document.nodeValue, null);
+}, "Document.nodeValue");
+
+test(function() {
+ var the_frag = document.createDocumentFragment();
+ assert_equals(the_frag.nodeValue, null);
+ the_frag.nodeValue = "foo";
+ assert_equals(the_frag.nodeValue, null);
+}, "DocumentFragment.nodeValue");
+
+test(function() {
+ var the_doctype = document.doctype;
+ assert_equals(the_doctype.nodeValue, null);
+ the_doctype.nodeValue = "foo";
+ assert_equals(the_doctype.nodeValue, null);
+}, "DocumentType.nodeValue");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-normalize.html b/testing/web-platform/tests/dom/nodes/Node-normalize.html
new file mode 100644
index 0000000000..4d455996e5
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-normalize.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<title>Node.normalize()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var df = document.createDocumentFragment(),
+ t1 = document.createTextNode("1"),
+ t2 = document.createTextNode("2"),
+ t3 = document.createTextNode("3"),
+ t4 = document.createTextNode("4")
+ df.appendChild(t1)
+ df.appendChild(t2)
+ assert_equals(df.childNodes.length, 2)
+ assert_equals(df.textContent, "12")
+ var el = document.createElement('x')
+ df.appendChild(el)
+ el.appendChild(t3)
+ el.appendChild(t4)
+ document.normalize()
+ assert_equals(el.childNodes.length, 2)
+ assert_equals(el.textContent, "34")
+ assert_equals(df.childNodes.length, 3)
+ assert_equals(t1.data, "1")
+ df.normalize()
+ assert_equals(df.childNodes.length, 2)
+ assert_equals(df.firstChild, t1)
+ assert_equals(t1.data, "12")
+ assert_equals(t2.data, "2")
+ assert_equals(el.firstChild, t3)
+ assert_equals(t3.data, "34")
+ assert_equals(t4.data, "4")
+})
+
+// https://www.w3.org/Bugs/Public/show_bug.cgi?id=19837
+test(function() {
+ var div = document.createElement("div")
+ var t1 = div.appendChild(document.createTextNode(""))
+ var t2 = div.appendChild(document.createTextNode("a"))
+ var t3 = div.appendChild(document.createTextNode(""))
+ assert_array_equals(div.childNodes, [t1, t2, t3])
+ div.normalize();
+ assert_array_equals(div.childNodes, [t2])
+}, "Empty text nodes separated by a non-empty text node")
+test(function() {
+ var div = document.createElement("div")
+ var t1 = div.appendChild(document.createTextNode(""))
+ var t2 = div.appendChild(document.createTextNode(""))
+ assert_array_equals(div.childNodes, [t1, t2])
+ div.normalize();
+ assert_array_equals(div.childNodes, [])
+}, "Empty text nodes")
+
+// The specification for normalize is clear that only "exclusive Text
+// nodes" are to be modified. This excludes CDATASection nodes, which
+// derive from Text. Naïve implementations may fail to skip
+// CDATASection nodes, or even worse, try to test textContent or
+// nodeValue without taking care to check the node type. They will
+// fail this test.
+test(function() {
+ // We create an XML document so that we can create CDATASection.
+ // Except for the CDATASection the result should be the same for
+ // an HTML document. (No non-Text node should be touched.)
+ var doc = new DOMParser().parseFromString("<div/>", "text/xml")
+ var div = doc.documentElement
+ var t1 = div.appendChild(doc.createTextNode("a"))
+ // The first parameter is the "target" of the processing
+ // instruction, and the 2nd is the text content.
+ var t2 = div.appendChild(doc.createProcessingInstruction("pi", ""))
+ var t3 = div.appendChild(doc.createTextNode("b"))
+ var t4 = div.appendChild(doc.createCDATASection(""))
+ var t5 = div.appendChild(doc.createTextNode("c"))
+ var t6 = div.appendChild(doc.createComment(""))
+ var t7 = div.appendChild(doc.createTextNode("d"))
+ var t8 = div.appendChild(doc.createElement("el"))
+ var t9 = div.appendChild(doc.createTextNode("e"))
+ var expected = [t1, t2, t3, t4, t5, t6, t7, t8, t9]
+ assert_array_equals(div.childNodes, expected)
+ div.normalize()
+ assert_array_equals(div.childNodes, expected)
+}, "Non-text nodes with empty textContent values.")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-parentElement.html b/testing/web-platform/tests/dom/nodes/Node-parentElement.html
new file mode 100644
index 0000000000..bc564bee0e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-parentElement.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<title>Node.parentElement</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_equals(document.parentElement, null)
+}, "When the parent is null, parentElement should be null")
+test(function() {
+ assert_equals(document.doctype.parentElement, null)
+}, "When the parent is a document, parentElement should be null (doctype)")
+test(function() {
+ assert_equals(document.documentElement.parentElement, null)
+}, "When the parent is a document, parentElement should be null (element)")
+test(function() {
+ var comment = document.appendChild(document.createComment("foo"))
+ assert_equals(comment.parentElement, null)
+}, "When the parent is a document, parentElement should be null (comment)")
+test(function() {
+ var df = document.createDocumentFragment()
+ assert_equals(df.parentElement, null)
+ var el = document.createElement("div")
+ assert_equals(el.parentElement, null)
+ df.appendChild(el)
+ assert_equals(el.parentNode, df)
+ assert_equals(el.parentElement, null)
+}, "parentElement should return null for children of DocumentFragments (element)")
+test(function() {
+ var df = document.createDocumentFragment()
+ assert_equals(df.parentElement, null)
+ var text = document.createTextNode("bar")
+ assert_equals(text.parentElement, null)
+ df.appendChild(text)
+ assert_equals(text.parentNode, df)
+ assert_equals(text.parentElement, null)
+}, "parentElement should return null for children of DocumentFragments (text)")
+test(function() {
+ var df = document.createDocumentFragment()
+ var parent = document.createElement("div")
+ df.appendChild(parent)
+ var el = document.createElement("div")
+ assert_equals(el.parentElement, null)
+ parent.appendChild(el)
+ assert_equals(el.parentElement, parent)
+}, "parentElement should work correctly with DocumentFragments (element)")
+test(function() {
+ var df = document.createDocumentFragment()
+ var parent = document.createElement("div")
+ df.appendChild(parent)
+ var text = document.createTextNode("bar")
+ assert_equals(text.parentElement, null)
+ parent.appendChild(text)
+ assert_equals(text.parentElement, parent)
+}, "parentElement should work correctly with DocumentFragments (text)")
+test(function() {
+ var parent = document.createElement("div")
+ var el = document.createElement("div")
+ assert_equals(el.parentElement, null)
+ parent.appendChild(el)
+ assert_equals(el.parentElement, parent)
+}, "parentElement should work correctly in disconnected subtrees (element)")
+test(function() {
+ var parent = document.createElement("div")
+ var text = document.createTextNode("bar")
+ assert_equals(text.parentElement, null)
+ parent.appendChild(text)
+ assert_equals(text.parentElement, parent)
+}, "parentElement should work correctly in disconnected subtrees (text)")
+test(function() {
+ var el = document.createElement("div")
+ assert_equals(el.parentElement, null)
+ document.body.appendChild(el)
+ assert_equals(el.parentElement, document.body)
+}, "parentElement should work correctly in a document (element)")
+test(function() {
+ var text = document.createElement("div")
+ assert_equals(text.parentElement, null)
+ document.body.appendChild(text)
+ assert_equals(text.parentElement, document.body)
+}, "parentElement should work correctly in a document (text)")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-parentNode-iframe.html b/testing/web-platform/tests/dom/nodes/Node-parentNode-iframe.html
new file mode 100644
index 0000000000..88bc5ab436
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-parentNode-iframe.html
@@ -0,0 +1 @@
+<a name='c'>c</a> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/Node-parentNode.html b/testing/web-platform/tests/dom/nodes/Node-parentNode.html
new file mode 100644
index 0000000000..cff6178627
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-parentNode.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Node.parentNode</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+// XXX need to test for more node types
+test(function() {
+ assert_equals(document.parentNode, null)
+}, "Document")
+test(function() {
+ assert_equals(document.doctype.parentNode, document)
+}, "Doctype")
+test(function() {
+ assert_equals(document.documentElement.parentNode, document)
+}, "Root element")
+test(function() {
+ var el = document.createElement("div")
+ assert_equals(el.parentNode, null)
+ document.body.appendChild(el)
+ assert_equals(el.parentNode, document.body)
+}, "Element")
+var t = async_test("Removed iframe");
+function testIframe(iframe) {
+ t.step(function() {
+ var doc = iframe.contentDocument;
+ iframe.parentNode.removeChild(iframe);
+ assert_equals(doc.firstChild.parentNode, doc);
+ });
+ t.done();
+}
+</script>
+<iframe id=a src="Node-parentNode-iframe.html" onload="testIframe(this)"></iframe>
diff --git a/testing/web-platform/tests/dom/nodes/Node-properties.html b/testing/web-platform/tests/dom/nodes/Node-properties.html
new file mode 100644
index 0000000000..10f92e7d7e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-properties.html
@@ -0,0 +1,688 @@
+<!doctype html>
+<title>Node assorted property tests</title>
+<link rel=author title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta charset=utf-8>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+/**
+ * First we define a data structure to tell us what tests to run. The keys
+ * will be eval()ed, and are mostly global variables defined in common.js. The
+ * values are objects, which maps properties to expected values. So
+ *
+ * foo: {
+ * bar: "baz",
+ * quz: 7,
+ * },
+ *
+ * will test that eval("foo.bar") === "baz" and eval("foo.quz") === 7. "foo"
+ * and "bar" could thus be expressions, like "document.documentElement" and
+ * "childNodes[4]" respectively.
+ *
+ * To avoid repetition, some values are automatically added based on others.
+ * For instance, if we specify nodeType: Node.TEXT_NODE, we'll automatically
+ * also test nodeName: "#text". This is handled by code after this variable is
+ * defined.
+ */
+var expected = {
+ testDiv: {
+ // Node
+ nodeType: Node.ELEMENT_NODE,
+ ownerDocument: document,
+ parentNode: document.body,
+ parentElement: document.body,
+ "childNodes.length": 6,
+ "childNodes[0]": paras[0],
+ "childNodes[1]": paras[1],
+ "childNodes[2]": paras[2],
+ "childNodes[3]": paras[3],
+ "childNodes[4]": paras[4],
+ "childNodes[5]": comment,
+ previousSibling: null,
+ nextSibling: document.getElementById("log"),
+ textContent: "A\u0308b\u0308c\u0308d\u0308e\u0308f\u0308g\u0308h\u0308\nIjklmnop\nQrstuvwxYzabcdefGhijklmn",
+
+ // Element
+ namespaceURI: "http://www.w3.org/1999/xhtml",
+ prefix: null,
+ localName: "div",
+ tagName: "DIV",
+ id: "test",
+ "children[0]": paras[0],
+ "children[1]": paras[1],
+ "children[2]": paras[2],
+ "children[3]": paras[3],
+ "children[4]": paras[4],
+ previousElementSibling: null,
+ // nextSibling isn't explicitly set
+ //nextElementSibling: ,
+ childElementCount: 5,
+ },
+ detachedDiv: {
+ // Node
+ nodeType: Node.ELEMENT_NODE,
+ ownerDocument: document,
+ parentNode: null,
+ parentElement: null,
+ "childNodes.length": 2,
+ "childNodes[0]": detachedPara1,
+ "childNodes[1]": detachedPara2,
+ previousSibling: null,
+ nextSibling: null,
+ textContent: "OpqrstuvWxyzabcd",
+
+ // Element
+ namespaceURI: "http://www.w3.org/1999/xhtml",
+ prefix: null,
+ localName: "div",
+ tagName: "DIV",
+ "children[0]": detachedPara1,
+ "children[1]": detachedPara2,
+ previousElementSibling: null,
+ nextElementSibling: null,
+ childElementCount: 2,
+ },
+ detachedPara1: {
+ // Node
+ nodeType: Node.ELEMENT_NODE,
+ ownerDocument: document,
+ parentNode: detachedDiv,
+ parentElement: detachedDiv,
+ "childNodes.length": 1,
+ previousSibling: null,
+ nextSibling: detachedPara2,
+ textContent: "Opqrstuv",
+
+ // Element
+ namespaceURI: "http://www.w3.org/1999/xhtml",
+ prefix: null,
+ localName: "p",
+ tagName: "P",
+ previousElementSibling: null,
+ nextElementSibling: detachedPara2,
+ childElementCount: 0,
+ },
+ detachedPara2: {
+ // Node
+ nodeType: Node.ELEMENT_NODE,
+ ownerDocument: document,
+ parentNode: detachedDiv,
+ parentElement: detachedDiv,
+ "childNodes.length": 1,
+ previousSibling: detachedPara1,
+ nextSibling: null,
+ textContent: "Wxyzabcd",
+
+ // Element
+ namespaceURI: "http://www.w3.org/1999/xhtml",
+ prefix: null,
+ localName: "p",
+ tagName: "P",
+ previousElementSibling: detachedPara1,
+ nextElementSibling: null,
+ childElementCount: 0,
+ },
+ document: {
+ // Node
+ nodeType: Node.DOCUMENT_NODE,
+ "childNodes.length": 2,
+ "childNodes[0]": document.doctype,
+ "childNodes[1]": document.documentElement,
+
+ // Document
+ URL: String(location),
+ compatMode: "CSS1Compat",
+ characterSet: "UTF-8",
+ contentType: "text/html",
+ doctype: doctype,
+ //documentElement: ,
+ },
+ foreignDoc: {
+ // Node
+ nodeType: Node.DOCUMENT_NODE,
+ "childNodes.length": 3,
+ "childNodes[0]": foreignDoc.doctype,
+ "childNodes[1]": foreignDoc.documentElement,
+ "childNodes[2]": foreignComment,
+
+ // Document
+ URL: "about:blank",
+ compatMode: "CSS1Compat",
+ characterSet: "UTF-8",
+ contentType: "text/html",
+ //doctype: ,
+ //documentElement: ,
+ },
+ foreignPara1: {
+ // Node
+ nodeType: Node.ELEMENT_NODE,
+ ownerDocument: foreignDoc,
+ parentNode: foreignDoc.body,
+ parentElement: foreignDoc.body,
+ "childNodes.length": 1,
+ previousSibling: null,
+ nextSibling: foreignPara2,
+ textContent: "Efghijkl",
+
+ // Element
+ namespaceURI: "http://www.w3.org/1999/xhtml",
+ prefix: null,
+ localName: "p",
+ tagName: "P",
+ previousElementSibling: null,
+ nextElementSibling: foreignPara2,
+ childElementCount: 0,
+ },
+ foreignPara2: {
+ // Node
+ nodeType: Node.ELEMENT_NODE,
+ ownerDocument: foreignDoc,
+ parentNode: foreignDoc.body,
+ parentElement: foreignDoc.body,
+ "childNodes.length": 1,
+ previousSibling: foreignPara1,
+ nextSibling: foreignTextNode,
+ textContent: "Mnopqrst",
+
+ // Element
+ namespaceURI: "http://www.w3.org/1999/xhtml",
+ prefix: null,
+ localName: "p",
+ tagName: "P",
+ previousElementSibling: foreignPara1,
+ nextElementSibling: null,
+ childElementCount: 0,
+ },
+ xmlDoc: {
+ // Node
+ nodeType: Node.DOCUMENT_NODE,
+ "childNodes.length": 4,
+ "childNodes[0]": xmlDoctype,
+ "childNodes[1]": xmlElement,
+ "childNodes[2]": processingInstruction,
+ "childNodes[3]": xmlComment,
+
+ // Document
+ URL: "about:blank",
+ compatMode: "CSS1Compat",
+ characterSet: "UTF-8",
+ contentType: "application/xml",
+ //doctype: ,
+ //documentElement: ,
+ },
+ xmlElement: {
+ // Node
+ nodeType: Node.ELEMENT_NODE,
+ ownerDocument: xmlDoc,
+ parentNode: xmlDoc,
+ parentElement: null,
+ "childNodes.length": 1,
+ "childNodes[0]": xmlTextNode,
+ previousSibling: xmlDoctype,
+ nextSibling: processingInstruction,
+ textContent: "do re mi fa so la ti",
+
+ // Element
+ namespaceURI: null,
+ prefix: null,
+ localName: "igiveuponcreativenames",
+ tagName: "igiveuponcreativenames",
+ previousElementSibling: null,
+ nextElementSibling: null,
+ childElementCount: 0,
+ },
+ detachedXmlElement: {
+ // Node
+ nodeType: Node.ELEMENT_NODE,
+ ownerDocument: xmlDoc,
+ parentNode: null,
+ parentElement: null,
+ "childNodes.length": 0,
+ previousSibling: null,
+ nextSibling: null,
+ textContent: "",
+
+ // Element
+ namespaceURI: null,
+ prefix: null,
+ localName: "everyone-hates-hyphenated-element-names",
+ tagName: "everyone-hates-hyphenated-element-names",
+ previousElementSibling: null,
+ nextElementSibling: null,
+ childElementCount: 0,
+ },
+ detachedTextNode: {
+ // Node
+ nodeType: Node.TEXT_NODE,
+ ownerDocument: document,
+ parentNode: null,
+ parentElement: null,
+ previousSibling: null,
+ nextSibling: null,
+ nodeValue: "Uvwxyzab",
+
+ // Text
+ wholeText: "Uvwxyzab",
+ },
+ foreignTextNode: {
+ // Node
+ nodeType: Node.TEXT_NODE,
+ ownerDocument: foreignDoc,
+ parentNode: foreignDoc.body,
+ parentElement: foreignDoc.body,
+ previousSibling: foreignPara2,
+ nextSibling: null,
+ nodeValue: "I admit that I harbor doubts about whether we really need so many things to test, but it's too late to stop now.",
+
+ // Text
+ wholeText: "I admit that I harbor doubts about whether we really need so many things to test, but it's too late to stop now.",
+ },
+ detachedForeignTextNode: {
+ // Node
+ nodeType: Node.TEXT_NODE,
+ ownerDocument: foreignDoc,
+ parentNode: null,
+ parentElement: null,
+ previousSibling: null,
+ nextSibling: null,
+ nodeValue: "Cdefghij",
+
+ // Text
+ wholeText: "Cdefghij",
+ },
+ xmlTextNode: {
+ // Node
+ nodeType: Node.TEXT_NODE,
+ ownerDocument: xmlDoc,
+ parentNode: xmlElement,
+ parentElement: xmlElement,
+ previousSibling: null,
+ nextSibling: null,
+ nodeValue: "do re mi fa so la ti",
+
+ // Text
+ wholeText: "do re mi fa so la ti",
+ },
+ detachedXmlTextNode: {
+ // Node
+ nodeType: Node.TEXT_NODE,
+ ownerDocument: xmlDoc,
+ parentNode: null,
+ parentElement: null,
+ previousSibling: null,
+ nextSibling: null,
+ nodeValue: "Klmnopqr",
+
+ // Text
+ wholeText: "Klmnopqr",
+ },
+ processingInstruction: {
+ // Node
+ nodeType: Node.PROCESSING_INSTRUCTION_NODE,
+ ownerDocument: xmlDoc,
+ parentNode: xmlDoc,
+ parentElement: null,
+ previousSibling: xmlElement,
+ nextSibling: xmlComment,
+ nodeValue: 'Did you know that ":syn sync fromstart" is very useful when using vim to edit large amounts of JavaScript embedded in HTML?',
+
+ // ProcessingInstruction
+ target: "somePI",
+ },
+ detachedProcessingInstruction: {
+ // Node
+ nodeType: Node.PROCESSING_INSTRUCTION_NODE,
+ ownerDocument: xmlDoc,
+ parentNode: null,
+ parentElement: null,
+ previousSibling: null,
+ nextSibling: null,
+ nodeValue: "chirp chirp chirp",
+
+ // ProcessingInstruction
+ target: "whippoorwill",
+ },
+ comment: {
+ // Node
+ nodeType: Node.COMMENT_NODE,
+ ownerDocument: document,
+ parentNode: testDiv,
+ parentElement: testDiv,
+ previousSibling: paras[4],
+ nextSibling: null,
+ nodeValue: "Alphabet soup?",
+ },
+ detachedComment: {
+ // Node
+ nodeType: Node.COMMENT_NODE,
+ ownerDocument: document,
+ parentNode: null,
+ parentElement: null,
+ previousSibling: null,
+ nextSibling: null,
+ nodeValue: "Stuvwxyz",
+ },
+ foreignComment: {
+ // Node
+ nodeType: Node.COMMENT_NODE,
+ ownerDocument: foreignDoc,
+ parentNode: foreignDoc,
+ parentElement: null,
+ previousSibling: foreignDoc.documentElement,
+ nextSibling: null,
+ nodeValue: '"Commenter" and "commentator" mean different things. I\'ve seen non-native speakers trip up on this.',
+ },
+ detachedForeignComment: {
+ // Node
+ nodeType: Node.COMMENT_NODE,
+ ownerDocument: foreignDoc,
+ parentNode: null,
+ parentElement: null,
+ previousSibling: null,
+ nextSibling: null,
+ nodeValue: "אריה יהודה",
+ },
+ xmlComment: {
+ // Node
+ nodeType: Node.COMMENT_NODE,
+ ownerDocument: xmlDoc,
+ parentNode: xmlDoc,
+ parentElement: null,
+ previousSibling: processingInstruction,
+ nextSibling: null,
+ nodeValue: "I maliciously created a comment that will break incautious XML serializers, but Firefox threw an exception, so all I got was this lousy T-shirt",
+ },
+ detachedXmlComment: {
+ // Node
+ nodeType: Node.COMMENT_NODE,
+ ownerDocument: xmlDoc,
+ parentNode: null,
+ parentElement: null,
+ previousSibling: null,
+ nextSibling: null,
+ nodeValue: "בן חיים אליעזר",
+ },
+ docfrag: {
+ // Node
+ nodeType: Node.DOCUMENT_FRAGMENT_NODE,
+ ownerDocument: document,
+ "childNodes.length": 0,
+ textContent: "",
+ },
+ foreignDocfrag: {
+ // Node
+ nodeType: Node.DOCUMENT_FRAGMENT_NODE,
+ ownerDocument: foreignDoc,
+ "childNodes.length": 0,
+ textContent: "",
+ },
+ xmlDocfrag: {
+ // Node
+ nodeType: Node.DOCUMENT_FRAGMENT_NODE,
+ ownerDocument: xmlDoc,
+ "childNodes.length": 0,
+ textContent: "",
+ },
+ doctype: {
+ // Node
+ nodeType: Node.DOCUMENT_TYPE_NODE,
+ ownerDocument: document,
+ parentNode: document,
+ previousSibling: null,
+ nextSibling: document.documentElement,
+
+ // DocumentType
+ name: "html",
+ publicId: "",
+ systemId: "",
+ },
+ foreignDoctype: {
+ // Node
+ nodeType: Node.DOCUMENT_TYPE_NODE,
+ ownerDocument: foreignDoc,
+ parentNode: foreignDoc,
+ previousSibling: null,
+ nextSibling: foreignDoc.documentElement,
+
+ // DocumentType
+ name: "html",
+ publicId: "",
+ systemId: "",
+ },
+ xmlDoctype: {
+ // Node
+ nodeType: Node.DOCUMENT_TYPE_NODE,
+ ownerDocument: xmlDoc,
+ parentNode: xmlDoc,
+ previousSibling: null,
+ nextSibling: xmlElement,
+
+ // DocumentType
+ name: "qorflesnorf",
+ publicId: "abcde",
+ systemId: "x\"'y",
+ },
+ "paras[0]": {
+ // Node
+ nodeType: Node.ELEMENT_NODE,
+ ownerDocument: document,
+ parentNode: testDiv,
+ parentElement: testDiv,
+ "childNodes.length": 1,
+ previousSibling: null,
+ nextSibling: paras[1],
+ textContent: "A\u0308b\u0308c\u0308d\u0308e\u0308f\u0308g\u0308h\u0308\n",
+
+ // Element
+ namespaceURI: "http://www.w3.org/1999/xhtml",
+ prefix: null,
+ localName: "p",
+ tagName: "P",
+ id: "a",
+ previousElementSibling: null,
+ nextElementSibling: paras[1],
+ childElementCount: 0,
+ },
+ "paras[1]": {
+ // Node
+ nodeType: Node.ELEMENT_NODE,
+ ownerDocument: document,
+ parentNode: testDiv,
+ parentElement: testDiv,
+ "childNodes.length": 1,
+ previousSibling: paras[0],
+ nextSibling: paras[2],
+ textContent: "Ijklmnop\n",
+
+ // Element
+ namespaceURI: "http://www.w3.org/1999/xhtml",
+ prefix: null,
+ localName: "p",
+ tagName: "P",
+ id: "b",
+ previousElementSibling: paras[0],
+ nextElementSibling: paras[2],
+ childElementCount: 0,
+ },
+ "paras[2]": {
+ // Node
+ nodeType: Node.ELEMENT_NODE,
+ ownerDocument: document,
+ parentNode: testDiv,
+ parentElement: testDiv,
+ "childNodes.length": 1,
+ previousSibling: paras[1],
+ nextSibling: paras[3],
+ textContent: "Qrstuvwx",
+
+ // Element
+ namespaceURI: "http://www.w3.org/1999/xhtml",
+ prefix: null,
+ localName: "p",
+ tagName: "P",
+ id: "c",
+ previousElementSibling: paras[1],
+ nextElementSibling: paras[3],
+ childElementCount: 0,
+ },
+ "paras[3]": {
+ // Node
+ nodeType: Node.ELEMENT_NODE,
+ ownerDocument: document,
+ parentNode: testDiv,
+ parentElement: testDiv,
+ "childNodes.length": 1,
+ previousSibling: paras[2],
+ nextSibling: paras[4],
+ textContent: "Yzabcdef",
+
+ // Element
+ namespaceURI: "http://www.w3.org/1999/xhtml",
+ prefix: null,
+ localName: "p",
+ tagName: "P",
+ id: "d",
+ previousElementSibling: paras[2],
+ nextElementSibling: paras[4],
+ childElementCount: 0,
+ },
+ "paras[4]": {
+ // Node
+ nodeType: Node.ELEMENT_NODE,
+ ownerDocument: document,
+ parentNode: testDiv,
+ parentElement: testDiv,
+ "childNodes.length": 1,
+ previousSibling: paras[3],
+ nextSibling: comment,
+ textContent: "Ghijklmn",
+
+ // Element
+ namespaceURI: "http://www.w3.org/1999/xhtml",
+ prefix: null,
+ localName: "p",
+ tagName: "P",
+ id: "e",
+ previousElementSibling: paras[3],
+ nextElementSibling: null,
+ childElementCount: 0,
+ },
+};
+
+for (var node in expected) {
+ // Now we set various default values by node type.
+ switch (expected[node].nodeType) {
+ case Node.ELEMENT_NODE:
+ expected[node].nodeName = expected[node].tagName;
+ expected[node].nodeValue = null;
+ expected[node]["children.length"] = expected[node].childElementCount;
+
+ if (expected[node].id === undefined) {
+ expected[node].id = "";
+ }
+ if (expected[node].className === undefined) {
+ expected[node].className = "";
+ }
+
+ var len = expected[node].childElementCount;
+ if (len === 0) {
+ expected[node].firstElementChild =
+ expected[node].lastElementChild = null;
+ } else {
+ // If we have expectations for the first/last child in children,
+ // use those. Otherwise, at least check that .firstElementChild ==
+ // .children[0] and .lastElementChild == .children[len - 1], even
+ // if we aren't sure what they should be.
+ expected[node].firstElementChild = expected[node]["children[0]"]
+ ? expected[node]["children[0]"]
+ : eval(node).children[0];
+ expected[node].lastElementChild =
+ expected[node]["children[" + (len - 1) + "]"]
+ ? expected[node]["children[" + (len - 1) + "]"]
+ : eval(node).children[len - 1];
+ }
+ break;
+
+ case Node.TEXT_NODE:
+ expected[node].nodeName = "#text";
+ expected[node]["childNodes.length"] = 0;
+ expected[node].textContent = expected[node].data =
+ expected[node].nodeValue;
+ expected[node].length = expected[node].nodeValue.length;
+ break;
+
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ expected[node].nodeName = expected[node].target;
+ expected[node]["childNodes.length"] = 0;
+ expected[node].textContent = expected[node].data =
+ expected[node].nodeValue;
+ expected[node].length = expected[node].nodeValue.length;
+ break;
+
+ case Node.COMMENT_NODE:
+ expected[node].nodeName = "#comment";
+ expected[node]["childNodes.length"] = 0;
+ expected[node].textContent = expected[node].data =
+ expected[node].nodeValue;
+ expected[node].length = expected[node].nodeValue.length;
+ break;
+
+ case Node.DOCUMENT_NODE:
+ expected[node].nodeName = "#document";
+ expected[node].ownerDocument = expected[node].parentNode =
+ expected[node].parentElement = expected[node].previousSibling =
+ expected[node].nextSibling = expected[node].nodeValue =
+ expected[node].textContent = null;
+ expected[node].documentURI = expected[node].URL;
+ expected[node].charset = expected[node].inputEncoding =
+ expected[node].characterSet;
+ break;
+
+ case Node.DOCUMENT_TYPE_NODE:
+ expected[node].nodeName = expected[node].name;
+ expected[node]["childNodes.length"] = 0;
+ expected[node].parentElement = expected[node].nodeValue =
+ expected[node].textContent = null;
+ break;
+
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ expected[node].nodeName = "#document-fragment";
+ expected[node].parentNode = expected[node].parentElement =
+ expected[node].previousSibling = expected[node].nextSibling =
+ expected[node].nodeValue = null;
+ break;
+ }
+
+ // Now we set some further default values that are independent of node
+ // type.
+ var len = expected[node]["childNodes.length"];
+ if (len === 0) {
+ expected[node].firstChild = expected[node].lastChild = null;
+ } else {
+ // If we have expectations for the first/last child in childNodes, use
+ // those. Otherwise, at least check that .firstChild == .childNodes[0]
+ // and .lastChild == .childNodes[len - 1], even if we aren't sure what
+ // they should be.
+ expected[node].firstChild = expected[node]["childNodes[0]"]
+ ? expected[node]["childNodes[0]"]
+ : eval(node).childNodes[0];
+ expected[node].lastChild =
+ expected[node]["childNodes[" + (len - 1) + "]"]
+ ? expected[node]["childNodes[" + (len - 1) + "]"]
+ : eval(node).childNodes[len - 1];
+ }
+ expected[node]["hasChildNodes()"] = !!expected[node]["childNodes.length"];
+
+ // Finally, we test!
+ for (var prop in expected[node]) {
+ test(function() {
+ assert_equals(eval(node + "." + prop), expected[node][prop]);
+ }, node + "." + prop);
+ }
+}
+
+testDiv.parentNode.removeChild(testDiv);
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-removeChild.html b/testing/web-platform/tests/dom/nodes/Node-removeChild.html
new file mode 100644
index 0000000000..6158423359
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-removeChild.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<title>Node.removeChild</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="creators.js"></script>
+<div id="log"></div>
+<iframe src=about:blank></iframe>
+<script>
+var documents = [
+ [function() { return document }, "the main document"],
+ [function() { return frames[0].document }, "a frame document"],
+ [function() { return document.implementation.createHTMLDocument() },
+ "a synthetic document"],
+];
+
+documents.forEach(function(d) {
+ var get = d[0], description = d[1]
+ for (var p in creators) {
+ var creator = creators[p];
+ test(function() {
+ var doc = get();
+ var s = doc[creator]("a")
+ assert_equals(s.ownerDocument, doc)
+ assert_throws_dom("NOT_FOUND_ERR", function() { document.body.removeChild(s) })
+ assert_equals(s.ownerDocument, doc)
+ }, "Passing a detached " + p + " from " + description +
+ " to removeChild should not affect it.")
+
+ test(function() {
+ var doc = get();
+ var s = doc[creator]("b")
+ doc.documentElement.appendChild(s)
+ assert_equals(s.ownerDocument, doc)
+ assert_throws_dom("NOT_FOUND_ERR", function() { document.body.removeChild(s) })
+ assert_equals(s.ownerDocument, doc)
+ }, "Passing a non-detached " + p + " from " + description +
+ " to removeChild should not affect it.")
+
+ test(function() {
+ var doc = get();
+ var s = doc[creator]("test")
+ doc.body.appendChild(s)
+ assert_equals(s.ownerDocument, doc)
+ assert_throws_dom(
+ "NOT_FOUND_ERR",
+ (doc.defaultView || self).DOMException,
+ function() { s.removeChild(doc) }
+ );
+ }, "Calling removeChild on a " + p + " from " + description +
+ " with no children should throw NOT_FOUND_ERR.")
+ }
+});
+
+test(function() {
+ assert_throws_js(TypeError, function() { document.body.removeChild(null) })
+ assert_throws_js(TypeError, function() { document.body.removeChild({'a':'b'}) })
+}, "Passing a value that is not a Node reference to removeChild should throw TypeError.")
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-replaceChild.html b/testing/web-platform/tests/dom/nodes/Node-replaceChild.html
new file mode 100644
index 0000000000..74aac67d43
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-replaceChild.html
@@ -0,0 +1,349 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Node.replaceChild</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body><a><b></b><c></c></a>
+<div id="log"></div>
+<!-- First test shared pre-insertion checks that work similarly for replaceChild
+ and insertBefore -->
+<script>
+ var insertFunc = Node.prototype.replaceChild;
+</script>
+<script src="pre-insertion-validation-notfound.js"></script>
+<script>
+// IDL.
+test(function() {
+ var a = document.createElement("div");
+ assert_throws_js(TypeError, function() {
+ a.replaceChild(null, null);
+ });
+
+ var b = document.createElement("div");
+ assert_throws_js(TypeError, function() {
+ a.replaceChild(b, null);
+ });
+ assert_throws_js(TypeError, function() {
+ a.replaceChild(null, b);
+ });
+}, "Passing null to replaceChild should throw a TypeError.")
+
+// Step 3.
+test(function() {
+ var a = document.createElement("div");
+ var b = document.createElement("div");
+ var c = document.createElement("div");
+ assert_throws_dom("NotFoundError", function() {
+ a.replaceChild(b, c);
+ });
+
+ var d = document.createElement("div");
+ d.appendChild(b);
+ assert_throws_dom("NotFoundError", function() {
+ a.replaceChild(b, c);
+ });
+ assert_throws_dom("NotFoundError", function() {
+ a.replaceChild(b, a);
+ });
+}, "If child's parent is not the context node, a NotFoundError exception should be thrown");
+
+// Step 1.
+test(function() {
+ var nodes = getNonParentNodes();
+
+ var a = document.createElement("div");
+ var b = document.createElement("div");
+ nodes.forEach(function(node) {
+ assert_throws_dom("HierarchyRequestError", function() {
+ node.replaceChild(a, b);
+ });
+ });
+}, "If the context node is not a node that can contain children, a HierarchyRequestError exception should be thrown")
+
+// Step 2.
+test(function() {
+ var a = document.createElement("div");
+ var b = document.createElement("div");
+
+ assert_throws_dom("HierarchyRequestError", function() {
+ a.replaceChild(a, a);
+ });
+
+ a.appendChild(b);
+ assert_throws_dom("HierarchyRequestError", function() {
+ a.replaceChild(a, b);
+ });
+
+ var c = document.createElement("div");
+ c.appendChild(a);
+ assert_throws_dom("HierarchyRequestError", function() {
+ a.replaceChild(c, b);
+ });
+}, "If node is an inclusive ancestor of the context node, a HierarchyRequestError should be thrown.")
+
+// Steps 4/5.
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var doc2 = document.implementation.createHTMLDocument("title2");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(doc2, doc.documentElement);
+ });
+
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(doc.createTextNode("text"), doc.documentElement);
+ });
+}, "If the context node is a document, inserting a document or text node should throw a HierarchyRequestError.")
+
+// Step 6.1.
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+
+ var df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ df.appendChild(doc.createElement("b"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(df, doc.documentElement);
+ });
+
+ df = doc.createDocumentFragment();
+ df.appendChild(doc.createTextNode("text"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(df, doc.documentElement);
+ });
+
+ df = doc.createDocumentFragment();
+ df.appendChild(doc.createComment("comment"));
+ df.appendChild(doc.createTextNode("text"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(df, doc.documentElement);
+ });
+}, "If the context node is a document, inserting a DocumentFragment that contains a text node or too many elements should throw a HierarchyRequestError.")
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ doc.removeChild(doc.documentElement);
+
+ var df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ df.appendChild(doc.createElement("b"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(df, doc.doctype);
+ });
+}, "If the context node is a document (without element children), inserting a DocumentFragment that contains multiple elements should throw a HierarchyRequestError.")
+
+// Step 6.1.
+test(function() {
+ // The context node has an element child that is not /child/.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.appendChild(doc.createComment("foo"));
+ assert_array_equals(doc.childNodes, [doc.doctype, doc.documentElement, comment]);
+
+ var df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(df, comment);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(df, doc.doctype);
+ });
+}, "If the context node is a document, inserting a DocumentFragment with an element if there already is an element child should throw a HierarchyRequestError.")
+test(function() {
+ // A doctype is following /child/.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.insertBefore(doc.createComment("foo"), doc.firstChild);
+ doc.removeChild(doc.documentElement);
+ assert_array_equals(doc.childNodes, [comment, doc.doctype]);
+
+ var df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(df, comment);
+ });
+}, "If the context node is a document, inserting a DocumentFragment with an element before the doctype should throw a HierarchyRequestError.")
+
+// Step 6.2.
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.appendChild(doc.createComment("foo"));
+ assert_array_equals(doc.childNodes, [doc.doctype, doc.documentElement, comment]);
+
+ var a = doc.createElement("a");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(a, comment);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(a, doc.doctype);
+ });
+}, "If the context node is a document, inserting an element if there already is an element child should throw a HierarchyRequestError.")
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.insertBefore(doc.createComment("foo"), doc.firstChild);
+ doc.removeChild(doc.documentElement);
+ assert_array_equals(doc.childNodes, [comment, doc.doctype]);
+
+ var a = doc.createElement("a");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(a, comment);
+ });
+}, "If the context node is a document, inserting an element before the doctype should throw a HierarchyRequestError.")
+
+// Step 6.3.
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.insertBefore(doc.createComment("foo"), doc.firstChild);
+ assert_array_equals(doc.childNodes, [comment, doc.doctype, doc.documentElement]);
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(doctype, comment);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(doctype, doc.documentElement);
+ });
+}, "If the context node is a document, inserting a doctype if there already is a doctype child should throw a HierarchyRequestError.")
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.appendChild(doc.createComment("foo"));
+ doc.removeChild(doc.doctype);
+ assert_array_equals(doc.childNodes, [doc.documentElement, comment]);
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.replaceChild(doctype, comment);
+ });
+}, "If the context node is a document, inserting a doctype after the document element should throw a HierarchyRequestError.")
+
+// Steps 4/5.
+test(function() {
+ var df = document.createDocumentFragment();
+ var a = df.appendChild(document.createElement("a"));
+
+ var doc = document.implementation.createHTMLDocument("title");
+ assert_throws_dom("HierarchyRequestError", function() {
+ df.replaceChild(doc, a);
+ });
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ df.replaceChild(doctype, a);
+ });
+}, "If the context node is a DocumentFragment, inserting a document or a doctype should throw a HierarchyRequestError.")
+test(function() {
+ var el = document.createElement("div");
+ var a = el.appendChild(document.createElement("a"));
+
+ var doc = document.implementation.createHTMLDocument("title");
+ assert_throws_dom("HierarchyRequestError", function() {
+ el.replaceChild(doc, a);
+ });
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ el.replaceChild(doctype, a);
+ });
+}, "If the context node is an element, inserting a document or a doctype should throw a HierarchyRequestError.")
+
+// Step 6.
+test(function() {
+ var a = document.createElement("div");
+ var b = document.createElement("div");
+ var c = document.createElement("div");
+ a.appendChild(b);
+ a.appendChild(c);
+ assert_array_equals(a.childNodes, [b, c]);
+ assert_equals(a.replaceChild(c, b), b);
+ assert_array_equals(a.childNodes, [c]);
+}, "Replacing a node with its next sibling should work (2 children)");
+test(function() {
+ var a = document.createElement("div");
+ var b = document.createElement("div");
+ var c = document.createElement("div");
+ var d = document.createElement("div");
+ var e = document.createElement("div");
+ a.appendChild(b);
+ a.appendChild(c);
+ a.appendChild(d);
+ a.appendChild(e);
+ assert_array_equals(a.childNodes, [b, c, d, e]);
+ assert_equals(a.replaceChild(d, c), c);
+ assert_array_equals(a.childNodes, [b, d, e]);
+}, "Replacing a node with its next sibling should work (4 children)");
+test(function() {
+ var a = document.createElement("div");
+ var b = document.createElement("div");
+ var c = document.createElement("div");
+ a.appendChild(b);
+ a.appendChild(c);
+ assert_array_equals(a.childNodes, [b, c]);
+ assert_equals(a.replaceChild(b, b), b);
+ assert_array_equals(a.childNodes, [b, c]);
+ assert_equals(a.replaceChild(c, c), c);
+ assert_array_equals(a.childNodes, [b, c]);
+}, "Replacing a node with itself should not move the node");
+
+// Step 7.
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var doctype = doc.doctype;
+ assert_array_equals(doc.childNodes, [doctype, doc.documentElement]);
+
+ var doc2 = document.implementation.createHTMLDocument("title2");
+ var doctype2 = doc2.doctype;
+ assert_array_equals(doc2.childNodes, [doctype2, doc2.documentElement]);
+
+ doc.replaceChild(doc2.doctype, doc.doctype);
+ assert_array_equals(doc.childNodes, [doctype2, doc.documentElement]);
+ assert_array_equals(doc2.childNodes, [doc2.documentElement]);
+ assert_equals(doctype.parentNode, null);
+ assert_equals(doctype.ownerDocument, doc);
+ assert_equals(doctype2.parentNode, doc);
+ assert_equals(doctype2.ownerDocument, doc);
+}, "If the context node is a document, inserting a new doctype should work.")
+
+// Bugs.
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var df = doc.createDocumentFragment();
+ var a = df.appendChild(doc.createElement("a"));
+ assert_equals(doc.documentElement, doc.replaceChild(df, doc.documentElement));
+ assert_array_equals(doc.childNodes, [doc.doctype, a]);
+}, "Replacing the document element with a DocumentFragment containing a single element should work.");
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var df = doc.createDocumentFragment();
+ var a = df.appendChild(doc.createComment("a"));
+ var b = df.appendChild(doc.createElement("b"));
+ var c = df.appendChild(doc.createComment("c"));
+ assert_equals(doc.documentElement, doc.replaceChild(df, doc.documentElement));
+ assert_array_equals(doc.childNodes, [doc.doctype, a, b, c]);
+}, "Replacing the document element with a DocumentFragment containing a single element and comments should work.");
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var a = doc.createElement("a");
+ assert_equals(doc.documentElement, doc.replaceChild(a, doc.documentElement));
+ assert_array_equals(doc.childNodes, [doc.doctype, a]);
+}, "Replacing the document element with a single element should work.");
+test(function() {
+ document.addEventListener("DOMNodeRemoved", function(e) {
+ document.body.appendChild(document.createElement("x"));
+ }, false);
+ var a = document.body.firstChild, b = a.firstChild, c = b.nextSibling;
+ assert_equals(a.replaceChild(c, b), b);
+ assert_equals(b.parentNode, null);
+ assert_equals(a.firstChild, c);
+ assert_equals(c.parentNode, a);
+}, "replaceChild should work in the presence of mutation events.")
+test(function() {
+ var TEST_ID = "findme";
+ var gBody = document.getElementsByTagName("body")[0];
+ var parent = document.createElement("div");
+ gBody.appendChild(parent);
+ var child = document.createElement("div");
+ parent.appendChild(child);
+ var df = document.createDocumentFragment();
+ var fragChild = df.appendChild(document.createElement("div"));
+ fragChild.setAttribute("id", TEST_ID);
+ parent.replaceChild(df, child);
+ assert_equals(document.getElementById(TEST_ID), fragChild, "should not be null");
+}, "Replacing an element with a DocumentFragment should allow a child of the DocumentFragment to be found by Id.")
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Node-textContent.html b/testing/web-platform/tests/dom/nodes/Node-textContent.html
new file mode 100644
index 0000000000..cf2e072087
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Node-textContent.html
@@ -0,0 +1,265 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Node.textContent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+// XXX mutation observers?
+// XXX Range gravitation?
+
+var documents, doctypes;
+setup(function() {
+ documents = [
+ [document, "parser"],
+ [document.implementation.createDocument("", "test", null), "createDocument"],
+ [document.implementation.createHTMLDocument("title"), "createHTMLDocument"],
+ ]
+ doctypes = [
+ [document.doctype, "parser"],
+ [document.implementation.createDocumentType("x", "", ""), "script"],
+ ]
+})
+
+// Getting
+// DocumentFragment, Element:
+test(function() {
+ var element = document.createElement("div")
+ assert_equals(element.textContent, "")
+}, "For an empty Element, textContent should be the empty string")
+
+test(function() {
+ assert_equals(document.createDocumentFragment().textContent, "")
+}, "For an empty DocumentFragment, textContent should be the empty string")
+
+test(function() {
+ var el = document.createElement("div")
+ el.appendChild(document.createComment(" abc "))
+ el.appendChild(document.createTextNode("\tDEF\t"))
+ el.appendChild(document.createProcessingInstruction("x", " ghi "))
+ assert_equals(el.textContent, "\tDEF\t")
+}, "Element with children")
+
+test(function() {
+ var el = document.createElement("div")
+ var child = document.createElement("div")
+ el.appendChild(child)
+ child.appendChild(document.createComment(" abc "))
+ child.appendChild(document.createTextNode("\tDEF\t"))
+ child.appendChild(document.createProcessingInstruction("x", " ghi "))
+ assert_equals(el.textContent, "\tDEF\t")
+}, "Element with descendants")
+
+test(function() {
+ var df = document.createDocumentFragment()
+ df.appendChild(document.createComment(" abc "))
+ df.appendChild(document.createTextNode("\tDEF\t"))
+ df.appendChild(document.createProcessingInstruction("x", " ghi "))
+ assert_equals(df.textContent, "\tDEF\t")
+}, "DocumentFragment with children")
+
+test(function() {
+ var df = document.createDocumentFragment()
+ var child = document.createElement("div")
+ df.appendChild(child)
+ child.appendChild(document.createComment(" abc "))
+ child.appendChild(document.createTextNode("\tDEF\t"))
+ child.appendChild(document.createProcessingInstruction("x", " ghi "))
+ assert_equals(df.textContent, "\tDEF\t")
+}, "DocumentFragment with descendants")
+
+// Text, ProcessingInstruction, Comment:
+test(function() {
+ assert_equals(document.createTextNode("").textContent, "")
+}, "For an empty Text, textContent should be the empty string")
+
+test(function() {
+ assert_equals(document.createProcessingInstruction("x", "").textContent, "")
+}, "For an empty ProcessingInstruction, textContent should be the empty string")
+
+test(function() {
+ assert_equals(document.createComment("").textContent, "")
+}, "For an empty Comment, textContent should be the empty string")
+
+test(function() {
+ assert_equals(document.createTextNode("abc").textContent, "abc")
+}, "For a Text with data, textContent should be that data")
+
+test(function() {
+ assert_equals(document.createProcessingInstruction("x", "abc").textContent,
+ "abc")
+}, "For a ProcessingInstruction with data, textContent should be that data")
+
+test(function() {
+ assert_equals(document.createComment("abc").textContent, "abc")
+}, "For a Comment with data, textContent should be that data")
+
+// Any other node:
+documents.forEach(function(argument) {
+ var doc = argument[0], creator = argument[1]
+ test(function() {
+ assert_equals(doc.textContent, null)
+ }, "For Documents created by " + creator + ", textContent should be null")
+})
+
+doctypes.forEach(function(argument) {
+ var doctype = argument[0], creator = argument[1]
+ test(function() {
+ assert_equals(doctype.textContent, null)
+ }, "For DocumentType created by " + creator + ", textContent should be null")
+})
+
+// Setting
+// DocumentFragment, Element:
+var testArgs = [
+ [null, null],
+ [undefined, null],
+ ["", null],
+ [42, "42"],
+ ["abc", "abc"],
+ ["<b>xyz<\/b>", "<b>xyz<\/b>"],
+ ["d\0e", "d\0e"]
+ // XXX unpaired surrogate?
+]
+testArgs.forEach(function(aValue) {
+ var argument = aValue[0], expectation = aValue[1]
+ var check = function(aElementOrDocumentFragment) {
+ if (expectation === null) {
+ assert_equals(aElementOrDocumentFragment.textContent, "")
+ assert_equals(aElementOrDocumentFragment.firstChild, null)
+ } else {
+ assert_equals(aElementOrDocumentFragment.textContent, expectation)
+ assert_equals(aElementOrDocumentFragment.childNodes.length, 1,
+ "Should have one child")
+ var firstChild = aElementOrDocumentFragment.firstChild
+ assert_true(firstChild instanceof Text, "child should be a Text")
+ assert_equals(firstChild.data, expectation)
+ }
+ }
+
+ test(function() {
+ var el = document.createElement("div")
+ el.textContent = argument
+ check(el)
+ }, "Element without children set to " + format_value(argument))
+
+ test(function() {
+ var el = document.createElement("div")
+ var text = el.appendChild(document.createTextNode(""))
+ el.textContent = argument
+ check(el)
+ assert_equals(text.parentNode, null,
+ "Preexisting Text should have been removed")
+ }, "Element with empty text node as child set to " + format_value(argument))
+
+ test(function() {
+ var el = document.createElement("div")
+ el.appendChild(document.createComment(" abc "))
+ el.appendChild(document.createTextNode("\tDEF\t"))
+ el.appendChild(document.createProcessingInstruction("x", " ghi "))
+ el.textContent = argument
+ check(el)
+ }, "Element with children set to " + format_value(argument))
+
+ test(function() {
+ var el = document.createElement("div")
+ var child = document.createElement("div")
+ el.appendChild(child)
+ child.appendChild(document.createComment(" abc "))
+ child.appendChild(document.createTextNode("\tDEF\t"))
+ child.appendChild(document.createProcessingInstruction("x", " ghi "))
+ el.textContent = argument
+ check(el)
+ assert_equals(child.childNodes.length, 3,
+ "Should not have changed the internal structure of the removed nodes.")
+ }, "Element with descendants set to " + format_value(argument))
+
+ test(function() {
+ var df = document.createDocumentFragment()
+ df.textContent = argument
+ check(df)
+ }, "DocumentFragment without children set to " + format_value(argument))
+
+ test(function() {
+ var df = document.createDocumentFragment()
+ var text = df.appendChild(document.createTextNode(""))
+ df.textContent = argument
+ check(df)
+ assert_equals(text.parentNode, null,
+ "Preexisting Text should have been removed")
+ }, "DocumentFragment with empty text node as child set to " + format_value(argument))
+
+ test(function() {
+ var df = document.createDocumentFragment()
+ df.appendChild(document.createComment(" abc "))
+ df.appendChild(document.createTextNode("\tDEF\t"))
+ df.appendChild(document.createProcessingInstruction("x", " ghi "))
+ df.textContent = argument
+ check(df)
+ }, "DocumentFragment with children set to " + format_value(argument))
+
+ test(function() {
+ var df = document.createDocumentFragment()
+ var child = document.createElement("div")
+ df.appendChild(child)
+ child.appendChild(document.createComment(" abc "))
+ child.appendChild(document.createTextNode("\tDEF\t"))
+ child.appendChild(document.createProcessingInstruction("x", " ghi "))
+ df.textContent = argument
+ check(df)
+ assert_equals(child.childNodes.length, 3,
+ "Should not have changed the internal structure of the removed nodes.")
+ }, "DocumentFragment with descendants set to " + format_value(argument))
+})
+
+// Text, ProcessingInstruction, Comment:
+test(function() {
+ var text = document.createTextNode("abc")
+ text.textContent = "def"
+ assert_equals(text.textContent, "def")
+ assert_equals(text.data, "def")
+}, "For a Text, textContent should set the data")
+
+test(function() {
+ var pi = document.createProcessingInstruction("x", "abc")
+ pi.textContent = "def"
+ assert_equals(pi.textContent, "def")
+ assert_equals(pi.data, "def")
+ assert_equals(pi.target, "x")
+}, "For a ProcessingInstruction, textContent should set the data")
+
+test(function() {
+ var comment = document.createComment("abc")
+ comment.textContent = "def"
+ assert_equals(comment.textContent, "def")
+ assert_equals(comment.data, "def")
+}, "For a Comment, textContent should set the data")
+
+// Any other node:
+documents.forEach(function(argument) {
+ var doc = argument[0], creator = argument[1]
+ test(function() {
+ var root = doc.documentElement
+ doc.textContent = "a"
+ assert_equals(doc.textContent, null)
+ assert_equals(doc.documentElement, root)
+ }, "For Documents created by " + creator + ", setting textContent should do nothing")
+})
+
+doctypes.forEach(function(argument) {
+ var doctype = argument[0], creator = argument[1]
+ test(function() {
+ var props = {
+ name: doctype.name,
+ publicId: doctype.publicId,
+ systemId: doctype.systemId,
+ }
+ doctype.textContent = "b"
+ assert_equals(doctype.textContent, null)
+ assert_equals(doctype.name, props.name, "name should not change")
+ assert_equals(doctype.publicId, props.publicId, "publicId should not change")
+ assert_equals(doctype.systemId, props.systemId, "systemId should not change")
+ }, "For DocumentType created by " + creator + ", setting textContent should do nothing")
+})
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/NodeList-Iterable.html b/testing/web-platform/tests/dom/nodes/NodeList-Iterable.html
new file mode 100644
index 0000000000..fcbee175cb
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/NodeList-Iterable.html
@@ -0,0 +1,61 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>NodeList Iterable Test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+ <p id="1"></p>
+ <p id="2"></p>
+ <p id="3"></p>
+ <p id="4"></p>
+ <p id="5"></p>
+
+ <div id="live"><b id="b1">1</b><b id="b2">2</b><b id="b3">3</b></div>
+<script>
+ var paragraphs;
+ setup(function() {
+ paragraphs = document.querySelectorAll('p');
+ })
+ test(function() {
+ assert_true('length' in paragraphs);
+ }, 'NodeList has length method.');
+ test(function() {
+ assert_true('values' in paragraphs);
+ }, 'NodeList has values method.');
+ test(function() {
+ assert_true('entries' in paragraphs);
+ }, 'NodeList has entries method.');
+ test(function() {
+ assert_true('forEach' in paragraphs);
+ }, 'NodeList has forEach method.');
+ test(function() {
+ assert_true(Symbol.iterator in paragraphs);
+ }, 'NodeList has Symbol.iterator.');
+ test(function() {
+ var ids = "12345", idx=0;
+ for(var node of paragraphs){
+ assert_equals(node.getAttribute('id'), ids[idx++]);
+ }
+ }, 'NodeList is iterable via for-of loop.');
+
+ test(function() {
+ assert_array_equals(Object.keys(paragraphs), ['0', '1', '2', '3', '4']);
+ }, 'NodeList responds to Object.keys correctly');
+
+ test(function() {
+ var container = document.getElementById('live');
+ var nodeList = container.childNodes;
+
+ var ids = [];
+ for (var el of nodeList) {
+ ids.push(el.id);
+ assert_equals(el.localName, 'b');
+ if (ids.length < 3) {
+ var newEl = document.createElement('b');
+ newEl.id = 'after' + el.id;
+ container.appendChild(newEl);
+ }
+ }
+
+ assert_array_equals(ids, ['b1', 'b2', 'b3', 'afterb1', 'afterb2']);
+ }, 'live NodeLists are for-of iterable and update appropriately');
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/NodeList-live-mutations.window.js b/testing/web-platform/tests/dom/nodes/NodeList-live-mutations.window.js
new file mode 100644
index 0000000000..a11fed1e38
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/NodeList-live-mutations.window.js
@@ -0,0 +1,79 @@
+function testNodeList(name, hooks) {
+ test(() => {
+ const nodes = {
+ root: document.createElement("div"),
+ div1: document.createElement("div"),
+ div2: document.createElement("div"),
+ p: document.createElement("p")
+ };
+
+ const list = nodes.root.childNodes;
+
+ hooks.initial(list, nodes);
+
+ nodes.root.appendChild(nodes.div1);
+ nodes.root.appendChild(nodes.p);
+ nodes.root.appendChild(nodes.div2);
+
+ hooks.afterInsertion(list, nodes);
+
+ nodes.root.removeChild(nodes.div1);
+
+ hooks.afterRemoval(list, nodes);
+ }, `NodeList live mutations: ${name}`);
+}
+
+testNodeList("NodeList.length", {
+ initial(list) {
+ assert_equals(list.length, 0);
+ },
+ afterInsertion(list) {
+ assert_equals(list.length, 3);
+ },
+ afterRemoval(list) {
+ assert_equals(list.length, 2);
+ }
+});
+
+testNodeList("NodeList.item(index)", {
+ initial(list) {
+ assert_equals(list.item(0), null);
+ },
+ afterInsertion(list, nodes) {
+ assert_equals(list.item(0), nodes.div1);
+ assert_equals(list.item(1), nodes.p);
+ assert_equals(list.item(2), nodes.div2);
+ },
+ afterRemoval(list, nodes) {
+ assert_equals(list.item(0), nodes.p);
+ assert_equals(list.item(1), nodes.div2);
+ }
+});
+
+testNodeList("NodeList[index]", {
+ initial(list) {
+ assert_equals(list[0], undefined);
+ },
+ afterInsertion(list, nodes) {
+ assert_equals(list[0], nodes.div1);
+ assert_equals(list[1], nodes.p);
+ assert_equals(list[2], nodes.div2);
+ },
+ afterRemoval(list, nodes) {
+ assert_equals(list[0], nodes.p);
+ assert_equals(list[1], nodes.div2);
+ }
+});
+
+testNodeList("NodeList ownPropertyNames", {
+ initial(list) {
+ assert_object_equals(Object.getOwnPropertyNames(list), []);
+ },
+ afterInsertion(list) {
+ assert_object_equals(Object.getOwnPropertyNames(list), ["0", "1", "2"]);
+ },
+ afterRemoval(list) {
+ assert_object_equals(Object.getOwnPropertyNames(list), ["0", "1"]);
+ }
+});
+
diff --git a/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-1.html b/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-1.html
new file mode 100644
index 0000000000..c5c58f9d12
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-1.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta name=timeout content=long>
+<title>NodeList (static collection) "length" getter tampered</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+
+<script src="support/NodeList-static-length-tampered.js"></script>
+<script>
+test(() => {
+ const nodeList = makeStaticNodeList(100);
+
+ for (var i = 0; i < 50; i++) {
+ if (i === 25)
+ Object.defineProperty(nodeList, "length", { get() { return 10; } });
+
+ assert_equals(indexOfNodeList(nodeList), i >= 25 ? -1 : 50);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-2.html b/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-2.html
new file mode 100644
index 0000000000..bac0511202
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-2.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta name=timeout content=long>
+<title>NodeList (static collection) "length" getter tampered</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+
+<script src="support/NodeList-static-length-tampered.js"></script>
+<script>
+test(() => {
+ const nodeList = makeStaticNodeList(100);
+
+ for (var i = 0; i < 50; i++) {
+ if (i === 25)
+ Object.setPrototypeOf(nodeList, { get length() { return 10; } });
+
+ assert_equals(indexOfNodeList(nodeList), i >= 25 ? -1 : 50);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-3.html b/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-3.html
new file mode 100644
index 0000000000..9690aab3c1
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-3.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta name=timeout content=long>
+<title>NodeList (static collection) "length" getter tampered</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+
+<script src="support/NodeList-static-length-tampered.js"></script>
+<script>
+test(() => {
+ const nodeList = makeStaticNodeList(100);
+
+ for (var i = 0; i < 50; i++) {
+ if (i === 25)
+ Object.defineProperty(NodeList.prototype, "length", { get() { return 10; } });
+
+ assert_equals(indexOfNodeList(nodeList), i >= 25 ? -1 : 50);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-indexOf-1.html b/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-indexOf-1.html
new file mode 100644
index 0000000000..5ce4146757
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-indexOf-1.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta name=timeout content=long>
+<title>NodeList (static collection) "length" getter tampered (Array.prototype.indexOf)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+
+<script src="support/NodeList-static-length-tampered.js"></script>
+<script>
+test(() => {
+ const nodeList = makeStaticNodeList(100);
+
+ for (var i = 0; i < 50; i++) {
+ if (i === 25)
+ Object.defineProperty(nodeList, "length", { get() { return 10; } });
+
+ assert_equals(arrayIndexOfNodeList(nodeList), i >= 25 ? -1 : 50);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-indexOf-2.html b/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-indexOf-2.html
new file mode 100644
index 0000000000..57814ed5ac
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-indexOf-2.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta name=timeout content=long>
+<title>NodeList (static collection) "length" getter tampered (Array.prototype.indexOf)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+
+<script src="support/NodeList-static-length-tampered.js"></script>
+<script>
+test(() => {
+ const nodeList = makeStaticNodeList(100);
+
+ for (var i = 0; i < 50; i++) {
+ if (i === 25)
+ Object.setPrototypeOf(nodeList, { get length() { return 10; } });
+
+ assert_equals(arrayIndexOfNodeList(nodeList), i >= 25 ? -1 : 50);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-indexOf-3.html b/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-indexOf-3.html
new file mode 100644
index 0000000000..838f376dd2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/NodeList-static-length-getter-tampered-indexOf-3.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta name=timeout content=long>
+<title>NodeList (static collection) "length" getter tampered (Array.prototype.indexOf)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+
+<script src="support/NodeList-static-length-tampered.js"></script>
+<script>
+test(() => {
+ const nodeList = makeStaticNodeList(100);
+
+ for (var i = 0; i < 50; i++) {
+ if (i === 25)
+ Object.defineProperty(NodeList.prototype, "length", { get() { return 10; } });
+
+ assert_equals(arrayIndexOfNodeList(nodeList), i >= 25 ? -1 : 50);
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-append.html b/testing/web-platform/tests/dom/nodes/ParentNode-append.html
new file mode 100644
index 0000000000..4e101f73a2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-append.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>ParentNode.append</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-parentnode-append">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="pre-insertion-validation-hierarchy.js"></script>
+<script>
+preInsertionValidateHierarchy("append");
+
+function test_append(node, nodeName) {
+ test(function() {
+ const parent = node.cloneNode();
+ parent.append();
+ assert_array_equals(parent.childNodes, []);
+ }, nodeName + '.append() without any argument, on a parent having no child.');
+
+ test(function() {
+ const parent = node.cloneNode();
+ parent.append(null);
+ assert_equals(parent.childNodes[0].textContent, 'null');
+ }, nodeName + '.append() with null as an argument, on a parent having no child.');
+
+ test(function() {
+ const parent = node.cloneNode();
+ parent.append(undefined);
+ assert_equals(parent.childNodes[0].textContent, 'undefined');
+ }, nodeName + '.append() with undefined as an argument, on a parent having no child.');
+
+ test(function() {
+ const parent = node.cloneNode();
+ parent.append('text');
+ assert_equals(parent.childNodes[0].textContent, 'text');
+ }, nodeName + '.append() with only text as an argument, on a parent having no child.');
+
+ test(function() {
+ const parent = node.cloneNode();
+ const x = document.createElement('x');
+ parent.append(x);
+ assert_array_equals(parent.childNodes, [x]);
+ }, nodeName + '.append() with only one element as an argument, on a parent having no child.');
+
+ test(function() {
+ const parent = node.cloneNode();
+ const child = document.createElement('test');
+ parent.appendChild(child);
+ parent.append(null);
+ assert_equals(parent.childNodes[0], child);
+ assert_equals(parent.childNodes[1].textContent, 'null');
+ }, nodeName + '.append() with null as an argument, on a parent having a child.');
+
+ test(function() {
+ const parent = node.cloneNode();
+ const x = document.createElement('x');
+ const child = document.createElement('test');
+ parent.appendChild(child);
+ parent.append(x, 'text');
+ assert_equals(parent.childNodes[0], child);
+ assert_equals(parent.childNodes[1], x);
+ assert_equals(parent.childNodes[2].textContent, 'text');
+ }, nodeName + '.append() with one element and text as argument, on a parent having a child.');
+}
+
+test_append(document.createElement('div'), 'Element');
+test_append(document.createDocumentFragment(), 'DocumentFragment');
+</script>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-children.html b/testing/web-platform/tests/dom/nodes/ParentNode-children.html
new file mode 100644
index 0000000000..6621e7d9de
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-children.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>ParentNode.children</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-parentnode-children">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div style="display: none">
+ <ul><li id='test'>1</li><li>2</li><li>3</li><li>4</li></ul>
+</div>
+<script>
+test(() => {
+ var node = document.getElementById("test");
+ var parentNode = node.parentNode;
+ var children = parentNode.children;
+ assert_true(children instanceof HTMLCollection);
+ var li = document.createElement("li");
+ assert_equals(children.length, 4);
+
+ parentNode.appendChild(li);
+ assert_equals(children.length, 5);
+
+ parentNode.removeChild(li);
+ assert_equals(children.length, 4);
+}, "ParentNode.children should be a live collection");
+</script>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-prepend.html b/testing/web-platform/tests/dom/nodes/ParentNode-prepend.html
new file mode 100644
index 0000000000..f6aa38a2dd
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-prepend.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>ParentNode.prepend</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-parentnode-prepend">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="pre-insertion-validation-hierarchy.js"></script>
+<script>
+preInsertionValidateHierarchy("prepend");
+
+function test_prepend(node, nodeName) {
+ test(function() {
+ const parent = node.cloneNode();
+ parent.prepend();
+ assert_array_equals(parent.childNodes, []);
+ }, nodeName + '.prepend() without any argument, on a parent having no child.');
+
+ test(function() {
+ const parent = node.cloneNode();
+ parent.prepend(null);
+ assert_equals(parent.childNodes[0].textContent, 'null');
+ }, nodeName + '.prepend() with null as an argument, on a parent having no child.');
+
+ test(function() {
+ const parent = node.cloneNode();
+ parent.prepend(undefined);
+ assert_equals(parent.childNodes[0].textContent, 'undefined');
+ }, nodeName + '.prepend() with undefined as an argument, on a parent having no child.');
+
+ test(function() {
+ const parent = node.cloneNode();
+ parent.prepend('text');
+ assert_equals(parent.childNodes[0].textContent, 'text');
+ }, nodeName + '.prepend() with only text as an argument, on a parent having no child.');
+
+ test(function() {
+ const parent = node.cloneNode();
+ const x = document.createElement('x');
+ parent.prepend(x);
+ assert_array_equals(parent.childNodes, [x]);
+ }, nodeName + '.prepend() with only one element as an argument, on a parent having no child.');
+
+ test(function() {
+ const parent = node.cloneNode();
+ const child = document.createElement('test');
+ parent.appendChild(child);
+ parent.prepend(null);
+ assert_equals(parent.childNodes[0].textContent, 'null');
+ assert_equals(parent.childNodes[1], child);
+ }, nodeName + '.prepend() with null as an argument, on a parent having a child.');
+
+ test(function() {
+ const parent = node.cloneNode();
+ const x = document.createElement('x');
+ const child = document.createElement('test');
+ parent.appendChild(child);
+ parent.prepend(x, 'text');
+ assert_equals(parent.childNodes[0], x);
+ assert_equals(parent.childNodes[1].textContent, 'text');
+ assert_equals(parent.childNodes[2], child);
+ }, nodeName + '.prepend() with one element and text as argument, on a parent having a child.');
+}
+
+test_prepend(document.createElement('div'), 'Element');
+test_prepend(document.createDocumentFragment(), 'DocumentFragment');
+</script>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All-content.html b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All-content.html
new file mode 100644
index 0000000000..8dc1354551
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All-content.html
@@ -0,0 +1,377 @@
+<!DOCTYPE html>
+<html id="html" lang="en">
+<head id="head">
+ <meta id="meta" charset="UTF-8">
+ <title id="title">Selectors-API Test Suite: HTML with Selectors Level 2 using TestHarness: Test Document</title>
+
+ <!-- Links for :link and :visited pseudo-class test -->
+ <link id="pseudo-link-link1" href="">
+ <link id="pseudo-link-link2" href="http://example.org/">
+ <link id="pseudo-link-link3">
+ <style>
+ @namespace ns "http://www.w3.org/1999/xhtml";
+ /* Declare the namespace prefix used in tests. This declaration should not be used by the API. */
+ </style>
+</head>
+<body id="body">
+<div id="root">
+ <div id="target"></div>
+
+ <div id="universal">
+ <p id="universal-p1">Universal selector tests inside element with <code id="universal-code1">id="universal"</code>.</p>
+ <hr id="universal-hr1">
+ <pre id="universal-pre1">Some preformatted text with some <span id="universal-span1">embedded code</span></pre>
+ <p id="universal-p2">This is a normal link: <a id="universal-a1" href="http://www.w3.org/">W3C</a></p>
+ <address id="universal-address1">Some more nested elements <code id="universal-code2"><a href="#" id="universal-a2">code hyperlink</a></code></address>
+ </div>
+
+ <div id="attr-presence">
+ <div class="attr-presence-div1" id="attr-presence-div1" align="center"></div>
+ <div class="attr-presence-div2" id="attr-presence-div2" align=""></div>
+ <div class="attr-presence-div3" id="attr-presence-div3" valign="center"></div>
+ <div class="attr-presence-div4" id="attr-presence-div4" alignv="center"></div>
+ <p id="attr-presence-p1"><a id="attr-presence-a1" tItLe=""></a><span id="attr-presence-span1" TITLE="attr-presence-span1"></span><i id="attr-presence-i1"></i></p>
+ <pre id="attr-presence-pre1" data-attr-presence="pre1"></pre>
+ <blockquote id="attr-presence-blockquote1" data-attr-presence="blockquote1"></blockquote>
+ <ul id="attr-presence-ul1" data-中文=""></ul>
+
+ <select id="attr-presence-select1">
+ <option id="attr-presence-select1-option1">A</option>
+ <option id="attr-presence-select1-option2">B</option>
+ <option id="attr-presence-select1-option3">C</option>
+ <option id="attr-presence-select1-option4">D</option>
+ </select>
+ <select id="attr-presence-select2">
+ <option id="attr-presence-select2-option1">A</option>
+ <option id="attr-presence-select2-option2">B</option>
+ <option id="attr-presence-select2-option3">C</option>
+ <option id="attr-presence-select2-option4" selected="selected">D</option>
+ </select>
+ <select id="attr-presence-select3" multiple="multiple">
+ <option id="attr-presence-select3-option1">A</option>
+ <option id="attr-presence-select3-option2" selected="">B</option>
+ <option id="attr-presence-select3-option3" selected="selected">C</option>
+ <option id="attr-presence-select3-option4">D</option>
+ </select>
+ </div>
+
+ <div id="attr-value">
+ <div id="attr-value-div1" align="center"></div>
+ <div id="attr-value-div2" align=""></div>
+ <div id="attr-value-div3" data-attr-value="&#xE9;"></div>
+ <div id="attr-value-div4" data-attr-value_foo="&#xE9;"></div>
+
+ <form id="attr-value-form1">
+ <input id="attr-value-input1" type="text">
+ <input id="attr-value-input2" type="password">
+ <input id="attr-value-input3" type="hidden">
+ <input id="attr-value-input4" type="radio">
+ <input id="attr-value-input5" type="checkbox">
+ <input id="attr-value-input6" type="radio">
+ <input id="attr-value-input7" type="text">
+ <input id="attr-value-input8" type="hidden">
+ <input id="attr-value-input9" type="radio">
+ </form>
+
+ <div id="attr-value-div5" data-attr-value="中文"></div>
+ </div>
+
+ <div id="attr-whitespace">
+ <div id="attr-whitespace-div1" class="foo div1 bar"></div>
+ <div id="attr-whitespace-div2" class=""></div>
+ <div id="attr-whitespace-div3" class="foo div3 bar"></div>
+
+ <div id="attr-whitespace-div4" data-attr-whitespace="foo &#xE9; bar"></div>
+ <div id="attr-whitespace-div5" data-attr-whitespace_foo="&#xE9; foo"></div>
+
+ <a id="attr-whitespace-a1" rel="next bookmark"></a>
+ <a id="attr-whitespace-a2" rel="tag nofollow"></a>
+ <a id="attr-whitespace-a3" rel="tag bookmark"></a>
+ <a id="attr-whitespace-a4" rel="book mark"></a> <!-- Intentional space in "book mark" -->
+ <a id="attr-whitespace-a5" rel="nofollow"></a>
+ <a id="attr-whitespace-a6" rev="bookmark nofollow"></a>
+ <a id="attr-whitespace-a7" rel="prev next tag alternate nofollow author help icon noreferrer prefetch search stylesheet tag"></a>
+
+ <p id="attr-whitespace-p1" title="Chinese 中文 characters"></p>
+ </div>
+
+ <div id="attr-hyphen">
+ <div id="attr-hyphen-div1"></div>
+ <div id="attr-hyphen-div2" lang="fr"></div>
+ <div id="attr-hyphen-div3" lang="en-AU"></div>
+ <div id="attr-hyphen-div4" lang="es"></div>
+ </div>
+
+ <div id="attr-begins">
+ <a id="attr-begins-a1" href="http://www.example.org"></a>
+ <a id="attr-begins-a2" href="http://example.org/"></a>
+ <a id="attr-begins-a3" href="http://www.example.com/"></a>
+
+ <div id="attr-begins-div1" lang="fr"></div>
+ <div id="attr-begins-div2" lang="en-AU"></div>
+ <div id="attr-begins-div3" lang="es"></div>
+ <div id="attr-begins-div4" lang="en-US"></div>
+ <div id="attr-begins-div5" lang="en"></div>
+
+ <p id="attr-begins-p1" class=" apple"></p> <!-- Intentional space in class value " apple". -->
+ </div>
+
+ <div id="attr-ends">
+ <a id="attr-ends-a1" href="http://www.example.org"></a>
+ <a id="attr-ends-a2" href="http://example.org/"></a>
+ <a id="attr-ends-a3" href="http://www.example.org"></a>
+
+ <div id="attr-ends-div1" lang="fr"></div>
+ <div id="attr-ends-div2" lang="de-CH"></div>
+ <div id="attr-ends-div3" lang="es"></div>
+ <div id="attr-ends-div4" lang="fr-CH"></div>
+
+ <p id="attr-ends-p1" class="apple "></p> <!-- Intentional space in class value "apple ". -->
+ </div>
+
+ <div id="attr-contains">
+ <a id="attr-contains-a1" href="http://www.example.org"></a>
+ <a id="attr-contains-a2" href="http://example.org/"></a>
+ <a id="attr-contains-a3" href="http://www.example.com/"></a>
+
+ <div id="attr-contains-div1" lang="fr"></div>
+ <div id="attr-contains-div2" lang="en-AU"></div>
+ <div id="attr-contains-div3" lang="de-CH"></div>
+ <div id="attr-contains-div4" lang="es"></div>
+ <div id="attr-contains-div5" lang="fr-CH"></div>
+ <div id="attr-contains-div6" lang="en-US"></div>
+
+ <p id="attr-contains-p1" class=" apple banana orange "></p>
+ </div>
+
+ <div id="pseudo-nth">
+ <table id="pseudo-nth-table1">
+ <tr id="pseudo-nth-tr1"><td id="pseudo-nth-td1"></td><td id="pseudo-nth-td2"></td><td id="pseudo-nth-td3"></td><td id="pseudo-nth-td4"></td><td id="pseudo-nth--td5"></td><td id="pseudo-nth-td6"></td></tr>
+ <tr id="pseudo-nth-tr2"><td id="pseudo-nth-td7"></td><td id="pseudo-nth-td8"></td><td id="pseudo-nth-td9"></td><td id="pseudo-nth-td10"></td><td id="pseudo-nth-td11"></td><td id="pseudo-nth-td12"></td></tr>
+ <tr id="pseudo-nth-tr3"><td id="pseudo-nth-td13"></td><td id="pseudo-nth-td14"></td><td id="pseudo-nth-td15"></td><td id="pseudo-nth-td16"></td><td id="pseudo-nth-td17"></td><td id="pseudo-nth-td18"></td></tr>
+ </table>
+
+ <ol id="pseudo-nth-ol1">
+ <li id="pseudo-nth-li1"></li>
+ <li id="pseudo-nth-li2"></li>
+ <li id="pseudo-nth-li3"></li>
+ <li id="pseudo-nth-li4"></li>
+ <li id="pseudo-nth-li5"></li>
+ <li id="pseudo-nth-li6"></li>
+ <li id="pseudo-nth-li7"></li>
+ <li id="pseudo-nth-li8"></li>
+ <li id="pseudo-nth-li9"></li>
+ <li id="pseudo-nth-li10"></li>
+ <li id="pseudo-nth-li11"></li>
+ <li id="pseudo-nth-li12"></li>
+ </ol>
+
+ <p id="pseudo-nth-p1">
+ <span id="pseudo-nth-span1">span1</span>
+ <em id="pseudo-nth-em1">em1</em>
+ <!-- comment node-->
+ <em id="pseudo-nth-em2">em2</em>
+ <span id="pseudo-nth-span2">span2</span>
+ <strong id="pseudo-nth-strong1">strong1</strong>
+ <em id="pseudo-nth-em3">em3</em>
+ <span id="pseudo-nth-span3">span3</span>
+ <span id="pseudo-nth-span4">span4</span>
+ <strong id="pseudo-nth-strong2">strong2</strong>
+ <em id="pseudo-nth-em4">em4</em>
+ </p>
+ </div>
+
+ <div id="pseudo-first-child">
+ <div id="pseudo-first-child-div1"></div>
+ <div id="pseudo-first-child-div2"></div>
+ <div id="pseudo-first-child-div3"></div>
+
+ <p id="pseudo-first-child-p1"><span id="pseudo-first-child-span1"></span><span id="pseudo-first-child-span2"></span></p>
+ <p id="pseudo-first-child-p2"><span id="pseudo-first-child-span3"></span><span id="pseudo-first-child-span4"></span></p>
+ <p id="pseudo-first-child-p3"><span id="pseudo-first-child-span5"></span><span id="pseudo-first-child-span6"></span></p>
+ </div>
+
+ <div id="pseudo-last-child">
+ <p id="pseudo-last-child-p1"><span id="pseudo-last-child-span1"></span><span id="pseudo-last-child-span2"></span></p>
+ <p id="pseudo-last-child-p2"><span id="pseudo-last-child-span3"></span><span id="pseudo-last-child-span4"></span></p>
+ <p id="pseudo-last-child-p3"><span id="pseudo-last-child-span5"></span><span id="pseudo-last-child-span6"></span></p>
+
+ <div id="pseudo-last-child-div1"></div>
+ <div id="pseudo-last-child-div2"></div>
+ <div id="pseudo-last-child-div3"></div>
+ </div>
+
+ <div id="pseudo-only">
+ <p id="pseudo-only-p1">
+ <span id="pseudo-only-span1"></span>
+ </p>
+ <p id="pseudo-only-p2">
+ <span id="pseudo-only-span2"></span>
+ <span id="pseudo-only-span3"></span>
+ </p>
+ <p id="pseudo-only-p3">
+ <span id="pseudo-only-span4"></span>
+ <em id="pseudo-only-em1"></em>
+ <span id="pseudo-only-span5"></span>
+ </p>
+ </div>>
+
+ <div id="pseudo-empty">
+ <p id="pseudo-empty-p1"></p>
+ <p id="pseudo-empty-p2"><!-- comment node --></p>
+ <p id="pseudo-empty-p3"> </p>
+ <p id="pseudo-empty-p4">Text node</p>
+ <p id="pseudo-empty-p5"><span id="pseudo-empty-span1"></span></p>
+ </div>
+
+ <div id="pseudo-link">
+ <a id="pseudo-link-a1" href="">with href</a>
+ <a id="pseudo-link-a2" href="http://example.org/">with href</a>
+ <a id="pseudo-link-a3">without href</a>
+ <map name="pseudo-link-map1" id="pseudo-link-map1">
+ <area id="pseudo-link-area1" href="">
+ <area id="pseudo-link-area2">
+ </map>
+ </div>
+
+ <div id="pseudo-lang">
+ <div id="pseudo-lang-div1"></div>
+ <div id="pseudo-lang-div2" lang="fr"></div>
+ <div id="pseudo-lang-div3" lang="en-AU"></div>
+ <div id="pseudo-lang-div4" lang="es"></div>
+ </div>
+
+ <div id="pseudo-ui">
+ <input id="pseudo-ui-input1" type="text">
+ <input id="pseudo-ui-input2" type="password">
+ <input id="pseudo-ui-input3" type="radio">
+ <input id="pseudo-ui-input4" type="radio" checked="checked">
+ <input id="pseudo-ui-input5" type="checkbox">
+ <input id="pseudo-ui-input6" type="checkbox" checked="checked">
+ <input id="pseudo-ui-input7" type="submit">
+ <input id="pseudo-ui-input8" type="button">
+ <input id="pseudo-ui-input9" type="hidden">
+ <textarea id="pseudo-ui-textarea1"></textarea>
+ <button id="pseudo-ui-button1">Enabled</button>
+
+ <input id="pseudo-ui-input10" disabled="disabled" type="text">
+ <input id="pseudo-ui-input11" disabled="disabled" type="password">
+ <input id="pseudo-ui-input12" disabled="disabled" type="radio">
+ <input id="pseudo-ui-input13" disabled="disabled" type="radio" checked="checked">
+ <input id="pseudo-ui-input14" disabled="disabled" type="checkbox">
+ <input id="pseudo-ui-input15" disabled="disabled" type="checkbox" checked="checked">
+ <input id="pseudo-ui-input16" disabled="disabled" type="submit">
+ <input id="pseudo-ui-input17" disabled="disabled" type="button">
+ <input id="pseudo-ui-input18" disabled="disabled" type="hidden">
+ <textarea id="pseudo-ui-textarea2" disabled="disabled"></textarea>
+ <button id="pseudo-ui-button2" disabled="disabled">Disabled</button>
+ </div>
+
+ <div id="not">
+ <div id="not-div1"></div>
+ <div id="not-div2"></div>
+ <div id="not-div3"></div>
+
+ <p id="not-p1"><span id="not-span1"></span><em id="not-em1"></em></p>
+ <p id="not-p2"><span id="not-span2"></span><em id="not-em2"></em></p>
+ <p id="not-p3"><span id="not-span3"></span><em id="not-em3"></em></p>
+ </div>
+
+ <div id="pseudo-element">All pseudo-element tests</div>
+
+ <div id="class">
+ <p id="class-p1" class="foo class-p bar"></p>
+ <p id="class-p2" class="class-p foo bar"></p>
+ <p id="class-p3" class="foo bar class-p"></p>
+
+ <!-- All permutations of the classes should match -->
+ <div id="class-div1" class="apple orange banana"></div>
+ <div id="class-div2" class="apple banana orange"></div>
+ <p id="class-p4" class="orange apple banana"></p>
+ <div id="class-div3" class="orange banana apple"></div>
+ <p id="class-p6" class="banana apple orange"></p>
+ <div id="class-div4" class="banana orange apple"></div>
+ <div id="class-div5" class="apple orange"></div>
+ <div id="class-div6" class="apple banana"></div>
+ <div id="class-div7" class="orange banana"></div>
+
+ <span id="class-span1" class="台北Táiběi 台北"></span>
+ <span id="class-span2" class="台北"></span>
+
+ <span id="class-span3" class="foo:bar"></span>
+ <span id="class-span4" class="test.foo[5]bar"></span>
+ </div>
+
+ <div id="id">
+ <div id="id-div1"></div>
+ <div id="id-div2"></div>
+
+ <ul id="id-ul1">
+ <li id="id-li-duplicate"></li>
+ <li id="id-li-duplicate"></li>
+ <li id="id-li-duplicate"></li>
+ <li id="id-li-duplicate"></li>
+ </ul>
+
+ <span id="台北Táiběi"></span>
+ <span id="台北"></span>
+
+ <span id="#foo:bar"></span>
+ <span id="test.foo[5]bar"></span>
+ </div>
+
+ <div id="descendant">
+ <div id="descendant-div1" class="descendant-div1">
+ <div id="descendant-div2" class="descendant-div2">
+ <div id="descendant-div3" class="descendant-div3">
+ </div>
+ </div>
+ </div>
+ <div id="descendant-div4" class="descendant-div4"></div>
+ </div>
+
+ <div id="child">
+ <div id="child-div1" class="child-div1">
+ <div id="child-div2" class="child-div2">
+ <div id="child-div3" class="child-div3">
+ </div>
+ </div>
+ </div>
+ <div id="child-div4" class="child-div4"></div>
+ </div>
+
+ <div id="adjacent">
+ <div id="adjacent-div1" class="adjacent-div1"></div>
+ <div id="adjacent-div2" class="adjacent-div2">
+ <div id="adjacent-div3" class="adjacent-div3"></div>
+ </div>
+ <div id="adjacent-div4" class="adjacent-div4">
+ <p id="adjacent-p1" class="adjacent-p1"></p>
+ <div id="adjacent-div5" class="adjacent-div5"></div>
+ </div>
+ <div id="adjacent-div6" class="adjacent-div6"></div>
+ <p id="adjacent-p2" class="adjacent-p2"></p>
+ <p id="adjacent-p3" class="adjacent-p3"></p>
+ </div>
+
+ <div id="sibling">
+ <div id="sibling-div1" class="sibling-div"></div>
+ <div id="sibling-div2" class="sibling-div">
+ <div id="sibling-div3" class="sibling-div"></div>
+ </div>
+ <div id="sibling-div4" class="sibling-div">
+ <p id="sibling-p1" class="sibling-p"></p>
+ <div id="sibling-div5" class="sibling-div"></div>
+ </div>
+ <div id="sibling-div6" class="sibling-div"></div>
+ <p id="sibling-p2" class="sibling-p"></p>
+ <p id="sibling-p3" class="sibling-p"></p>
+ </div>
+
+ <div id="group">
+ <em id="group-em1"></em>
+ <strong id="group-strong1"></strong>
+ </div>
+</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All-content.xht b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All-content.xht
new file mode 100644
index 0000000000..0e9b925f58
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All-content.xht
@@ -0,0 +1,372 @@
+<!DOCTYPE html>
+<html id="html" lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<head id="head">
+ <title id="title">Selectors-API Test Suite: HTML with Selectors Level 2 using TestHarness: Test Document</title>
+
+ <!-- Links for :link and :visited pseudo-class test -->
+ <link id="pseudo-link-link1" href=""/>
+ <link id="pseudo-link-link2" href="http://example.org/"/>
+ <link id="pseudo-link-link3"/>
+</head>
+<body id="body">
+<div id="root">
+ <div id="target"></div>
+
+ <div id="universal">
+ <p id="universal-p1">Universal selector tests inside element with <code id="universal-code1">id="universal"</code>.</p>
+ <hr id="universal-hr1"/>
+ <pre id="universal-pre1">Some preformatted text with some <span id="universal-span1">embedded code</span></pre>
+ <p id="universal-p2">This is a normal link: <a id="universal-a1" href="http://www.w3.org/">W3C</a></p>
+ <address id="universal-address1">Some more nested elements <code id="universal-code2"><a href="#" id="universal-a2">code hyperlink</a></code></address>
+ </div>
+
+ <div id="attr-presence">
+ <div class="attr-presence-div1" id="attr-presence-div1" align="center"></div>
+ <div class="attr-presence-div2" id="attr-presence-div2" align=""></div>
+ <div class="attr-presence-div3" id="attr-presence-div3" valign="center"></div>
+ <div class="attr-presence-div4" id="attr-presence-div4" alignv="center"></div>
+ <p id="attr-presence-p1"><a id="attr-presence-a1" tItLe=""></a><span id="attr-presence-span1" TITLE="attr-presence-span1"></span><i id="attr-presence-i1"></i></p>
+ <pre id="attr-presence-pre1" data-attr-presence="pre1"></pre>
+ <blockquote id="attr-presence-blockquote1" data-attr-presence="blockquote1"></blockquote>
+ <ul id="attr-presence-ul1" data-中文=""></ul>
+
+ <select id="attr-presence-select1">
+ <option id="attr-presence-select1-option1">A</option>
+ <option id="attr-presence-select1-option2">B</option>
+ <option id="attr-presence-select1-option3">C</option>
+ <option id="attr-presence-select1-option4">D</option>
+ </select>
+ <select id="attr-presence-select2">
+ <option id="attr-presence-select2-option1">A</option>
+ <option id="attr-presence-select2-option2">B</option>
+ <option id="attr-presence-select2-option3">C</option>
+ <option id="attr-presence-select2-option4" selected="selected">D</option>
+ </select>
+ <select id="attr-presence-select3" multiple="multiple">
+ <option id="attr-presence-select3-option1">A</option>
+ <option id="attr-presence-select3-option2" selected="">B</option>
+ <option id="attr-presence-select3-option3" selected="selected">C</option>
+ <option id="attr-presence-select3-option4">D</option>
+ </select>
+ </div>
+
+ <div id="attr-value">
+ <div id="attr-value-div1" align="center"></div>
+ <div id="attr-value-div2" align=""></div>
+ <div id="attr-value-div3" data-attr-value="&#xE9;"></div>
+ <div id="attr-value-div4" data-attr-value_foo="&#xE9;"></div>
+
+ <form id="attr-value-form1">
+ <input id="attr-value-input1" type="text"/>
+ <input id="attr-value-input2" type="password"/>
+ <input id="attr-value-input3" type="hidden"/>
+ <input id="attr-value-input4" type="radio"/>
+ <input id="attr-value-input5" type="checkbox"/>
+ <input id="attr-value-input6" type="radio"/>
+ <input id="attr-value-input7" type="text"/>
+ <input id="attr-value-input8" type="hidden"/>
+ <input id="attr-value-input9" type="radio"/>
+ </form>
+
+ <div id="attr-value-div5" data-attr-value="中文"></div>
+ </div>
+
+ <div id="attr-whitespace">
+ <div id="attr-whitespace-div1" class="foo div1 bar"></div>
+ <div id="attr-whitespace-div2" class=""></div>
+ <div id="attr-whitespace-div3" class="foo div3 bar"></div>
+
+ <div id="attr-whitespace-div4" data-attr-whitespace="foo &#xE9; bar"></div>
+ <div id="attr-whitespace-div5" data-attr-whitespace_foo="&#xE9; foo"></div>
+
+ <a id="attr-whitespace-a1" rel="next bookmark"></a>
+ <a id="attr-whitespace-a2" rel="tag nofollow"></a>
+ <a id="attr-whitespace-a3" rel="tag bookmark"></a>
+ <a id="attr-whitespace-a4" rel="book mark"></a> <!-- Intentional space in "book mark" -->
+ <a id="attr-whitespace-a5" rel="nofollow"></a>
+ <a id="attr-whitespace-a6" rev="bookmark nofollow"></a>
+ <a id="attr-whitespace-a7" rel="prev next tag alternate nofollow author help icon noreferrer prefetch search stylesheet tag"></a>
+
+ <p id="attr-whitespace-p1" title="Chinese 中文 characters"></p>
+ </div>
+
+ <div id="attr-hyphen">
+ <div id="attr-hyphen-div1"></div>
+ <div id="attr-hyphen-div2" lang="fr"></div>
+ <div id="attr-hyphen-div3" lang="en-AU"></div>
+ <div id="attr-hyphen-div4" lang="es"></div>
+ </div>
+
+ <div id="attr-begins">
+ <a id="attr-begins-a1" href="http://www.example.org"></a>
+ <a id="attr-begins-a2" href="http://example.org/"></a>
+ <a id="attr-begins-a3" href="http://www.example.com/"></a>
+
+ <div id="attr-begins-div1" lang="fr"></div>
+ <div id="attr-begins-div2" lang="en-AU"></div>
+ <div id="attr-begins-div3" lang="es"></div>
+ <div id="attr-begins-div4" lang="en-US"></div>
+ <div id="attr-begins-div5" lang="en"></div>
+
+ <p id="attr-begins-p1" class=" apple"></p> <!-- Intentional space in class value " apple". -->
+ </div>
+
+ <div id="attr-ends">
+ <a id="attr-ends-a1" href="http://www.example.org"></a>
+ <a id="attr-ends-a2" href="http://example.org/"></a>
+ <a id="attr-ends-a3" href="http://www.example.org"></a>
+
+ <div id="attr-ends-div1" lang="fr"></div>
+ <div id="attr-ends-div2" lang="de-CH"></div>
+ <div id="attr-ends-div3" lang="es"></div>
+ <div id="attr-ends-div4" lang="fr-CH"></div>
+
+ <p id="attr-ends-p1" class="apple "></p> <!-- Intentional space in class value "apple ". -->
+ </div>
+
+ <div id="attr-contains">
+ <a id="attr-contains-a1" href="http://www.example.org"></a>
+ <a id="attr-contains-a2" href="http://example.org/"></a>
+ <a id="attr-contains-a3" href="http://www.example.com/"></a>
+
+ <div id="attr-contains-div1" lang="fr"></div>
+ <div id="attr-contains-div2" lang="en-AU"></div>
+ <div id="attr-contains-div3" lang="de-CH"></div>
+ <div id="attr-contains-div4" lang="es"></div>
+ <div id="attr-contains-div5" lang="fr-CH"></div>
+ <div id="attr-contains-div6" lang="en-US"></div>
+
+ <p id="attr-contains-p1" class=" apple banana orange "></p>
+ </div>
+
+ <div id="pseudo-nth">
+ <table id="pseudo-nth-table1">
+ <tr id="pseudo-nth-tr1"><td id="pseudo-nth-td1"></td><td id="pseudo-nth-td2"></td><td id="pseudo-nth-td3"></td><td id="pseudo-nth-td4"></td><td id="pseudo-nth--td5"></td><td id="pseudo-nth-td6"></td></tr>
+ <tr id="pseudo-nth-tr2"><td id="pseudo-nth-td7"></td><td id="pseudo-nth-td8"></td><td id="pseudo-nth-td9"></td><td id="pseudo-nth-td10"></td><td id="pseudo-nth-td11"></td><td id="pseudo-nth-td12"></td></tr>
+ <tr id="pseudo-nth-tr3"><td id="pseudo-nth-td13"></td><td id="pseudo-nth-td14"></td><td id="pseudo-nth-td15"></td><td id="pseudo-nth-td16"></td><td id="pseudo-nth-td17"></td><td id="pseudo-nth-td18"></td></tr>
+ </table>
+
+ <ol id="pseudo-nth-ol1">
+ <li id="pseudo-nth-li1"></li>
+ <li id="pseudo-nth-li2"></li>
+ <li id="pseudo-nth-li3"></li>
+ <li id="pseudo-nth-li4"></li>
+ <li id="pseudo-nth-li5"></li>
+ <li id="pseudo-nth-li6"></li>
+ <li id="pseudo-nth-li7"></li>
+ <li id="pseudo-nth-li8"></li>
+ <li id="pseudo-nth-li9"></li>
+ <li id="pseudo-nth-li10"></li>
+ <li id="pseudo-nth-li11"></li>
+ <li id="pseudo-nth-li12"></li>
+ </ol>
+
+ <p id="pseudo-nth-p1">
+ <span id="pseudo-nth-span1">span1</span>
+ <em id="pseudo-nth-em1">em1</em>
+ <!-- comment node-->
+ <em id="pseudo-nth-em2">em2</em>
+ <span id="pseudo-nth-span2">span2</span>
+ <strong id="pseudo-nth-strong1">strong1</strong>
+ <em id="pseudo-nth-em3">em3</em>
+ <span id="pseudo-nth-span3">span3</span>
+ <span id="pseudo-nth-span4">span4</span>
+ <strong id="pseudo-nth-strong2">strong2</strong>
+ <em id="pseudo-nth-em4">em4</em>
+ </p>
+ </div>
+
+ <div id="pseudo-first-child">
+ <div id="pseudo-first-child-div1"></div>
+ <div id="pseudo-first-child-div2"></div>
+ <div id="pseudo-first-child-div3"></div>
+
+ <p id="pseudo-first-child-p1"><span id="pseudo-first-child-span1"></span><span id="pseudo-first-child-span2"></span></p>
+ <p id="pseudo-first-child-p2"><span id="pseudo-first-child-span3"></span><span id="pseudo-first-child-span4"></span></p>
+ <p id="pseudo-first-child-p3"><span id="pseudo-first-child-span5"></span><span id="pseudo-first-child-span6"></span></p>
+ </div>
+
+ <div id="pseudo-last-child">
+ <p id="pseudo-last-child-p1"><span id="pseudo-last-child-span1"></span><span id="pseudo-last-child-span2"></span></p>
+ <p id="pseudo-last-child-p2"><span id="pseudo-last-child-span3"></span><span id="pseudo-last-child-span4"></span></p>
+ <p id="pseudo-last-child-p3"><span id="pseudo-last-child-span5"></span><span id="pseudo-last-child-span6"></span></p>
+
+ <div id="pseudo-last-child-div1"></div>
+ <div id="pseudo-last-child-div2"></div>
+ <div id="pseudo-last-child-div3"></div>
+ </div>
+
+ <div id="pseudo-only">
+ <p id="pseudo-only-p1">
+ <span id="pseudo-only-span1"></span>
+ </p>
+ <p id="pseudo-only-p2">
+ <span id="pseudo-only-span2"></span>
+ <span id="pseudo-only-span3"></span>
+ </p>
+ <p id="pseudo-only-p3">
+ <span id="pseudo-only-span4"></span>
+ <em id="pseudo-only-em1"></em>
+ <span id="pseudo-only-span5"></span>
+ </p>
+ </div>>
+
+ <div id="pseudo-empty">
+ <p id="pseudo-empty-p1"></p>
+ <p id="pseudo-empty-p2"><!-- comment node --></p>
+ <p id="pseudo-empty-p3"> </p>
+ <p id="pseudo-empty-p4">Text node</p>
+ <p id="pseudo-empty-p5"><span id="pseudo-empty-span1"></span></p>
+ </div>
+
+ <div id="pseudo-link">
+ <a id="pseudo-link-a1" href="">with href</a>
+ <a id="pseudo-link-a2" href="http://example.org/">with href</a>
+ <a id="pseudo-link-a3">without href</a>
+ <map name="pseudo-link-map1" id="pseudo-link-map1">
+ <area id="pseudo-link-area1" href=""/>
+ <area id="pseudo-link-area2"/>
+ </map>
+ </div>
+
+ <div id="pseudo-lang">
+ <div id="pseudo-lang-div1"></div>
+ <div id="pseudo-lang-div2" lang="fr"></div>
+ <div id="pseudo-lang-div3" lang="en-AU"></div>
+ <div id="pseudo-lang-div4" lang="es"></div>
+ </div>
+
+ <div id="pseudo-ui">
+ <input id="pseudo-ui-input1" type="text"/>
+ <input id="pseudo-ui-input2" type="password"/>
+ <input id="pseudo-ui-input3" type="radio"/>
+ <input id="pseudo-ui-input4" type="radio" checked="checked"/>
+ <input id="pseudo-ui-input5" type="checkbox"/>
+ <input id="pseudo-ui-input6" type="checkbox" checked="checked"/>
+ <input id="pseudo-ui-input7" type="submit"/>
+ <input id="pseudo-ui-input8" type="button"/>
+ <input id="pseudo-ui-input9" type="hidden"/>
+ <textarea id="pseudo-ui-textarea1"></textarea>
+ <button id="pseudo-ui-button1">Enabled</button>
+
+ <input id="pseudo-ui-input10" disabled="disabled" type="text"/>
+ <input id="pseudo-ui-input11" disabled="disabled" type="password"/>
+ <input id="pseudo-ui-input12" disabled="disabled" type="radio"/>
+ <input id="pseudo-ui-input13" disabled="disabled" type="radio" checked="checked"/>
+ <input id="pseudo-ui-input14" disabled="disabled" type="checkbox"/>
+ <input id="pseudo-ui-input15" disabled="disabled" type="checkbox" checked="checked"/>
+ <input id="pseudo-ui-input16" disabled="disabled" type="submit"/>
+ <input id="pseudo-ui-input17" disabled="disabled" type="button"/>
+ <input id="pseudo-ui-input18" disabled="disabled" type="hidden"/>
+ <textarea id="pseudo-ui-textarea2" disabled="disabled"></textarea>
+ <button id="pseudo-ui-button2" disabled="disabled">Disabled</button>
+ </div>
+
+ <div id="not">
+ <div id="not-div1"></div>
+ <div id="not-div2"></div>
+ <div id="not-div3"></div>
+
+ <p id="not-p1"><span id="not-span1"></span><em id="not-em1"></em></p>
+ <p id="not-p2"><span id="not-span2"></span><em id="not-em2"></em></p>
+ <p id="not-p3"><span id="not-span3"></span><em id="not-em3"></em></p>
+ </div>
+
+ <div id="pseudo-element">All pseudo-element tests</div>
+
+ <div id="class">
+ <p id="class-p1" class="foo class-p bar"></p>
+ <p id="class-p2" class="class-p foo bar"></p>
+ <p id="class-p3" class="foo bar class-p"></p>
+
+ <!-- All permutations of the classes should match -->
+ <div id="class-div1" class="apple orange banana"></div>
+ <div id="class-div2" class="apple banana orange"></div>
+ <p id="class-p4" class="orange apple banana"></p>
+ <div id="class-div3" class="orange banana apple"></div>
+ <p id="class-p6" class="banana apple orange"></p>
+ <div id="class-div4" class="banana orange apple"></div>
+ <div id="class-div5" class="apple orange"></div>
+ <div id="class-div6" class="apple banana"></div>
+ <div id="class-div7" class="orange banana"></div>
+
+ <span id="class-span1" class="台北Táiběi 台北"></span>
+ <span id="class-span2" class="台北"></span>
+
+ <span id="class-span3" class="foo:bar"></span>
+ <span id="class-span4" class="test.foo[5]bar"></span>
+ </div>
+
+ <div id="id">
+ <div id="id-div1"></div>
+ <div id="id-div2"></div>
+
+ <ul id="id-ul1">
+ <li id="id-li-duplicate"></li>
+ <li id="id-li-duplicate"></li>
+ <li id="id-li-duplicate"></li>
+ <li id="id-li-duplicate"></li>
+ </ul>
+
+ <span id="台北Táiběi"></span>
+ <span id="台北"></span>
+
+ <span id="#foo:bar"></span>
+ <span id="test.foo[5]bar"></span>
+ </div>
+
+ <div id="descendant">
+ <div id="descendant-div1" class="descendant-div1">
+ <div id="descendant-div2" class="descendant-div2">
+ <div id="descendant-div3" class="descendant-div3">
+ </div>
+ </div>
+ </div>
+ <div id="descendant-div4" class="descendant-div4"></div>
+ </div>
+
+ <div id="child">
+ <div id="child-div1" class="child-div1">
+ <div id="child-div2" class="child-div2">
+ <div id="child-div3" class="child-div3">
+ </div>
+ </div>
+ </div>
+ <div id="child-div4" class="child-div4"></div>
+ </div>
+
+ <div id="adjacent">
+ <div id="adjacent-div1" class="adjacent-div1"></div>
+ <div id="adjacent-div2" class="adjacent-div2">
+ <div id="adjacent-div3" class="adjacent-div3"></div>
+ </div>
+ <div id="adjacent-div4" class="adjacent-div4">
+ <p id="adjacent-p1" class="adjacent-p1"></p>
+ <div id="adjacent-div5" class="adjacent-div5"></div>
+ </div>
+ <div id="adjacent-div6" class="adjacent-div6"></div>
+ <p id="adjacent-p2" class="adjacent-p2"></p>
+ <p id="adjacent-p3" class="adjacent-p3"></p>
+ </div>
+
+ <div id="sibling">
+ <div id="sibling-div1" class="sibling-div"></div>
+ <div id="sibling-div2" class="sibling-div">
+ <div id="sibling-div3" class="sibling-div"></div>
+ </div>
+ <div id="sibling-div4" class="sibling-div">
+ <p id="sibling-p1" class="sibling-p"></p>
+ <div id="sibling-div5" class="sibling-div"></div>
+ </div>
+ <div id="sibling-div6" class="sibling-div"></div>
+ <p id="sibling-p2" class="sibling-p"></p>
+ <p id="sibling-p3" class="sibling-p"></p>
+ </div>
+
+ <div id="group">
+ <em id="group-em1"></em>
+ <strong id="group-strong1"></strong>
+ </div>
+</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All-xht.xht b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All-xht.xht
new file mode 100644
index 0000000000..f2d94da1da
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All-xht.xht
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<html id="html" lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<head id="head">
+<meta name="timeout" content="long" />
+<title>Selectors-API Test Suite: XHTML</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="selectors.js"></script>
+<script src="ParentNode-querySelector-All.js"></script>
+<style>iframe { visibility: hidden; position: absolute; }</style>
+</head>
+<body>
+<div id="log">This test requires JavaScript.</div>
+
+<script><![CDATA[
+async_test(function() {
+ var frame = document.createElement("iframe");
+ var self = this;
+ frame.onload = function() {
+ // :target doesn't work before a page rendering on some browsers. We run
+ // tests after an animation frame because it may be later than the first
+ // page rendering.
+ requestAnimationFrame(self.step_func_done(init.bind(self, frame)));
+ };
+ frame.src = "ParentNode-querySelector-All-content.xht#target";
+ document.body.appendChild(frame);
+})
+
+function init(target) {
+ /*
+ * This test suite tests Selectors API methods in 4 different contexts:
+ * 1. Document node
+ * 2. In-document Element node
+ * 3. Detached Element node (an element with no parent, not in the document)
+ * 4. Document Fragment node
+ *
+ * For each context, the following tests are run:
+ *
+ * The interface check tests ensure that each type of node exposes the Selectors API methods
+ *
+ * The special selector tests verify the result of passing special values for the selector parameter,
+ * to ensure that the correct WebIDL processing is performed, such as stringification of null and
+ * undefined and missing parameter. The universal selector is also tested here, rather than with the
+ * rest of ordinary selectors for practical reasons.
+ *
+ * The static list verification tests ensure that the node lists returned by the method remain unchanged
+ * due to subsequent document modication, and that a new list is generated each time the method is
+ * invoked based on the current state of the document.
+ *
+ * The invalid selector tests ensure that SyntaxError is thrown for invalid forms of selectors
+ *
+ * The valid selector tests check the result from querying many different types of selectors, with a
+ * list of expected elements. This checks that querySelector() always returns the first result from
+ * querySelectorAll(), and that all matching elements are correctly returned in tree-order. The tests
+ * can be limited by specifying the test types to run, using the testType variable. The constants for this
+ * can be found in selectors.js.
+ *
+ * All the selectors tested for both the valid and invalid selector tests are found in selectors.js.
+ * See comments in that file for documentation of the format used.
+ *
+ * The ParentNode-querySelector-All.js file contains all the common test functions for running each of the aforementioned tests
+ */
+
+ var testType = TEST_QSA;
+ var docType = "xhtml"; // Only run tests suitable for XHTML
+
+ // Prepare the nodes for testing
+ var doc = target.contentDocument; // Document Node tests
+
+ var element = doc.getElementById("root"); // In-document Element Node tests
+
+ //Setup the namespace tests
+ setupSpecialElements(doc, element);
+
+ var outOfScope = element.cloneNode(true); // Append this to the body before running the in-document
+ // Element tests, but after running the Document tests. This
+ // tests that no elements that are not descendants of element
+ // are selected.
+
+ traverse(outOfScope, function(elem) { // Annotate each element as being a clone; used for verifying
+ elem.setAttribute("data-clone", ""); // that none of these elements ever match.
+ });
+
+
+ var detached = element.cloneNode(true); // Detached Element Node tests
+
+ var fragment = doc.createDocumentFragment(); // Fragment Node tests
+ fragment.appendChild(element.cloneNode(true));
+
+ var empty = document.createElement("div"); // Empty Node tests
+
+ // Setup Tests
+ interfaceCheck("Document", doc);
+ interfaceCheck("Detached Element", detached);
+ interfaceCheck("Fragment", fragment);
+ interfaceCheck("In-document Element", element);
+
+ runSpecialSelectorTests("Document", doc);
+ runSpecialSelectorTests("Detached Element", detached);
+ runSpecialSelectorTests("Fragment", fragment);
+ runSpecialSelectorTests("In-document Element", element);
+
+ verifyStaticList("Document", doc, doc);
+ verifyStaticList("Detached Element", doc, detached);
+ verifyStaticList("Fragment", doc, fragment);
+ verifyStaticList("In-document Element", doc, element);
+
+ runInvalidSelectorTest("Document", doc, invalidSelectors);
+ runInvalidSelectorTest("Detached Element", detached, invalidSelectors);
+ runInvalidSelectorTest("Fragment", fragment, invalidSelectors);
+ runInvalidSelectorTest("In-document Element", element, invalidSelectors);
+ runInvalidSelectorTest("Empty Element", empty, invalidSelectors);
+
+ runValidSelectorTest("Document", doc, validSelectors, testType, docType);
+ runValidSelectorTest("Detached Element", detached, validSelectors, testType, docType);
+ runValidSelectorTest("Fragment", fragment, validSelectors, testType, docType);
+
+ doc.body.appendChild(outOfScope); // Append before in-document Element tests.
+ // None of these elements should match
+ runValidSelectorTest("In-document Element", element, validSelectors, testType, docType);
+}
+]]></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All.html b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All.html
new file mode 100644
index 0000000000..7d68e7f297
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<meta name=timeout content=long>
+<title>Selectors-API Test Suite: HTML</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="selectors.js"></script>
+<script src="ParentNode-querySelector-All.js"></script>
+<style>iframe { visibility: hidden; position: absolute; }</style>
+
+<div id="log">This test requires JavaScript.</div>
+
+<script>
+async_test(function() {
+ var frame = document.createElement("iframe");
+ var self = this;
+ frame.onload = function() {
+ // :target doesn't work before a page rendering on some browsers. We run
+ // tests after an animation frame because it may be later than the first
+ // page rendering.
+ requestAnimationFrame(self.step_func_done(init.bind(self, frame)));
+ };
+ frame.src = "ParentNode-querySelector-All-content.html#target";
+ document.body.appendChild(frame);
+});
+
+function init(target) {
+ /*
+ * This test suite tests Selectors API methods in 4 different contexts:
+ * 1. Document node
+ * 2. In-document Element node
+ * 3. Detached Element node (an element with no parent, not in the document)
+ * 4. Document Fragment node
+ *
+ * For each context, the following tests are run:
+ *
+ * The interface check tests ensure that each type of node exposes the Selectors API methods
+ *
+ * The special selector tests verify the result of passing special values for the selector parameter,
+ * to ensure that the correct WebIDL processing is performed, such as stringification of null and
+ * undefined and missing parameter. The universal selector is also tested here, rather than with the
+ * rest of ordinary selectors for practical reasons.
+ *
+ * The static list verification tests ensure that the node lists returned by the method remain unchanged
+ * due to subsequent document modication, and that a new list is generated each time the method is
+ * invoked based on the current state of the document.
+ *
+ * The invalid selector tests ensure that SyntaxError is thrown for invalid forms of selectors
+ *
+ * The valid selector tests check the result from querying many different types of selectors, with a
+ * list of expected elements. This checks that querySelector() always returns the first result from
+ * querySelectorAll(), and that all matching elements are correctly returned in tree-order. The tests
+ * can be limited by specifying the test types to run, using the testType variable. The constants for this
+ * can be found in selectors.js.
+ *
+ * All the selectors tested for both the valid and invalid selector tests are found in selectors.js.
+ * See comments in that file for documentation of the format used.
+ *
+ * The ParentNode-querySelector-All.js file contains all the common test functions for running each of the aforementioned tests
+ */
+
+ var testType = TEST_QSA;
+ var docType = "html"; // Only run tests suitable for HTML
+
+ // Prepare the nodes for testing
+ var doc = target.contentDocument; // Document Node tests
+
+ var element = doc.getElementById("root"); // In-document Element Node tests
+
+ //Setup the namespace tests
+ setupSpecialElements(doc, element);
+
+ var outOfScope = element.cloneNode(true); // Append this to the body before running the in-document
+ // Element tests, but after running the Document tests. This
+ // tests that no elements that are not descendants of element
+ // are selected.
+
+ traverse(outOfScope, function(elem) { // Annotate each element as being a clone; used for verifying
+ elem.setAttribute("data-clone", ""); // that none of these elements ever match.
+ });
+
+
+ var detached = element.cloneNode(true); // Detached Element Node tests
+
+ var fragment = doc.createDocumentFragment(); // Fragment Node tests
+ fragment.appendChild(element.cloneNode(true));
+
+ var empty = document.createElement("div"); // Empty Node tests
+
+ // Setup Tests
+ interfaceCheck("Document", doc);
+ interfaceCheck("Detached Element", detached);
+ interfaceCheck("Fragment", fragment);
+ interfaceCheck("In-document Element", element);
+
+ runSpecialSelectorTests("Document", doc);
+ runSpecialSelectorTests("Detached Element", detached);
+ runSpecialSelectorTests("Fragment", fragment);
+ runSpecialSelectorTests("In-document Element", element);
+
+ verifyStaticList("Document", doc, doc);
+ verifyStaticList("Detached Element", doc, detached);
+ verifyStaticList("Fragment", doc, fragment);
+ verifyStaticList("In-document Element", doc, element);
+
+ runInvalidSelectorTest("Document", doc, invalidSelectors);
+ runInvalidSelectorTest("Detached Element", detached, invalidSelectors);
+ runInvalidSelectorTest("Fragment", fragment, invalidSelectors);
+ runInvalidSelectorTest("In-document Element", element, invalidSelectors);
+ runInvalidSelectorTest("Empty Element", empty, invalidSelectors);
+
+ runValidSelectorTest("Document", doc, validSelectors, testType, docType);
+ runValidSelectorTest("Detached Element", detached, validSelectors, testType, docType);
+ runValidSelectorTest("Fragment", fragment, validSelectors, testType, docType);
+
+ doc.body.appendChild(outOfScope); // Append before in-document Element tests.
+ // None of these elements should match
+ runValidSelectorTest("In-document Element", element, validSelectors, testType, docType);
+}
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All.js b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All.js
new file mode 100644
index 0000000000..3c6c503179
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-All.js
@@ -0,0 +1,261 @@
+// Require selectors.js to be included before this.
+
+/*
+ * Create and append special elements that cannot be created correctly with HTML markup alone.
+ */
+function setupSpecialElements(doc, parent) {
+ // Setup null and undefined tests
+ parent.appendChild(doc.createElement("null"));
+ parent.appendChild(doc.createElement("undefined"));
+
+ // Setup namespace tests
+ var anyNS = doc.createElement("div");
+ var noNS = doc.createElement("div");
+ anyNS.id = "any-namespace";
+ noNS.id = "no-namespace";
+
+ var divs;
+ div = [doc.createElement("div"),
+ doc.createElementNS("http://www.w3.org/1999/xhtml", "div"),
+ doc.createElementNS("", "div"),
+ doc.createElementNS("http://www.example.org/ns", "div")];
+
+ div[0].id = "any-namespace-div1";
+ div[1].id = "any-namespace-div2";
+ div[2].setAttribute("id", "any-namespace-div3"); // Non-HTML elements can't use .id property
+ div[3].setAttribute("id", "any-namespace-div4");
+
+ for (var i = 0; i < div.length; i++) {
+ anyNS.appendChild(div[i])
+ }
+
+ div = [doc.createElement("div"),
+ doc.createElementNS("http://www.w3.org/1999/xhtml", "div"),
+ doc.createElementNS("", "div"),
+ doc.createElementNS("http://www.example.org/ns", "div")];
+
+ div[0].id = "no-namespace-div1";
+ div[1].id = "no-namespace-div2";
+ div[2].setAttribute("id", "no-namespace-div3"); // Non-HTML elements can't use .id property
+ div[3].setAttribute("id", "no-namespace-div4");
+
+ for (i = 0; i < div.length; i++) {
+ noNS.appendChild(div[i])
+ }
+
+ parent.appendChild(anyNS);
+ parent.appendChild(noNS);
+
+ var span = doc.getElementById("attr-presence-i1");
+ span.setAttributeNS("http://www.example.org/ns", "title", "");
+}
+
+/*
+ * Check that the querySelector and querySelectorAll methods exist on the given Node
+ */
+function interfaceCheck(type, obj) {
+ test(function() {
+ var q = typeof obj.querySelector === "function";
+ assert_true(q, type + " supports querySelector.");
+ }, type + " supports querySelector")
+
+ test(function() {
+ var qa = typeof obj.querySelectorAll === "function";
+ assert_true( qa, type + " supports querySelectorAll.");
+ }, type + " supports querySelectorAll")
+
+ test(function() {
+ var list = obj.querySelectorAll("div");
+ if (obj.ownerDocument) { // The object is not a Document
+ assert_true(list instanceof obj.ownerDocument.defaultView.NodeList, "The result should be an instance of a NodeList")
+ } else { // The object is a Document
+ assert_true(list instanceof obj.defaultView.NodeList, "The result should be an instance of a NodeList")
+ }
+ }, type + ".querySelectorAll returns NodeList instance")
+}
+
+/*
+ * Verify that the NodeList returned by querySelectorAll is static and and that a new list is created after
+ * each call. A static list should not be affected by subsequent changes to the DOM.
+ */
+function verifyStaticList(type, doc, root) {
+ var pre, post, preLength;
+
+ test(function() {
+ pre = root.querySelectorAll("div");
+ preLength = pre.length;
+
+ var div = doc.createElement("div");
+ (root.body || root).appendChild(div);
+
+ assert_equals(pre.length, preLength, "The length of the NodeList should not change.")
+ }, type + ": static NodeList")
+
+ test(function() {
+ post = root.querySelectorAll("div"),
+ assert_equals(post.length, preLength + 1, "The length of the new NodeList should be 1 more than the previous list.")
+ }, type + ": new NodeList")
+}
+
+/*
+ * Verify handling of special values for the selector parameter, including stringification of
+ * null and undefined, and the handling of the empty string.
+ */
+function runSpecialSelectorTests(type, root) {
+ let global = (root.ownerDocument || root).defaultView;
+
+ test(function() { // 1
+ assert_equals(root.querySelectorAll(null).length, 1, "This should find one element with the tag name 'NULL'.");
+ }, type + ".querySelectorAll null")
+
+ test(function() { // 2
+ assert_equals(root.querySelectorAll(undefined).length, 1, "This should find one element with the tag name 'UNDEFINED'.");
+ }, type + ".querySelectorAll undefined")
+
+ test(function() { // 3
+ assert_throws_js(global.TypeError, function() {
+ root.querySelectorAll();
+ }, "This should throw a TypeError.")
+ }, type + ".querySelectorAll no parameter")
+
+ test(function() { // 4
+ var elm = root.querySelector(null)
+ assert_not_equals(elm, null, "This should find an element.");
+ assert_equals(elm.tagName.toUpperCase(), "NULL", "The tag name should be 'NULL'.")
+ }, type + ".querySelector null")
+
+ test(function() { // 5
+ var elm = root.querySelector(undefined)
+ assert_not_equals(elm, undefined, "This should find an element.");
+ assert_equals(elm.tagName.toUpperCase(), "UNDEFINED", "The tag name should be 'UNDEFINED'.")
+ }, type + ".querySelector undefined")
+
+ test(function() { // 6
+ assert_throws_js(global.TypeError, function() {
+ root.querySelector();
+ }, "This should throw a TypeError.")
+ }, type + ".querySelector no parameter")
+
+ test(function() { // 7
+ result = root.querySelectorAll("*");
+ var i = 0;
+ traverse(root, function(elem) {
+ if (elem !== root) {
+ assert_equals(elem, result[i], "The result in index " + i + " should be in tree order.");
+ i++;
+ }
+ })
+ }, type + ".querySelectorAll tree order");
+}
+
+/*
+ * Execute queries with the specified valid selectors for both querySelector() and querySelectorAll()
+ * Only run these tests when results are expected. Don't run for syntax error tests.
+ */
+function runValidSelectorTest(type, root, selectors, testType, docType) {
+ var nodeType = "";
+ switch (root.nodeType) {
+ case Node.DOCUMENT_NODE:
+ nodeType = "document";
+ break;
+ case Node.ELEMENT_NODE:
+ nodeType = root.parentNode ? "element" : "detached";
+ break;
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ nodeType = "fragment";
+ break;
+ default:
+ assert_unreached();
+ nodeType = "unknown"; // This should never happen.
+ }
+
+ for (var i = 0; i < selectors.length; i++) {
+ var s = selectors[i];
+ var n = s["name"];
+ var q = s["selector"];
+ var e = s["expect"];
+
+ if ((!s["exclude"] || (s["exclude"].indexOf(nodeType) === -1 && s["exclude"].indexOf(docType) === -1))
+ && (s["testType"] & testType) ) {
+ var foundall, found;
+
+ test(function() {
+ foundall = root.querySelectorAll(q);
+ assert_not_equals(foundall, null, "The method should not return null.")
+ assert_equals(foundall.length, e.length, "The method should return the expected number of matches.")
+
+ for (var i = 0; i < e.length; i++) {
+ assert_not_equals(foundall[i], null, "The item in index " + i + " should not be null.")
+ assert_equals(foundall[i].getAttribute("id"), e[i], "The item in index " + i + " should have the expected ID.");
+ assert_false(foundall[i].hasAttribute("data-clone"), "This should not be a cloned element.");
+ }
+ }, type + ".querySelectorAll: " + n + ": " + q);
+
+ test(function() {
+ found = root.querySelector(q);
+
+ if (e.length > 0) {
+ assert_not_equals(found, null, "The method should return a match.")
+ assert_equals(found.getAttribute("id"), e[0], "The method should return the first match.");
+ assert_equals(found, foundall[0], "The result should match the first item from querySelectorAll.");
+ assert_false(found.hasAttribute("data-clone"), "This should not be annotated as a cloned element.");
+ } else {
+ assert_equals(found, null, "The method should not match anything.");
+ }
+ }, type + ".querySelector: " + n + ": " + q);
+ }
+ }
+}
+
+function windowFor(root) {
+ return root.defaultView || root.ownerDocument.defaultView;
+}
+
+/*
+ * Execute queries with the specified invalid selectors for both querySelector() and querySelectorAll()
+ * Only run these tests when errors are expected. Don't run for valid selector tests.
+ */
+function runInvalidSelectorTest(type, root, selectors) {
+ for (var i = 0; i < selectors.length; i++) {
+ var s = selectors[i];
+ var n = s["name"];
+ var q = s["selector"];
+
+ test(function() {
+ assert_throws_dom("SyntaxError", windowFor(root).DOMException, function() {
+ root.querySelector(q)
+ });
+ }, type + ".querySelector: " + n + ": " + q);
+
+ test(function() {
+ assert_throws_dom("SyntaxError", windowFor(root).DOMException, function() {
+ root.querySelectorAll(q)
+ });
+ }, type + ".querySelectorAll: " + n + ": " + q);
+ }
+}
+
+function traverse(elem, fn) {
+ if (elem.nodeType === elem.ELEMENT_NODE) {
+ fn(elem);
+ }
+ elem = elem.firstChild;
+ while (elem) {
+ traverse(elem, fn);
+ elem = elem.nextSibling;
+ }
+}
+
+function getNodeType(node) {
+ switch (node.nodeType) {
+ case Node.DOCUMENT_NODE:
+ return "document";
+ case Node.ELEMENT_NODE:
+ return node.parentNode ? "element" : "detached";
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ return "fragment";
+ default:
+ assert_unreached();
+ return "unknown"; // This should never happen.
+ }
+}
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-case-insensitive.html b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-case-insensitive.html
new file mode 100644
index 0000000000..e461ee5016
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-case-insensitive.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>querySelector(All) must work with the i and *= selectors</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!-- Regression test for https://github.com/jsdom/jsdom/issues/2551 -->
+
+<input name="User" id="testInput"></input>
+
+<script>
+"use strict";
+const input = document.getElementById("testInput");
+
+test(() => {
+ assert_equals(document.querySelector("input[name*=user i]"), input);
+}, "querySelector");
+
+test(() => {
+ assert_array_equals(document.querySelectorAll("input[name*=user i]"), [input]);
+}, "querySelectorAll");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-escapes.html b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-escapes.html
new file mode 100644
index 0000000000..65a75e5c03
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-escapes.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>querySelector() with CSS escapes</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-parentnode-queryselector">
+<link rel="help" href="https://drafts.csswg.org/css-syntax/#consume-escaped-code-point">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="author" title="bellbind" href="mailto:bellbind@gmail.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+function testMatched(id, selector) {
+ test(() => {
+ const container = document.createElement("div");
+ const child = document.createElement("span");
+ child.id = id;
+
+ container.appendChild(child);
+
+ assert_equals(container.querySelector(selector), child);
+ }, `${JSON.stringify(id)} should match with ${JSON.stringify(selector)}`);
+}
+
+function testNeverMatched(id, selector) {
+ test(() => {
+ const container = document.createElement("div");
+ const child = document.createElement("span");
+ child.id = id;
+
+ container.appendChild(child);
+
+ assert_equals(container.querySelector(selector), null);
+ }, `${JSON.stringify(id)} should never match with ${JSON.stringify(selector)}`);
+}
+
+// 4.3.7 from https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
+testMatched("nonescaped", "#nonescaped");
+
+// - escape hex digit
+testMatched("0nextIsWhiteSpace", "#\\30 nextIsWhiteSpace");
+testMatched("0nextIsNotHexLetters", "#\\30nextIsNotHexLetters");
+testMatched("0connectHexMoreThan6Hex", "#\\000030connectHexMoreThan6Hex");
+testMatched("0spaceMoreThan6Hex", "#\\000030 spaceMoreThan6Hex");
+
+// - hex digit special replacement
+// 1. zero points
+testMatched("zero\u{fffd}", "#zero\\0");
+testNeverMatched("zero\u{0}", "#zero\\0");
+testMatched("zero\u{fffd}", "#zero\\000000");
+testNeverMatched("zero\u{0}", "#zero\\000000");
+// 2. surrogate points
+testMatched("\u{fffd}surrogateFirst", "#\\d83d surrogateFirst");
+testNeverMatched("\ud83dsurrogateFirst", "#\\d83d surrogateFirst");
+testMatched("surrogateSecond\u{fffd}", "#surrogateSecond\\dd11");
+testNeverMatched("surrogateSecond\udd11", "#surrogateSecond\\dd11");
+testMatched("surrogatePair\u{fffd}\u{fffd}", "#surrogatePair\\d83d\\dd11");
+testNeverMatched("surrogatePair\u{1f511}", "#surrogatePair\\d83d\\dd11");
+// 3. out of range points
+testMatched("outOfRange\u{fffd}", "#outOfRange\\110000");
+testMatched("outOfRange\u{fffd}", "#outOfRange\\110030");
+testNeverMatched("outOfRange\u{30}", "#outOfRange\\110030");
+testMatched("outOfRange\u{fffd}", "#outOfRange\\555555");
+testMatched("outOfRange\u{fffd}", "#outOfRange\\ffffff");
+
+// - escape EOF
+testNeverMatched("eof\\", "#eof\\");
+
+// - escape anythong else
+testMatched(".comma", "#\\.comma");
+testMatched("-minus", "#\\-minus");
+testMatched("g", "#\\g");
+
+// non edge cases
+testMatched("aBMPRegular", "#\\61 BMPRegular");
+testMatched("\u{1f511}nonBMP", "#\\1f511 nonBMP");
+testMatched("00continueEscapes", "#\\30\\30 continueEscapes");
+testMatched("00continueEscapes", "#\\30 \\30 continueEscapes");
+testMatched("continueEscapes00", "#continueEscapes\\30 \\30 ");
+testMatched("continueEscapes00", "#continueEscapes\\30 \\30");
+testMatched("continueEscapes00", "#continueEscapes\\30\\30 ");
+testMatched("continueEscapes00", "#continueEscapes\\30\\30");
+
+// ident tests case from CSS tests of chromium source: https://goo.gl/3Cxdov
+testMatched("hello", "#hel\\6Co");
+testMatched("&B", "#\\26 B");
+testMatched("hello", "#hel\\6C o");
+testMatched("spaces", "#spac\\65\r\ns");
+testMatched("spaces", "#sp\\61\tc\\65\fs");
+testMatched("test\u{D799}", "#test\\D799");
+testMatched("\u{E000}", "#\\E000");
+testMatched("test", "#te\\s\\t");
+testMatched("spaces in\tident", "#spaces\\ in\\\tident");
+testMatched(".,:!", "#\\.\\,\\:\\!");
+testMatched("null\u{fffd}", "#null\\0");
+testMatched("null\u{fffd}", "#null\\0000");
+testMatched("large\u{fffd}", "#large\\110000");
+testMatched("large\u{fffd}", "#large\\23456a");
+testMatched("surrogate\u{fffd}", "#surrogate\\D800");
+testMatched("surrogate\u{fffd}", "#surrogate\\0DBAC");
+testMatched("\u{fffd}surrogate", "#\\00DFFFsurrogate");
+testMatched("\u{10ffff}", "#\\10fFfF");
+testMatched("\u{10ffff}0", "#\\10fFfF0");
+testMatched("\u{100000}00", "#\\10000000");
+testMatched("eof\u{fffd}", "#eof\\");
+
+testMatched("simple-ident", "#simple-ident");
+testMatched("testing123", "#testing123");
+testMatched("_underscore", "#_underscore");
+testMatched("-text", "#-text");
+testMatched("-m", "#-\\6d");
+testMatched("--abc", "#--abc");
+testMatched("--", "#--");
+testMatched("--11", "#--11");
+testMatched("---", "#---");
+testMatched("\u{2003}", "#\u{2003}");
+testMatched("\u{A0}", "#\u{A0}");
+testMatched("\u{1234}", "#\u{1234}");
+testMatched("\u{12345}", "#\u{12345}");
+testMatched("\u{fffd}", "#\u{0}");
+testMatched("ab\u{fffd}c", "#ab\u{0}c");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-scope.html b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-scope.html
new file mode 100644
index 0000000000..d984956d6c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-querySelector-scope.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>querySelector(All) scoped to a root element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div><h1 id="test"></h1><p><span>hello</span></p></div>
+
+<script>
+"use strict";
+const div = document.querySelector("div");
+const p = document.querySelector("p");
+
+test(() => {
+ assert_equals(div.querySelector(":scope > p"), p);
+ assert_equals(div.querySelector(":scope > span"), null);
+}, "querySelector with :scope");
+
+test(() => {
+ assert_equals(div.querySelector("#test + p"), p);
+ assert_equals(p.querySelector("#test + p"), null);
+}, "querySelector with id and sibling");
+
+test(() => {
+ assert_array_equals(div.querySelectorAll(":scope > p"), [p]);
+ assert_array_equals(div.querySelectorAll(":scope > span"), []);
+}, "querySelectorAll with :scope");
+
+test(() => {
+ assert_array_equals(div.querySelectorAll("#test + p"), [p]);
+ assert_array_equals(p.querySelectorAll("#test + p"), []);
+}, "querySelectorAll with id and sibling");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-querySelectorAll-removed-elements.html b/testing/web-platform/tests/dom/nodes/ParentNode-querySelectorAll-removed-elements.html
new file mode 100644
index 0000000000..3cefc80906
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-querySelectorAll-removed-elements.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>querySelectorAll must not return removed elements</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!-- Regression test for https://github.com/jsdom/jsdom/issues/2519 -->
+
+<div id="container"></div>
+
+<script>
+"use strict";
+
+setup({ single_test: true });
+
+const container = document.querySelector("#container");
+function getIDs() {
+ return [...container.querySelectorAll("a.test")].map(el => el.id);
+}
+
+container.innerHTML = `<a id="link-a" class="test">a link</a>`;
+assert_array_equals(getIDs(), ["link-a"], "Sanity check: initial setup");
+
+container.innerHTML = `<a id="link-b" class="test"><img src="foo.jpg"></a>`;
+assert_array_equals(getIDs(), ["link-b"], "After replacement");
+
+container.innerHTML = `<a id="link-a" class="test">a link</a>`;
+assert_array_equals(getIDs(), ["link-a"], "After changing back to the original HTML");
+
+done();
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-querySelectors-exclusive.html b/testing/web-platform/tests/dom/nodes/ParentNode-querySelectors-exclusive.html
new file mode 100644
index 0000000000..5cff9367cf
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-querySelectors-exclusive.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>querySelector/querySelectorAll should not include their thisArg</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!-- Regression test for https://github.com/jsdom/jsdom/issues/2296 -->
+
+<script>
+"use strict";
+
+setup({ single_test: true });
+
+const button = document.createElement("button");
+
+assert_equals(button.querySelector("*"), null, "querySelector, '*', before modification");
+assert_equals(button.querySelector("button"), null, "querySelector, 'button', before modification");
+assert_equals(button.querySelector("button, span"), null, "querySelector, 'button, span', before modification");
+assert_array_equals(button.querySelectorAll("*"), [], "querySelectorAll, '*', before modification");
+assert_array_equals(button.querySelectorAll("button"), [], "querySelectorAll, 'button', before modification");
+assert_array_equals(
+ button.querySelectorAll("button, span"), [],
+ "querySelectorAll, 'button, span', before modification"
+);
+
+
+button.innerHTML = "text";
+
+assert_equals(button.querySelector("*"), null, "querySelector, '*', after modification");
+assert_equals(button.querySelector("button"), null, "querySelector, 'button', after modification");
+assert_equals(button.querySelector("button, span"), null, "querySelector, 'button, span', after modification");
+assert_array_equals(button.querySelectorAll("*"), [], "querySelectorAll, '*', after modification");
+assert_array_equals(button.querySelectorAll("button"), [], "querySelectorAll, 'button', after modification");
+assert_array_equals(
+ button.querySelectorAll("button, span"), [],
+ "querySelectorAll, 'button, span', after modification"
+);
+
+done();
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-querySelectors-namespaces.html b/testing/web-platform/tests/dom/nodes/ParentNode-querySelectors-namespaces.html
new file mode 100644
index 0000000000..714999b3f0
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-querySelectors-namespaces.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>querySelectorAll must work with namespace attribute selectors on SVG</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!-- Regression test for https://github.com/jsdom/jsdom/issues/2028 -->
+
+<svg id="thesvg" xlink:href="foo"></svg>
+
+<script>
+"use strict";
+
+setup({ single_test: true });
+
+const el = document.getElementById("thesvg");
+
+assert_equals(document.querySelector("[*|href]"), el);
+assert_array_equals(document.querySelectorAll("[*|href]"), [el]);
+
+done();
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-querySelectors-space-and-dash-attribute-value.html b/testing/web-platform/tests/dom/nodes/ParentNode-querySelectors-space-and-dash-attribute-value.html
new file mode 100644
index 0000000000..e08c6e6db1
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-querySelectors-space-and-dash-attribute-value.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>querySelector(All) must work for attribute values that contain spaces and dashes</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!-- Regression test for https://github.com/jsdom/jsdom/issues/2542 -->
+
+<a title="test with - dash and space" id="testme">Test One</a>
+
+<script>
+"use strict";
+const el = document.getElementById("testme");
+
+test(() => {
+ assert_equals(document.querySelector("a[title='test with - dash and space']"), el);
+}, "querySelector");
+
+test(() => {
+ assert_equals(document.querySelector("a[title='test with - dash and space']"), el);
+}, "querySelectorAll");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/ParentNode-replaceChildren.html b/testing/web-platform/tests/dom/nodes/ParentNode-replaceChildren.html
new file mode 100644
index 0000000000..ee22009fcb
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ParentNode-replaceChildren.html
@@ -0,0 +1,205 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>ParentNode.replaceChildren</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-parentnode-replacechildren">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="pre-insertion-validation-hierarchy.js"></script>
+<script>
+ preInsertionValidateHierarchy("replaceChildren");
+
+ function test_replacechildren(node, nodeName) {
+ test(() => {
+ const parent = node.cloneNode();
+ parent.replaceChildren();
+ assert_array_equals(parent.childNodes, []);
+ }, `${nodeName}.replaceChildren() without any argument, on a parent having no child.`);
+
+ test(() => {
+ const parent = node.cloneNode();
+ parent.replaceChildren(null);
+ assert_equals(parent.childNodes[0].textContent, 'null');
+ }, `${nodeName}.replaceChildren() with null as an argument, on a parent having no child.`);
+
+ test(() => {
+ const parent = node.cloneNode();
+ parent.replaceChildren(undefined);
+ assert_equals(parent.childNodes[0].textContent, 'undefined');
+ }, `${nodeName}.replaceChildren() with undefined as an argument, on a parent having no child.`);
+
+ test(() => {
+ const parent = node.cloneNode();
+ parent.replaceChildren('text');
+ assert_equals(parent.childNodes[0].textContent, 'text');
+ }, `${nodeName}.replaceChildren() with only text as an argument, on a parent having no child.`);
+
+ test(() => {
+ const parent = node.cloneNode();
+ const x = document.createElement('x');
+ parent.replaceChildren(x);
+ assert_array_equals(parent.childNodes, [x]);
+ }, `${nodeName}.replaceChildren() with only one element as an argument, on a parent having no child.`);
+
+ test(() => {
+ const parent = node.cloneNode();
+ const child = document.createElement('test');
+ parent.appendChild(child);
+ parent.replaceChildren();
+ assert_array_equals(parent.childNodes, []);
+ }, `${nodeName}.replaceChildren() without any argument, on a parent having a child.`);
+
+ test(() => {
+ const parent = node.cloneNode();
+ const child = document.createElement('test');
+ parent.appendChild(child);
+ parent.replaceChildren(null);
+ assert_equals(parent.childNodes.length, 1);
+ assert_equals(parent.childNodes[0].textContent, 'null');
+ }, `${nodeName}.replaceChildren() with null as an argument, on a parent having a child.`);
+
+ test(() => {
+ const parent = node.cloneNode();
+ const x = document.createElement('x');
+ const child = document.createElement('test');
+ parent.appendChild(child);
+ parent.replaceChildren(x, 'text');
+ assert_equals(parent.childNodes.length, 2);
+ assert_equals(parent.childNodes[0], x);
+ assert_equals(parent.childNodes[1].textContent, 'text');
+ }, `${nodeName}.replaceChildren() with one element and text as argument, on a parent having a child.`);
+
+ async_test(t => {
+ let phase = 0;
+
+ const previousParent = node.cloneNode();
+ const insertions = [
+ document.createElement("test1"),
+ document.createElement("test2")
+ ];
+ previousParent.append(...insertions);
+
+ const parent = node.cloneNode();
+ const children = [
+ document.createElement("test3"),
+ document.createElement("test4")
+ ];
+ parent.append(...children);
+
+ const previousObserver = new MutationObserver(mutations => {
+ t.step(() => {
+ assert_equals(phase, 0);
+ assert_equals(mutations.length, 2);
+ for (const [i, mutation] of Object.entries(mutations)) {
+ assert_equals(mutation.type, "childList");
+ assert_equals(mutation.addedNodes.length, 0);
+ assert_equals(mutation.removedNodes.length, 1);
+ assert_equals(mutation.removedNodes[0], insertions[i]);
+ }
+ phase = 1;
+ });
+ });
+ previousObserver.observe(previousParent, { childList: true });
+
+ const observer = new MutationObserver(mutations => {
+ t.step(() => {
+ assert_equals(phase, 1, "phase");
+ assert_equals(mutations.length, 1, "mutations.length");
+ const mutation = mutations[0];
+ assert_equals(mutation.type, "childList", "mutation.type");
+ assert_equals(mutation.addedNodes.length, 2, "added nodes length");
+ assert_array_equals([...mutation.addedNodes], insertions, "added nodes");
+ assert_equals(mutation.removedNodes.length, 2, "removed nodes length");
+ assert_array_equals([...mutation.removedNodes], children, "removed nodes");
+ });
+ t.done();
+ });
+ observer.observe(parent, { childList: true });
+
+ parent.replaceChildren(...previousParent.children);
+ }, `${nodeName}.replaceChildren() should move nodes in the right order`);
+ }
+
+ test_replacechildren(document.createElement('div'), 'Element');
+ test_replacechildren(document.createDocumentFragment(), 'DocumentFragment');
+
+ async_test(t => {
+ let root = document.createElement("div");
+ root.innerHTML = "<div id='a'>text<div id='b'>text2</div></div>";
+ const a = root.firstChild;
+ const b = a.lastChild;
+ const txt = b.previousSibling;
+ const txt2 = b.firstChild;
+
+ const observer = new MutationObserver((mutations) => {
+
+ assert_equals(mutations.length, 2, "mutations.length");
+
+ assert_equals(mutations[0].target.id, "a", "Target of the removal");
+ assert_equals(mutations[0].addedNodes.length, 0, "Should not have added nodes");
+ assert_equals(mutations[0].removedNodes.length, 1, "Should have 1 removed node");
+ assert_equals(mutations[0].removedNodes[0], txt, "Should have removed txt node");
+
+ assert_equals(mutations[1].target.id, "b", "Target of the replaceChildren");
+ assert_equals(mutations[1].removedNodes.length, 1, "Should have removed 1 node");
+ assert_equals(mutations[1].removedNodes[0], txt2, "Should have removed txt2 node");
+ assert_equals(mutations[1].addedNodes.length, 1, "Should have added a node");
+ assert_equals(mutations[1].addedNodes[0], txt, "Should have added txt node");
+
+ observer.disconnect();
+ t.done();
+ });
+
+ observer.observe(a, {
+ subtree: true,
+ childList: true
+ });
+
+ b.replaceChildren(txt);
+ }, "There should be a MutationRecord for the node removed from another parent node.");
+
+ async_test(t => {
+ // This is almost the same test as above, but passes two nodes to replaceChildren.
+
+ let root = document.createElement("div");
+ root.innerHTML = "<div id='a'><div id='c'></div>text<div id='b'>text2</div></div>";
+ const a = root.firstChild;
+ const b = a.lastChild;
+ const c = a.firstChild;
+ const txt = b.previousSibling;
+ const txt2 = b.firstChild;
+
+ const observer = new MutationObserver((mutations) => {
+
+ assert_equals(mutations.length, 3, "mutations.length");
+
+ assert_equals(mutations[0].target.id, "a", "Target of the removal");
+ assert_equals(mutations[0].addedNodes.length, 0, "Should not have added nodes");
+ assert_equals(mutations[0].removedNodes.length, 1, "Should have 1 removed node");
+ assert_equals(mutations[0].removedNodes[0], c, "Should have removed c node");
+
+ assert_equals(mutations[1].target.id, "a", "Target of the removal");
+ assert_equals(mutations[1].addedNodes.length, 0, "Should not have added nodes");
+ assert_equals(mutations[1].removedNodes.length, 1, "Should have 1 removed node");
+ assert_equals(mutations[1].removedNodes[0], txt, "Should have removed txt node");
+
+ assert_equals(mutations[2].target.id, "b", "Target of the replaceChildren");
+ assert_equals(mutations[2].removedNodes.length, 1, "Should have removed 1 node");
+ assert_equals(mutations[2].removedNodes[0], txt2, "Should have removed txt2 node");
+ assert_equals(mutations[2].addedNodes.length, 2, "Should have added a node");
+ assert_equals(mutations[2].addedNodes[0], c, "Should have added c node");
+ assert_equals(mutations[2].addedNodes[1], txt, "Should have added txt node");
+
+ observer.disconnect();
+ t.done();
+ });
+
+ observer.observe(a, {
+ subtree: true,
+ childList: true
+ });
+
+ b.replaceChildren(c, txt);
+ }, "There should be MutationRecords for the nodes removed from another parent node.");
+</script>
+
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/ProcessingInstruction-escapes-1.xhtml b/testing/web-platform/tests/dom/nodes/ProcessingInstruction-escapes-1.xhtml
new file mode 100644
index 0000000000..d629a8464b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ProcessingInstruction-escapes-1.xhtml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="support/style.css" type="text/css"?>
+<?xml-stylesheet href="data:text/css,&#x41;&amp;&apos;" type="text/css"?>
+<?xml-stylesheet href="data:text/css,&#65;&amp;&apos;" type="text/css"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>ProcessingInstruction numeric escapes</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-processinginstruction-target"/>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-characterdata-data"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"/>
+<script>
+<![CDATA[
+test(function() {
+ var pienc = document.firstChild.nextSibling;
+ assert_true(pienc instanceof ProcessingInstruction)
+ assert_equals(pienc.target, "xml-stylesheet")
+ assert_equals(pienc.data, 'href="data:text/css,&#x41;&amp;&apos;" type="text/css"')
+ assert_equals(pienc.sheet.href, "data:text/css,A&'");
+
+ pienc = pienc.nextSibling;
+ assert_true(pienc instanceof ProcessingInstruction)
+ assert_equals(pienc.target, "xml-stylesheet")
+ assert_equals(pienc.data, 'href="data:text/css,&#65;&amp;&apos;" type="text/css"')
+ assert_equals(pienc.sheet.href, "data:text/css,A&'");
+})
+]]>
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/ProcessingInstruction-in-doctype.xhtml b/testing/web-platform/tests/dom/nodes/ProcessingInstruction-in-doctype.xhtml
new file mode 100644
index 0000000000..c35e710f9c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ProcessingInstruction-in-doctype.xhtml
@@ -0,0 +1,15 @@
+<!DOCTYPE html [<?x y?>]><html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>XML: Processing instruction in doctype internal subset</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ test(() => {
+ assert_equals(document.documentElement.previousSibling, document.firstChild);
+ assert_equals(document.firstChild.nodeType, Node.DOCUMENT_TYPE_NODE);
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/ProcessingInstruction-literal-1.xhtml b/testing/web-platform/tests/dom/nodes/ProcessingInstruction-literal-1.xhtml
new file mode 100644
index 0000000000..4eaf86cbdc
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ProcessingInstruction-literal-1.xhtml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>&lt;?xml?> is not a ProcessingInstruction</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"/>
+<script>
+test(function() {
+ assert_equals(document.firstChild, document.documentElement)
+})
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/ProcessingInstruction-literal-2.xhtml b/testing/web-platform/tests/dom/nodes/ProcessingInstruction-literal-2.xhtml
new file mode 100644
index 0000000000..d878c697c0
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/ProcessingInstruction-literal-2.xhtml
@@ -0,0 +1,21 @@
+<?xml-stylesheet href="support/style.css" type="text/css"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>ProcessingInstruction literals</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-processinginstruction-target"/>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-characterdata-data"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"/>
+<script>
+test(function() {
+ var pienc = document.firstChild;
+ assert_true(pienc instanceof ProcessingInstruction)
+ assert_equals(pienc.target, "xml-stylesheet")
+ assert_equals(pienc.data, 'href="support/style.css" type="text/css"')
+})
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/Text-constructor.html b/testing/web-platform/tests/dom/nodes/Text-constructor.html
new file mode 100644
index 0000000000..dbd9a0be01
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Text-constructor.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Text constructor</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-text">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="Comment-Text-constructor.js"></script>
+<div id="log"></div>
+<script>
+test_constructor("Text");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Text-splitText.html b/testing/web-platform/tests/dom/nodes/Text-splitText.html
new file mode 100644
index 0000000000..2dd23018cb
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Text-splitText.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Text.splitText()</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-text-splittextoffset">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var text = document.createTextNode("camembert");
+ assert_throws_dom("INDEX_SIZE_ERR", function () { text.splitText(10) });
+}, "Split text after end of data");
+
+test(function() {
+ var text = document.createTextNode("");
+ var new_text = text.splitText(0);
+ assert_equals(text.data, "");
+ assert_equals(new_text.data, "");
+}, "Split empty text");
+
+test(function() {
+ var text = document.createTextNode("comté");
+ var new_text = text.splitText(0);
+ assert_equals(text.data, "");
+ assert_equals(new_text.data, "comté");
+}, "Split text at beginning");
+
+test(function() {
+ var text = document.createTextNode("comté");
+ var new_text = text.splitText(5);
+ assert_equals(text.data, "comté");
+ assert_equals(new_text.data, "");
+}, "Split text at end");
+
+test(function() {
+ var text = document.createTextNode("comté");
+ var new_text = text.splitText(3);
+ assert_equals(text.data, "com");
+ assert_equals(new_text.data, "té");
+ assert_equals(new_text.parentNode, null);
+}, "Split root");
+
+test(function() {
+ var parent = document.createElement('div');
+ var text = document.createTextNode("bleu");
+ parent.appendChild(text);
+ var new_text = text.splitText(2);
+ assert_equals(text.data, "bl");
+ assert_equals(new_text.data, "eu");
+ assert_equals(text.nextSibling, new_text);
+ assert_equals(new_text.parentNode, parent);
+}, "Split child");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/Text-wholeText.html b/testing/web-platform/tests/dom/nodes/Text-wholeText.html
new file mode 100644
index 0000000000..2467930da8
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/Text-wholeText.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Text - wholeText</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-text-wholetext">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+
+test(() => {
+ const parent = document.createElement("div");
+
+ const t1 = document.createTextNode("a");
+ const t2 = document.createTextNode("b");
+ const t3 = document.createTextNode("c");
+
+ assert_equals(t1.wholeText, t1.textContent);
+
+ parent.appendChild(t1);
+
+ assert_equals(t1.wholeText, t1.textContent);
+
+ parent.appendChild(t2);
+
+ assert_equals(t1.wholeText, t1.textContent + t2.textContent);
+ assert_equals(t2.wholeText, t1.textContent + t2.textContent);
+
+ parent.appendChild(t3);
+
+ assert_equals(t1.wholeText, t1.textContent + t2.textContent + t3.textContent);
+ assert_equals(t2.wholeText, t1.textContent + t2.textContent + t3.textContent);
+ assert_equals(t3.wholeText, t1.textContent + t2.textContent + t3.textContent);
+
+ const a = document.createElement("a");
+ a.textContent = "I'm an Anchor";
+ parent.insertBefore(a, t3);
+
+ const span = document.createElement("span");
+ span.textContent = "I'm a Span";
+ parent.appendChild(document.createElement("span"));
+
+ assert_equals(t1.wholeText, t1.textContent + t2.textContent);
+ assert_equals(t2.wholeText, t1.textContent + t2.textContent);
+ assert_equals(t3.wholeText, t3.textContent);
+}, "wholeText returns text of all Text nodes logically adjacent to the node, in document order.");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/adoption.window.js b/testing/web-platform/tests/dom/nodes/adoption.window.js
new file mode 100644
index 0000000000..ad90aaf375
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/adoption.window.js
@@ -0,0 +1,58 @@
+// Testing DocumentFragment with host separately as it has a different node document by design
+test(() => {
+ const df = document.createElement("template").content;
+ const child = df.appendChild(new Text('hi'));
+ assert_not_equals(df.ownerDocument, document);
+ const nodeDocument = df.ownerDocument;
+ document.body.appendChild(df);
+ assert_equals(df.childNodes.length, 0);
+ assert_equals(child.ownerDocument, document);
+ assert_equals(df.ownerDocument, nodeDocument);
+}, `appendChild() and DocumentFragment with host`);
+
+test(() => {
+ const df = document.createElement("template").content;
+ const child = df.appendChild(new Text('hi'));
+ const nodeDocument = df.ownerDocument;
+ document.adoptNode(df);
+ assert_equals(df.childNodes.length, 1);
+ assert_equals(child.ownerDocument, nodeDocument);
+ assert_equals(df.ownerDocument, nodeDocument);
+}, `adoptNode() and DocumentFragment with host`);
+
+[
+ {
+ "name": "DocumentFragment",
+ "creator": doc => doc.createDocumentFragment()
+ },
+ {
+ "name": "ShadowRoot",
+ "creator": doc => doc.createElementNS("http://www.w3.org/1999/xhtml", "div").attachShadow({mode: "closed"})
+ }
+].forEach(dfTest => {
+ test(() => {
+ const doc = new Document();
+ const df = dfTest.creator(doc);
+ const child = df.appendChild(new Text('hi'));
+ assert_equals(df.ownerDocument, doc);
+
+ document.body.appendChild(df);
+ assert_equals(df.childNodes.length, 0);
+ assert_equals(child.ownerDocument, document);
+ assert_equals(df.ownerDocument, doc);
+ }, `appendChild() and ${dfTest.name}`);
+
+ test(() => {
+ const doc = new Document();
+ const df = dfTest.creator(doc);
+ const child = df.appendChild(new Text('hi'));
+ if (dfTest.name === "ShadowRoot") {
+ assert_throws_dom("HierarchyRequestError", () => document.adoptNode(df));
+ } else {
+ document.adoptNode(df);
+ assert_equals(df.childNodes.length, 1);
+ assert_equals(child.ownerDocument, document);
+ assert_equals(df.ownerDocument, document);
+ }
+ }, `adoptNode() and ${dfTest.name}`);
+});
diff --git a/testing/web-platform/tests/dom/nodes/append-on-Document.html b/testing/web-platform/tests/dom/nodes/append-on-Document.html
new file mode 100644
index 0000000000..78f278b381
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/append-on-Document.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>DocumentType.append</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-parentnode-append">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+function test_append_on_Document() {
+
+ var node = document.implementation.createDocument(null, null);
+ test(function() {
+ var parent = node.cloneNode();
+ parent.append();
+ assert_array_equals(parent.childNodes, []);
+ }, 'Document.append() without any argument, on a Document having no child.');
+
+ test(function() {
+ var parent = node.cloneNode();
+ var x = document.createElement('x');
+ parent.append(x);
+ assert_array_equals(parent.childNodes, [x]);
+ }, 'Document.append() with only one element as an argument, on a Document having no child.');
+
+ test(function() {
+ var parent = node.cloneNode();
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ parent.appendChild(x);
+ assert_throws_dom('HierarchyRequestError', function() { parent.append(y); });
+ assert_array_equals(parent.childNodes, [x]);
+ }, 'Document.append() with only one element as an argument, on a Document having one child.');
+
+ test(function() {
+ var parent = node.cloneNode();
+ assert_throws_dom('HierarchyRequestError', function() { parent.append('text'); });
+ assert_array_equals(parent.childNodes, []);
+ }, 'Document.append() with text as an argument, on a Document having no child.');
+
+ test(function() {
+ var parent = node.cloneNode();
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ assert_throws_dom('HierarchyRequestError', function() { parent.append(x, y); });
+ assert_array_equals(parent.childNodes, []);
+ }, 'Document.append() with two elements as the argument, on a Document having no child.');
+
+}
+
+test_append_on_Document();
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/attributes-namednodemap.html b/testing/web-platform/tests/dom/nodes/attributes-namednodemap.html
new file mode 100644
index 0000000000..96f9d30703
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/attributes-namednodemap.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<title>Tests of some tricky semantics around NamedNodeMap and the element.attributes collection</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://dom.spec.whatwg.org/#interface-namednodemap">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-element-attributes">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+
+ const element = document.createElement("div");
+ element.setAttribute("x", "first");
+
+ assert_equals(element.attributes.length, 1, "one attribute");
+ assert_equals(element.attributes.x.value, "first");
+
+}, "an attribute set by setAttribute should be accessible as a field on the `attributes` field of an Element");
+
+test(() => {
+
+ const element = document.createElement("div");
+ const map = element.attributes;
+
+ assert_equals(map.length, 0);
+
+ const attr1 = document.createAttribute("attr1");
+ map.setNamedItem(attr1);
+ assert_equals(map.attr1, attr1);
+ assert_equals(map.length, 1);
+
+ const attr2 = document.createAttribute("attr2");
+ map.setNamedItem(attr2);
+ assert_equals(map.attr2, attr2);
+ assert_equals(map.length, 2);
+
+ const rm1 = map.removeNamedItem("attr1");
+ assert_equals(rm1, attr1);
+ assert_equals(map.length, 1);
+
+ const rm2 = map.removeNamedItem("attr2");
+ assert_equals(rm2, attr2);
+ assert_equals(map.length, 0);
+
+}, "setNamedItem and removeNamedItem on `attributes` should add and remove fields from `attributes`");
+
+test(() => {
+
+ const element = document.createElement("div");
+ const map = element.attributes;
+
+ const fooAttribute = document.createAttribute("foo");
+ map.setNamedItem(fooAttribute);
+
+ const itemAttribute = document.createAttribute("item");
+ map.setNamedItem(itemAttribute);
+
+ assert_equals(map.foo, fooAttribute);
+ assert_equals(map.item, NamedNodeMap.prototype.item);
+ assert_equals(typeof map.item, "function");
+
+ map.removeNamedItem("item");
+ assert_equals(map.item, NamedNodeMap.prototype.item);
+ assert_equals(typeof map.item, "function");
+
+}, "setNamedItem and removeNamedItem on `attributes` should not interfere with existing method names");
+
+test(() => {
+
+ const element = document.createElement("div");
+ element.setAttributeNS(null, "x", "first");
+
+ assert_equals(element.attributes.length, 1, "one attribute");
+ assert_equals(element.attributes.x.value, "first");
+
+}, "an attribute with a null namespace should be accessible as a field on the `attributes` field of an Element");
+
+test(() => {
+
+ const element = document.createElement("div");
+ element.setAttributeNS("foo", "x", "first");
+
+ assert_equals(element.attributes.length, 1, "one attribute");
+ assert_equals(element.attributes.x.value, "first");
+
+}, "an attribute with a set namespace should be accessible as a field on the `attributes` field of an Element");
+
+test(() => {
+
+ const element = document.createElement("div");
+ element.setAttributeNS("foo", "setNamedItem", "first");
+
+ assert_equals(element.attributes.length, 1, "one attribute");
+ assert_equals(typeof element.attributes.setNamedItem, "function");
+
+}, "setting an attribute should not overwrite the methods of an `NamedNodeMap` object");
+
+test(() => {
+
+ const element = document.createElement("div");
+ element.setAttributeNS("foo", "toString", "first");
+
+ assert_equals(element.attributes.length, 1, "one attribute");
+ assert_equals(typeof element.attributes.toString, "function");
+
+}, "setting an attribute should not overwrite the methods defined by prototype ancestors of an `NamedNodeMap` object");
+
+test(() => {
+
+ const element = document.createElement("div");
+ element.setAttributeNS("foo", "length", "first");
+
+ assert_equals(element.attributes.length, 1, "one attribute");
+
+}, "setting an attribute should not overwrite the the length property of an `NamedNodeMap` object");
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/attributes.html b/testing/web-platform/tests/dom/nodes/attributes.html
new file mode 100644
index 0000000000..c6db7eb8aa
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/attributes.html
@@ -0,0 +1,858 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Attributes tests</title>
+<link rel=help href="https://dom.spec.whatwg.org/#attr">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-setattribute">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-setattributens">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="attributes.js"></script>
+<script src="productions.js"></script>
+<div id="log"></div>
+<span id="test1"></span>
+<span class="&amp;&lt;&gt;foo"></span>
+<span id="test2">
+ <span ~=""></span>
+ <span ~></span>
+ <span></span>
+</span>
+<script>
+var XML = "http://www.w3.org/XML/1998/namespace"
+var XMLNS = "http://www.w3.org/2000/xmlns/"
+
+// toggleAttribute exhaustive tests
+// Step 1
+test(function() {
+ var el = document.createElement("foo")
+ for (var i = 0; i < invalid_names.length; i++) {
+ assert_throws_dom("INVALID_CHARACTER_ERR", function() { el.toggleAttribute(invalid_names[i], true) })
+ }
+ for (var i = 0; i < invalid_names.length; i++) {
+ assert_throws_dom("INVALID_CHARACTER_ERR", function() { el.toggleAttribute(invalid_names[i]) })
+ }
+ for (var i = 0; i < invalid_names.length; i++) {
+ assert_throws_dom("INVALID_CHARACTER_ERR", function() { el.toggleAttribute(invalid_names[i], false) })
+ }
+}, "When qualifiedName does not match the Name production, an " +
+ "INVALID_CHARACTER_ERR exception is to be thrown. (toggleAttribute)")
+test(function() {
+ var el = document.getElementById("test2")
+ for (var i = 0; i < el.children.length; i++) {
+ assert_throws_dom("INVALID_CHARACTER_ERR", function() {
+ el.children[i].toggleAttribute("~", false)
+ })
+ }
+ for (var i = 0; i < el.children.length; i++) {
+ assert_throws_dom("INVALID_CHARACTER_ERR", function() {
+ el.children[i].toggleAttribute("~")
+ })
+ }
+ for (var i = 0; i < el.children.length; i++) {
+ assert_throws_dom("INVALID_CHARACTER_ERR", function() {
+ el.children[i].toggleAttribute("~", true)
+ })
+ }
+}, "When qualifiedName does not match the Name production, an " +
+ "INVALID_CHARACTER_ERR exception is to be thrown, even if the attribute " +
+ "is already present. (toggleAttribute)")
+
+// Step 2
+test(function() {
+ var el = document.createElement("div")
+ assert_true(el.toggleAttribute("ALIGN"))
+ assert_true(!el.hasAttributeNS("", "ALIGN"))
+ assert_true(el.hasAttributeNS("", "align"))
+ assert_true(el.hasAttribute("align"))
+ assert_true(!el.toggleAttribute("ALIGN"))
+ assert_true(!el.hasAttributeNS("", "ALIGN"))
+ assert_true(!el.hasAttributeNS("", "align"))
+ assert_true(!el.hasAttribute("align"))
+}, "toggleAttribute should lowercase its name argument (upper case attribute)")
+test(function() {
+ var el = document.createElement("div")
+ assert_true(el.toggleAttribute("CHEEseCaKe"))
+ assert_true(!el.hasAttributeNS("", "CHEEseCaKe"))
+ assert_true(el.hasAttributeNS("", "cheesecake"))
+ assert_true(el.hasAttribute("cheesecake"))
+}, "toggleAttribute should lowercase its name argument (mixed case attribute)")
+
+// Step 3
+test(function() {
+ var el = document.createElement("foo")
+ var tests = ["xmlns", "xmlns:a", "xmlnsx", "xmlns0"]
+ for (var i = 0; i < tests.length; i++) {
+ assert_true(el.toggleAttribute(tests[i]));
+ assert_true(el.hasAttribute(tests[i]));
+ }
+}, "toggleAttribute should not throw even when qualifiedName starts with 'xmlns'")
+
+// Step 4
+test(function() {
+ var el = document.createElement("foo")
+ for (var i = 0; i < valid_names.length; i++) {
+ assert_true(el.toggleAttribute(valid_names[i]))
+ assert_true(el.hasAttribute(valid_names[i]))
+ assert_true(!el.toggleAttribute(valid_names[i]))
+ assert_true(!el.hasAttribute(valid_names[i]))
+ // Check using force attr
+ assert_true(el.toggleAttribute(valid_names[i], true))
+ assert_true(el.hasAttribute(valid_names[i]))
+ assert_true(el.toggleAttribute(valid_names[i], true))
+ assert_true(el.hasAttribute(valid_names[i]))
+ assert_true(!el.toggleAttribute(valid_names[i], false))
+ assert_true(!el.hasAttribute(valid_names[i]))
+ }
+}, "Basic functionality should be intact. (toggleAttribute)")
+
+// Step 5
+test(function() {
+ var el = document.createElement("foo")
+ el.toggleAttribute("a")
+ el.toggleAttribute("b")
+ el.setAttribute("a", "thing")
+ el.toggleAttribute("c")
+ attributes_are(el, [["a", "thing"],
+ ["b", ""],
+ ["c", ""]])
+}, "toggleAttribute should not change the order of previously set attributes.")
+test(function() {
+ var el = document.createElement("baz")
+ el.setAttributeNS("ab", "attr", "fail")
+ el.setAttributeNS("kl", "attr", "pass")
+ el.toggleAttribute("attr")
+ attributes_are(el, [["attr", "pass", "kl"]])
+}, "toggleAttribute should set the first attribute with the given name")
+test(function() {
+ // Based on a test by David Flanagan.
+ var el = document.createElement("baz")
+ el.setAttributeNS("foo", "foo:bar", "1");
+ el.setAttributeNS("foo", "foo:bat", "2");
+ assert_equals(el.getAttribute("foo:bar"), "1")
+ assert_equals(el.getAttribute("foo:bat"), "2")
+ attr_is(el.attributes[0], "1", "bar", "foo", "foo", "foo:bar")
+ attr_is(el.attributes[1], "2", "bat", "foo", "foo", "foo:bat")
+ el.toggleAttribute("foo:bar");
+ assert_true(!el.hasAttribute("foo:bar"))
+ attr_is(el.attributes[0], "2", "bat", "foo", "foo", "foo:bat")
+}, "toggleAttribute should set the attribute with the given qualified name")
+
+test(function() {
+ var el = document.createElement("foo")
+ el.style = "color: red; background-color: green"
+ assert_equals(el.toggleAttribute("style"), false)
+}, "Toggling element with inline style should make inline style disappear")
+
+// setAttribute exhaustive tests
+// Step 1
+test(function() {
+ var el = document.createElement("foo")
+ for (var i = 0; i < invalid_names.length; i++) {
+ assert_throws_dom("INVALID_CHARACTER_ERR", function() { el.setAttribute(invalid_names[i], "test") })
+ }
+}, "When qualifiedName does not match the Name production, an " +
+ "INVALID_CHARACTER_ERR exception is to be thrown. (setAttribute)")
+test(function() {
+ var el = document.getElementById("test2")
+ for (var i = 0; i < el.children.length; i++) {
+ assert_throws_dom("INVALID_CHARACTER_ERR", function() {
+ el.children[i].setAttribute("~", "test")
+ })
+ }
+}, "When qualifiedName does not match the Name production, an " +
+ "INVALID_CHARACTER_ERR exception is to be thrown, even if the attribute " +
+ "is already present. (setAttribute)")
+
+// Step 2
+test(function() {
+ var el = document.createElement("div")
+ el.setAttribute("ALIGN", "left")
+ assert_equals(el.getAttributeNS("", "ALIGN"), null)
+ assert_equals(el.getAttributeNS("", "align"), "left")
+ assert_equals(el.getAttribute("align"), "left")
+}, "setAttribute should lowercase its name argument (upper case attribute)")
+test(function() {
+ var el = document.createElement("div")
+ el.setAttribute("CHEEseCaKe", "tasty")
+ assert_equals(el.getAttributeNS("", "CHEEseCaKe"), null)
+ assert_equals(el.getAttributeNS("", "cheesecake"), "tasty")
+ assert_equals(el.getAttribute("cheesecake"), "tasty")
+}, "setAttribute should lowercase its name argument (mixed case attribute)")
+
+// Step 3
+test(function() {
+ var el = document.createElement("foo")
+ var tests = ["xmlns", "xmlns:a", "xmlnsx", "xmlns0"]
+ for (var i = 0; i < tests.length; i++) {
+ el.setAttribute(tests[i], "success");
+ }
+}, "setAttribute should not throw even when qualifiedName starts with 'xmlns'")
+
+// Step 4
+test(function() {
+ var el = document.createElement("foo")
+ for (var i = 0; i < valid_names.length; i++) {
+ el.setAttribute(valid_names[i], "test")
+ assert_equals(el.getAttribute(valid_names[i]), "test")
+ }
+}, "Basic functionality should be intact.")
+
+// Step 5
+test(function() {
+ var el = document.createElement("foo")
+ el.setAttribute("a", "1")
+ el.setAttribute("b", "2")
+ el.setAttribute("a", "3")
+ el.setAttribute("c", "4")
+ attributes_are(el, [["a", "3"],
+ ["b", "2"],
+ ["c", "4"]])
+}, "setAttribute should not change the order of previously set attributes.")
+test(function() {
+ var el = document.createElement("baz")
+ el.setAttributeNS("ab", "attr", "fail")
+ el.setAttributeNS("kl", "attr", "pass")
+ el.setAttribute("attr", "pass")
+ attributes_are(el, [["attr", "pass", "ab"],
+ ["attr", "pass", "kl"]])
+}, "setAttribute should set the first attribute with the given name")
+test(function() {
+ // Based on a test by David Flanagan.
+ var el = document.createElement("baz")
+ el.setAttributeNS("foo", "foo:bar", "1");
+ assert_equals(el.getAttribute("foo:bar"), "1")
+ attr_is(el.attributes[0], "1", "bar", "foo", "foo", "foo:bar")
+ el.setAttribute("foo:bar", "2");
+ assert_equals(el.getAttribute("foo:bar"), "2")
+ attr_is(el.attributes[0], "2", "bar", "foo", "foo", "foo:bar")
+}, "setAttribute should set the attribute with the given qualified name")
+
+// setAttributeNS exhaustive tests
+// Step 1
+test(function() {
+ var el = document.createElement("foo")
+ for (var i = 0, il = invalid_names.length; i < il; ++i) {
+ assert_throws_dom("INVALID_CHARACTER_ERR",
+ function() { el.setAttributeNS("a", invalid_names[i], "fail") })
+ }
+}, "When qualifiedName does not match the Name production, an " +
+ "INVALID_CHARACTER_ERR exception is to be thrown. (setAttributeNS)")
+
+test(function() {
+ var el = document.getElementById("test2")
+ for (var i = 0; i < el.children.length; i++) {
+ assert_throws_dom("INVALID_CHARACTER_ERR", function() {
+ el.children[i].setAttributeNS(null, "~", "test")
+ })
+ }
+}, "When qualifiedName does not match the Name production, an " +
+ "INVALID_CHARACTER_ERR exception is to be thrown, even if the attribute " +
+ "is already present. (setAttributeNS)")
+
+// Step 2
+test(function() {
+ var el = document.createElement("foo")
+ for (var i = 0, il = invalid_qnames.length; i < il; ++i) {
+ assert_throws_dom("INVALID_CHARACTER_ERR",
+ function() { el.setAttributeNS("a", invalid_qnames[i], "fail") },
+ "Expected exception for " + invalid_qnames[i] + ".")
+ }
+}, "When qualifiedName does not match the QName production, an " +
+ "INVALID_CHARACTER_ERR exception is to be thrown.")
+
+// Step 3
+test(function() {
+ var el = document.createElement("foo")
+ el.setAttributeNS(null, "aa", "bb")
+ el.setAttributeNS("", "xx", "bb")
+ attributes_are(el, [["aa", "bb"],
+ ["xx", "bb"]])
+}, "null and the empty string should result in a null namespace.")
+
+// Step 4
+test(function() {
+ var el = document.createElement("foo")
+ assert_throws_dom("NAMESPACE_ERR",
+ function() { el.setAttributeNS("", "aa:bb", "fail") })
+ assert_throws_dom("NAMESPACE_ERR",
+ function() { el.setAttributeNS(null, "aa:bb", "fail") })
+}, "A namespace is required to use a prefix.")
+
+// Step 5
+test(function() {
+ var el = document.createElement("foo")
+ assert_throws_dom("NAMESPACE_ERR",
+ function() { el.setAttributeNS("a", "xml:bb", "fail") })
+}, "The xml prefix should not be allowed for arbitrary namespaces")
+test(function() {
+ var el = document.createElement("foo")
+ el.setAttributeNS(XML, "a:bb", "pass")
+ assert_equals(el.attributes.length, 1)
+ attr_is(el.attributes[0], "pass", "bb", XML, "a", "a:bb")
+}, "XML-namespaced attributes don't need an xml prefix")
+
+// Step 6
+test(function() {
+ var el = document.createElement("foo")
+ assert_throws_dom("NAMESPACE_ERR",
+ function() { el.setAttributeNS("a", "xmlns:bb", "fail") })
+}, "The xmlns prefix should not be allowed for arbitrary namespaces")
+test(function() {
+ var el = document.createElement("foo")
+ assert_throws_dom("NAMESPACE_ERR",
+ function() { el.setAttributeNS("a", "xmlns", "fail") })
+}, "The xmlns qualified name should not be allowed for arbitrary namespaces")
+test(function() {
+ var el = document.createElement("foo")
+ el.setAttributeNS("ns", "a:xmlns", "pass")
+ assert_equals(el.attributes.length, 1)
+ attr_is(el.attributes[0], "pass", "xmlns", "ns", "a", "a:xmlns")
+}, "xmlns should be allowed as local name")
+
+// Step 7
+test(function() {
+ var el = document.createElement("foo")
+ assert_throws_dom("NAMESPACE_ERR",
+ function() { el.setAttributeNS(XMLNS, "a:xmlns", "fail") })
+ assert_throws_dom("NAMESPACE_ERR",
+ function() { el.setAttributeNS(XMLNS, "b:foo", "fail") })
+}, "The XMLNS namespace should require xmlns as prefix or qualified name")
+test(function() {
+ var el = document.createElement("foo")
+ el.setAttributeNS(XMLNS, "xmlns:a", "pass")
+ assert_equals(el.attributes.length, 1)
+ attr_is(el.attributes[0], "pass", "a", XMLNS, "xmlns", "xmlns:a")
+}, "xmlns should be allowed as prefix in the XMLNS namespace")
+test(function() {
+ var el = document.createElement("foo")
+ el.setAttributeNS(XMLNS, "xmlns", "pass")
+ assert_equals(el.attributes.length, 1)
+ attr_is(el.attributes[0], "pass", "xmlns", XMLNS, null, "xmlns")
+}, "xmlns should be allowed as qualified name in the XMLNS namespace")
+
+// Step 8-9
+test(function() {
+ var el = document.createElement("foo")
+ el.setAttributeNS("a", "foo:bar", "X")
+ assert_equals(el.attributes.length, 1)
+ attr_is(el.attributes[0], "X", "bar", "a", "foo", "foo:bar")
+
+ el.setAttributeNS("a", "quux:bar", "Y")
+ assert_equals(el.attributes.length, 1)
+ attr_is(el.attributes[0], "Y", "bar", "a", "foo", "foo:bar")
+ el.removeAttributeNS("a", "bar")
+}, "Setting the same attribute with another prefix should not change the prefix")
+
+// Miscellaneous tests
+test(function() {
+ var el = document.createElement("iframe")
+ el.setAttribute("src", "file:///home")
+ assert_equals(el.getAttribute("src"), "file:///home")
+}, "setAttribute should not throw even if a load is not allowed")
+test(function() {
+ var docFragment = document.createDocumentFragment()
+ var newOne = document.createElement("newElement")
+ newOne.setAttribute("newdomestic", "Yes")
+ docFragment.appendChild(newOne)
+ var domesticNode = docFragment.firstChild
+ var attr = domesticNode.attributes.item(0)
+ attr_is(attr, "Yes", "newdomestic", null, null, "newdomestic")
+}, "Attributes should work in document fragments.")
+test(function() {
+ var el = document.createElement("foo")
+ el.setAttribute("x", "y")
+ var attr = el.attributes[0]
+ attr.value = "Y&lt;"
+ attr_is(attr, "Y&lt;", "x", null, null, "x")
+ assert_equals(el.getAttribute("x"), "Y&lt;")
+}, "Attribute values should not be parsed.")
+test(function() {
+ var el = document.getElementsByTagName("span")[0]
+ attr_is(el.attributes[0], "test1", "id", null, null, "id")
+}, "Specified attributes should be accessible.")
+test(function() {
+ var el = document.getElementsByTagName("span")[1]
+ attr_is(el.attributes[0], "&<>foo", "class", null, null, "class")
+}, "Entities in attributes should have been expanded while parsing.")
+
+test(function() {
+ var el = document.createElement("div")
+ assert_equals(el.hasAttribute("bar"), false)
+ assert_equals(el.hasAttributeNS(null, "bar"), false)
+ assert_equals(el.hasAttributeNS("", "bar"), false)
+ assert_equals(el.getAttribute("bar"), null)
+ assert_equals(el.getAttributeNS(null, "bar"), null)
+ assert_equals(el.getAttributeNS("", "bar"), null)
+}, "Unset attributes return null")
+test(function() {
+ var el = document.createElement("div")
+ el.setAttributeNS("ab", "attr", "t1")
+ el.setAttributeNS("kl", "attr", "t2")
+ assert_equals(el.hasAttribute("attr"), true)
+ assert_equals(el.hasAttributeNS("ab", "attr"), true)
+ assert_equals(el.hasAttributeNS("kl", "attr"), true)
+ assert_equals(el.getAttribute("attr"), "t1")
+ assert_equals(el.getAttributeNS("ab", "attr"), "t1")
+ assert_equals(el.getAttributeNS("kl", "attr"), "t2")
+ assert_equals(el.getAttributeNS(null, "attr"), null)
+ assert_equals(el.getAttributeNS("", "attr"), null)
+}, "First set attribute is returned by getAttribute")
+test(function() {
+ var el = document.createElement("div")
+ el.setAttribute("style", "color:#fff;")
+ assert_equals(el.hasAttribute("style"), true)
+ assert_equals(el.hasAttributeNS(null, "style"), true)
+ assert_equals(el.hasAttributeNS("", "style"), true)
+ assert_equals(el.getAttribute("style"), "color:#fff;")
+ assert_equals(el.getAttributeNS(null, "style"), "color:#fff;")
+ assert_equals(el.getAttributeNS("", "style"), "color:#fff;")
+}, "Style attributes are not normalized")
+test(function() {
+ var el = document.createElement("div")
+ el.setAttributeNS("", "ALIGN", "left")
+ assert_equals(el.hasAttribute("ALIGN"), false)
+ assert_equals(el.hasAttribute("align"), false)
+ assert_equals(el.hasAttributeNS(null, "ALIGN"), true)
+ assert_equals(el.hasAttributeNS(null, "align"), false)
+ assert_equals(el.hasAttributeNS("", "ALIGN"), true)
+ assert_equals(el.hasAttributeNS("", "align"), false)
+ assert_equals(el.getAttribute("ALIGN"), null)
+ assert_equals(el.getAttribute("align"), null)
+ assert_equals(el.getAttributeNS(null, "ALIGN"), "left")
+ assert_equals(el.getAttributeNS("", "ALIGN"), "left")
+ assert_equals(el.getAttributeNS(null, "align"), null)
+ assert_equals(el.getAttributeNS("", "align"), null)
+ el.removeAttributeNS("", "ALIGN")
+}, "Only lowercase attributes are returned on HTML elements (upper case attribute)")
+test(function() {
+ var el = document.createElement("div")
+ el.setAttributeNS("", "CHEEseCaKe", "tasty")
+ assert_equals(el.hasAttribute("CHEESECAKE"), false)
+ assert_equals(el.hasAttribute("CHEEseCaKe"), false)
+ assert_equals(el.hasAttribute("cheesecake"), false)
+ assert_equals(el.hasAttributeNS("", "CHEESECAKE"), false)
+ assert_equals(el.hasAttributeNS("", "CHEEseCaKe"), true)
+ assert_equals(el.hasAttributeNS("", "cheesecake"), false)
+ assert_equals(el.hasAttributeNS(null, "CHEESECAKE"), false)
+ assert_equals(el.hasAttributeNS(null, "CHEEseCaKe"), true)
+ assert_equals(el.hasAttributeNS(null, "cheesecake"), false)
+ assert_equals(el.getAttribute("CHEESECAKE"), null)
+ assert_equals(el.getAttribute("CHEEseCaKe"), null)
+ assert_equals(el.getAttribute("cheesecake"), null)
+ assert_equals(el.getAttributeNS(null, "CHEESECAKE"), null)
+ assert_equals(el.getAttributeNS("", "CHEESECAKE"), null)
+ assert_equals(el.getAttributeNS(null, "CHEEseCaKe"), "tasty")
+ assert_equals(el.getAttributeNS("", "CHEEseCaKe"), "tasty")
+ assert_equals(el.getAttributeNS(null, "cheesecake"), null)
+ assert_equals(el.getAttributeNS("", "cheesecake"), null)
+ el.removeAttributeNS("", "CHEEseCaKe")
+}, "Only lowercase attributes are returned on HTML elements (mixed case attribute)")
+test(function() {
+ var el = document.createElement("div")
+ document.body.appendChild(el)
+ el.setAttributeNS("", "align", "left")
+ el.setAttributeNS("xx", "align", "right")
+ el.setAttributeNS("", "foo", "left")
+ el.setAttributeNS("xx", "foo", "right")
+ assert_equals(el.hasAttribute("align"), true)
+ assert_equals(el.hasAttribute("foo"), true)
+ assert_equals(el.hasAttributeNS("xx", "align"), true)
+ assert_equals(el.hasAttributeNS(null, "foo"), true)
+ assert_equals(el.getAttribute("align"), "left")
+ assert_equals(el.getAttribute("foo"), "left")
+ assert_equals(el.getAttributeNS("xx", "align"), "right")
+ assert_equals(el.getAttributeNS(null, "foo"), "left")
+ assert_equals(el.getAttributeNS("", "foo"), "left")
+ el.removeAttributeNS("", "align")
+ el.removeAttributeNS("xx", "align")
+ el.removeAttributeNS("", "foo")
+ el.removeAttributeNS("xx", "foo")
+ document.body.removeChild(el)
+}, "First set attribute is returned with mapped attribute set first")
+test(function() {
+ var el = document.createElement("div")
+ el.setAttributeNS("xx", "align", "right")
+ el.setAttributeNS("", "align", "left")
+ el.setAttributeNS("xx", "foo", "right")
+ el.setAttributeNS("", "foo", "left")
+ assert_equals(el.hasAttribute("align"), true)
+ assert_equals(el.hasAttribute("foo"), true)
+ assert_equals(el.hasAttributeNS("xx", "align"), true)
+ assert_equals(el.hasAttributeNS(null, "foo"), true)
+ assert_equals(el.getAttribute("align"), "right")
+ assert_equals(el.getAttribute("foo"), "right")
+ assert_equals(el.getAttributeNS("xx", "align"), "right")
+ assert_equals(el.getAttributeNS(null, "foo"), "left")
+ assert_equals(el.getAttributeNS("", "foo"), "left")
+ el.removeAttributeNS("", "align")
+ el.removeAttributeNS("xx", "align")
+ el.removeAttributeNS("", "foo")
+ el.removeAttributeNS("xx", "foo")
+}, "First set attribute is returned with mapped attribute set later")
+
+test(function() {
+ var el = document.createElementNS("http://www.example.com", "foo")
+ el.setAttribute("A", "test")
+ assert_equals(el.hasAttribute("A"), true, "hasAttribute()")
+ assert_equals(el.hasAttributeNS("", "A"), true, "el.hasAttributeNS(\"\")")
+ assert_equals(el.hasAttributeNS(null, "A"), true, "el.hasAttributeNS(null)")
+ assert_equals(el.hasAttributeNS(undefined, "A"), true, "el.hasAttributeNS(undefined)")
+ assert_equals(el.hasAttributeNS("foo", "A"), false, "el.hasAttributeNS(\"foo\")")
+
+ assert_equals(el.getAttribute("A"), "test", "getAttribute()")
+ assert_equals(el.getAttributeNS("", "A"), "test", "el.getAttributeNS(\"\")")
+ assert_equals(el.getAttributeNS(null, "A"), "test", "el.getAttributeNS(null)")
+ assert_equals(el.getAttributeNS(undefined, "A"), "test", "el.getAttributeNS(undefined)")
+ assert_equals(el.getAttributeNS("foo", "A"), null, "el.getAttributeNS(\"foo\")")
+}, "Non-HTML element with upper-case attribute")
+
+test(function() {
+ var el = document.createElement("div")
+ el.setAttribute("pre:fix", "value 1")
+ el.setAttribute("fix", "value 2")
+
+ var prefixed = el.attributes[0]
+ assert_equals(prefixed.localName, "pre:fix", "prefixed local name")
+ assert_equals(prefixed.namespaceURI, null, "prefixed namespace")
+
+ var unprefixed = el.attributes[1]
+ assert_equals(unprefixed.localName, "fix", "unprefixed local name")
+ assert_equals(unprefixed.namespaceURI, null, "unprefixed namespace")
+
+ el.removeAttributeNS(null, "pre:fix")
+ assert_equals(el.attributes[0], unprefixed)
+}, "Attribute with prefix in local name")
+
+test(function() {
+ var el = document.createElement("div")
+ el.setAttribute("foo", "bar")
+ var attr = el.attributes[0]
+ assert_equals(attr.ownerElement, el)
+ el.removeAttribute("foo")
+ assert_equals(attr.ownerElement, null)
+}, "Attribute loses its owner when removed")
+
+test(function() {
+ var el = document.createElement("div")
+ el.setAttribute("foo", "bar")
+ var attr = el.attributes[0]
+ var attrNode = el.getAttributeNode("foo");
+ var attrNodeNS = el.getAttributeNodeNS("", "foo");
+ assert_equals(attr, attrNode);
+ assert_equals(attr, attrNodeNS);
+ el.setAttributeNS("x", "foo2", "bar2");
+ var attr2 = el.attributes[1];
+ var attrNodeNS2 = el.getAttributeNodeNS("x", "foo2");
+ assert_equals(attr2, attrNodeNS2);
+}, "Basic functionality of getAttributeNode/getAttributeNodeNS")
+
+test(function() {
+ var el = document.createElement("div")
+ el.setAttribute("foo", "bar")
+ var attrNode = el.getAttributeNode("foo");
+ var attrNodeNS = el.getAttributeNodeNS("", "foo");
+ assert_equals(attrNode, attrNodeNS);
+ el.removeAttribute("foo");
+ var el2 = document.createElement("div");
+ el2.setAttributeNode(attrNode);
+ assert_equals(attrNode, el2.getAttributeNode("foo"));
+ assert_equals(attrNode, el2.attributes[0]);
+ assert_equals(attrNode.ownerElement, el2);
+ assert_equals(attrNode.value, "bar");
+
+ var el3 = document.createElement("div");
+ el2.removeAttribute("foo");
+ el3.setAttribute("foo", "baz");
+ el3.setAttributeNode(attrNode);
+ assert_equals(el3.getAttribute("foo"), "bar");
+}, "Basic functionality of setAttributeNode")
+
+test(function() {
+ var el = document.createElement("div");
+ var attr1 = document.createAttributeNS("ns1", "p1:name");
+ attr1.value = "value1";
+ var attr2 = document.createAttributeNS("ns2", "p2:name");
+ attr2.value = "value2";
+ el.setAttributeNode(attr1);
+ el.setAttributeNode(attr2);
+ assert_equals(el.getAttributeNodeNS("ns1", "name").value, "value1");
+ assert_equals(el.getAttributeNodeNS("ns2", "name").value, "value2");
+}, "setAttributeNode should distinguish attributes with same local name and different namespaces")
+
+test(function() {
+ var el = document.createElement("div");
+ var attr1 = document.createAttributeNS("ns1", "p1:name");
+ attr1.value = "value1";
+ var attr2 = document.createAttributeNS("ns1", "p1:NAME");
+ attr2.value = "VALUE2";
+ el.setAttributeNode(attr1);
+ el.setAttributeNode(attr2);
+ assert_equals(el.getAttributeNodeNS("ns1", "name").value, "value1");
+ assert_equals(el.getAttributeNodeNS("ns1", "NAME").value, "VALUE2");
+}, "setAttributeNode doesn't have case-insensitivity even with an HTMLElement")
+
+test(function() {
+ var el = document.createElement("div")
+ el.setAttributeNS("x", "foo", "bar")
+ var attrNode = el.getAttributeNodeNS("x", "foo");
+ el.removeAttribute("foo");
+ var el2 = document.createElement("div");
+ el2.setAttributeNS("x", "foo", "baz");
+ el2.setAttributeNodeNS(attrNode);
+ assert_equals(el2.getAttributeNS("x", "foo"), "bar");
+}, "Basic functionality of setAttributeNodeNS")
+
+test(function() {
+ var el = document.createElement("div");
+ var other = document.createElement("div");
+ var attr = document.createAttribute("foo");
+ assert_equals(el.setAttributeNode(attr), null);
+ assert_equals(attr.ownerElement, el);
+ assert_throws_dom("INUSE_ATTRIBUTE_ERR",
+ function() { other.setAttributeNode(attr) },
+ "Attribute already associated with el")
+}, "If attr’s element is neither null nor element, throw an InUseAttributeError.");
+
+test(function() {
+ var el = document.createElement("div");
+ var attr = document.createAttribute("foo");
+ assert_equals(el.setAttributeNode(attr), null);
+ el.setAttribute("bar", "qux");
+ assert_equals(el.setAttributeNode(attr), attr);
+ assert_equals(el.attributes[0], attr);
+}, "Replacing an attr by itself");
+
+test(function() {
+ var el = document.createElement("div")
+ el.setAttribute("foo", "bar")
+ var attrNode = el.getAttributeNode("foo");
+ el.removeAttributeNode(attrNode);
+ var el2 = document.createElement("div");
+ el2.setAttributeNode(attrNode);
+ assert_equals(el2.attributes[0], attrNode);
+ assert_equals(el.attributes.length, 0);
+}, "Basic functionality of removeAttributeNode")
+
+test(function() {
+ var el = document.createElement("div")
+ el.setAttribute("foo", "bar")
+ var attrNode = el.getAttributeNode("foo");
+ var el2 = document.createElement("div");
+ assert_throws_dom("INUSE_ATTRIBUTE_ERR", function(){el2.setAttributeNode(attrNode)});
+}, "setAttributeNode on bound attribute should throw InUseAttributeError")
+
+// Have to use an async_test to see what a DOMAttrModified listener sees,
+// because otherwise the event dispatch code will swallow our exceptions. And
+// we want to make sure this test always happens, even when no mutation events
+// run.
+var setAttributeNode_mutation_test = async_test("setAttributeNode, if it fires mutation events, should fire one with the new node when resetting an existing attribute");
+
+test(function(){
+ var el = document.createElement("div")
+ var attrNode1 = document.createAttribute("foo");
+ attrNode1.value = "bar";
+ el.setAttributeNode(attrNode1);
+ var attrNode2 = document.createAttribute("foo");
+ attrNode2.value = "baz";
+
+ el.addEventListener("DOMAttrModified", function(e) {
+ // If this never gets called, that's OK, I guess. But if it gets called, it
+ // better represent a single modification with attrNode2 as the relatedNode.
+ // We have to do an inner test() call here, because otherwise the exceptions
+ // our asserts trigger will get swallowed by the event dispatch code.
+ setAttributeNode_mutation_test.step(function() {
+ assert_equals(e.attrName, "foo");
+ assert_equals(e.attrChange, MutationEvent.MODIFICATION);
+ assert_equals(e.prevValue, "bar");
+ assert_equals(e.newValue, "baz");
+ assert_equals(e.relatedNode, attrNode2);
+ });
+ });
+
+ var oldNode = el.setAttributeNode(attrNode2);
+ assert_equals(oldNode, attrNode1,
+ "Must return the old attr node from a setAttributeNode call");
+}, "setAttributeNode, if it fires mutation events, should fire one with the new node when resetting an existing attribute (outer shell)");
+setAttributeNode_mutation_test.done();
+
+test(function(){
+ var el = document.createElement("div")
+ el.setAttribute("a", "b");
+ el.setAttribute("c", "d");
+
+ assert_array_equals(Array.prototype.map.call(el.attributes, function(a) { return a.name }),
+ ["a", "c"]);
+ assert_array_equals(Array.prototype.map.call(el.attributes, function(a) { return a.value }),
+ ["b", "d"]);
+
+ var attrNode = document.createAttribute("a");
+ attrNode.value = "e";
+ el.setAttributeNode(attrNode);
+
+ assert_array_equals(Array.prototype.map.call(el.attributes, function(a) { return a.name }),
+ ["a", "c"]);
+ assert_array_equals(Array.prototype.map.call(el.attributes, function(a) { return a.value }),
+ ["e", "d"]);
+}, "setAttributeNode called with an Attr that has the same name as an existing one should not change attribute order");
+
+test(function() {
+ var el = document.createElement("div");
+ el.setAttribute("foo", "bar");
+ assert_equals(el.getAttributeNames().length, 1);
+ assert_equals(el.getAttributeNames()[0], el.attributes[0].name);
+ assert_equals(el.getAttributeNames()[0], "foo");
+
+ el.removeAttribute("foo");
+ assert_equals(el.getAttributeNames().length, 0);
+
+ el.setAttribute("foo", "bar");
+ el.setAttributeNS("", "FOO", "bar");
+ el.setAttributeNS("dummy1", "foo", "bar");
+ el.setAttributeNS("dummy2", "dummy:foo", "bar");
+ assert_equals(el.getAttributeNames().length, 4);
+ assert_equals(el.getAttributeNames()[0], "foo");
+ assert_equals(el.getAttributeNames()[1], "FOO");
+ assert_equals(el.getAttributeNames()[2], "foo");
+ assert_equals(el.getAttributeNames()[3], "dummy:foo");
+ assert_equals(el.getAttributeNames()[0], el.attributes[0].name);
+ assert_equals(el.getAttributeNames()[1], el.attributes[1].name);
+ assert_equals(el.getAttributeNames()[2], el.attributes[2].name);
+ assert_equals(el.getAttributeNames()[3], el.attributes[3].name);
+
+ el.removeAttributeNS("", "FOO");
+ assert_equals(el.getAttributeNames().length, 3);
+ assert_equals(el.getAttributeNames()[0], "foo");
+ assert_equals(el.getAttributeNames()[1], "foo");
+ assert_equals(el.getAttributeNames()[2], "dummy:foo");
+ assert_equals(el.getAttributeNames()[0], el.attributes[0].name);
+ assert_equals(el.getAttributeNames()[1], el.attributes[1].name);
+ assert_equals(el.getAttributeNames()[2], el.attributes[2].name);
+}, "getAttributeNames tests");
+
+function getEnumerableOwnProps1(obj) {
+ var arr = [];
+ for (var prop in obj) {
+ if (obj.hasOwnProperty(prop)) {
+ arr.push(prop);
+ }
+ }
+ return arr;
+}
+
+function getEnumerableOwnProps2(obj) {
+ return Object.getOwnPropertyNames(obj).filter(
+ function (name) { return Object.getOwnPropertyDescriptor(obj, name).enumerable; })
+}
+
+test(function() {
+ var el = document.createElement("div");
+ el.setAttribute("a", "");
+ el.setAttribute("b", "");
+ assert_array_equals(getEnumerableOwnProps1(el.attributes),
+ ["0", "1"])
+ assert_array_equals(getEnumerableOwnProps2(el.attributes),
+ ["0", "1"])
+ assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+ ["0", "1", "a", "b"])
+}, "Own property correctness with basic attributes");
+
+test(function() {
+ var el = document.createElement("div");
+ el.setAttributeNS("", "a", "");
+ el.setAttribute("b", "");
+ el.setAttributeNS("foo", "a", "");
+ assert_array_equals(getEnumerableOwnProps1(el.attributes),
+ ["0", "1", "2"])
+ assert_array_equals(getEnumerableOwnProps2(el.attributes),
+ ["0", "1", "2"])
+ assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+ ["0", "1", "2", "a", "b"])
+ for (var propName of Object.getOwnPropertyNames(el.attributes)) {
+ assert_true(el.attributes[propName] instanceof Attr,
+ "el.attributes has an Attr for property name " + propName);
+ }
+}, "Own property correctness with non-namespaced attribute before same-name namespaced one");
+
+test(function() {
+ var el = document.createElement("div");
+ el.setAttributeNS("foo", "a", "");
+ el.setAttribute("b", "");
+ el.setAttributeNS("", "a", "");
+ assert_array_equals(getEnumerableOwnProps1(el.attributes),
+ ["0", "1", "2"])
+ assert_array_equals(getEnumerableOwnProps2(el.attributes),
+ ["0", "1", "2"])
+ assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+ ["0", "1", "2", "a", "b"])
+ for (var propName of Object.getOwnPropertyNames(el.attributes)) {
+ assert_true(el.attributes[propName] instanceof Attr,
+ "el.attributes has an Attr for property name " + propName);
+ }
+}, "Own property correctness with namespaced attribute before same-name non-namespaced one");
+
+test(function() {
+ var el = document.createElement("div");
+ el.setAttributeNS("foo", "a:b", "");
+ el.setAttributeNS("foo", "c:d", "");
+ el.setAttributeNS("bar", "a:b", "");
+ assert_array_equals(getEnumerableOwnProps1(el.attributes),
+ ["0", "1", "2"])
+ assert_array_equals(getEnumerableOwnProps2(el.attributes),
+ ["0", "1", "2"])
+ assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+ ["0", "1", "2", "a:b", "c:d"])
+ for (var propName of Object.getOwnPropertyNames(el.attributes)) {
+ assert_true(el.attributes[propName] instanceof Attr,
+ "el.attributes has an Attr for property name " + propName);
+ }
+}, "Own property correctness with two namespaced attributes with the same name-with-prefix");
+
+test(function() {
+ var el = document.createElement("div");
+ el.setAttributeNS("foo", "A:B", "");
+ el.setAttributeNS("bar", "c:D", "");
+ el.setAttributeNS("baz", "e:F", "");
+ el.setAttributeNS("qux", "g:h", "");
+ el.setAttributeNS("", "I", "");
+ el.setAttributeNS("", "j", "");
+ assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+ ["0", "1", "2", "3", "4", "5", "g:h", "j"])
+ for (var propName of Object.getOwnPropertyNames(el.attributes)) {
+ assert_true(el.attributes[propName] instanceof Attr,
+ "el.attributes has an Attr for property name " + propName);
+ }
+}, "Own property names should only include all-lowercase qualified names for an HTML element in an HTML document");
+
+test(function() {
+ var el = document.createElementNS("", "div");
+ el.setAttributeNS("foo", "A:B", "");
+ el.setAttributeNS("bar", "c:D", "");
+ el.setAttributeNS("baz", "e:F", "");
+ el.setAttributeNS("qux", "g:h", "");
+ el.setAttributeNS("", "I", "");
+ el.setAttributeNS("", "j", "");
+ assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+ ["0", "1", "2", "3", "4", "5", "A:B", "c:D", "e:F", "g:h", "I", "j"])
+ for (var propName of Object.getOwnPropertyNames(el.attributes)) {
+ assert_true(el.attributes[propName] instanceof Attr,
+ "el.attributes has an Attr for property name " + propName);
+ }
+}, "Own property names should include all qualified names for a non-HTML element in an HTML document");
+
+test(function() {
+ var doc = document.implementation.createDocument(null, "");
+ assert_equals(doc.contentType, "application/xml");
+ var el = doc.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ el.setAttributeNS("foo", "A:B", "");
+ el.setAttributeNS("bar", "c:D", "");
+ el.setAttributeNS("baz", "e:F", "");
+ el.setAttributeNS("qux", "g:h", "");
+ el.setAttributeNS("", "I", "");
+ el.setAttributeNS("", "j", "");
+ assert_array_equals(Object.getOwnPropertyNames(el.attributes),
+ ["0", "1", "2", "3", "4", "5", "A:B", "c:D", "e:F", "g:h", "I", "j"])
+ for (var propName of Object.getOwnPropertyNames(el.attributes)) {
+ assert_true(el.attributes[propName] instanceof Attr,
+ "el.attributes has an Attr for property name " + propName);
+ }
+}, "Own property names should include all qualified names for an HTML element in a non-HTML document");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/attributes.js b/testing/web-platform/tests/dom/nodes/attributes.js
new file mode 100644
index 0000000000..ef32bf6a67
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/attributes.js
@@ -0,0 +1,18 @@
+function attr_is(attr, v, ln, ns, p, n) {
+ assert_equals(attr.value, v)
+ assert_equals(attr.nodeValue, v)
+ assert_equals(attr.textContent, v)
+ assert_equals(attr.localName, ln)
+ assert_equals(attr.namespaceURI, ns)
+ assert_equals(attr.prefix, p)
+ assert_equals(attr.name, n)
+ assert_equals(attr.nodeName, n);
+ assert_equals(attr.specified, true)
+}
+
+function attributes_are(el, l) {
+ for (var i = 0, il = l.length; i < il; i++) {
+ attr_is(el.attributes[i], l[i][1], l[i][0], (l[i].length < 3) ? null : l[i][2], null, l[i][0])
+ assert_equals(el.attributes[i].ownerElement, el)
+ }
+}
diff --git a/testing/web-platform/tests/dom/nodes/case.html b/testing/web-platform/tests/dom/nodes/case.html
new file mode 100644
index 0000000000..c3c195141c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/case.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Tests for case-sensitivity in APIs</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-createelement">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-createelementns">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-getelementsbytagname">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-getelementsbytagnamens">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-setattribute">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-setattributens">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-hasattribute">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-hasattributens">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-getelementsbytagname">
+<link rel=help href="https://dom.spec.whatwg.org/#dom-element-getelementsbytagnamens">
+<script>var is_html = true;</script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="case.js"></script>
+<div id="log"></div>
diff --git a/testing/web-platform/tests/dom/nodes/case.js b/testing/web-platform/tests/dom/nodes/case.js
new file mode 100644
index 0000000000..8c2da4a44a
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/case.js
@@ -0,0 +1,186 @@
+/*
+ * document.createElement(NS)
+ *
+ * document.getElementsByTagName(NS)
+ *
+ * Element.setAttribute(NS)
+ *
+ * Element.getAttribute(NS)
+ * Element.hasAttribute(NS)
+ * Element.getElementsByTagName(NS)
+ */
+
+var tests = [];
+setup(function() {
+ var name_inputs = ["abc", "Abc", "ABC", "ä", "Ä"];
+ var namespaces = ["http://www.w3.org/1999/xhtml", "http://www.w3.org/2000/svg", "http://FOO"];
+ name_inputs.forEach(function(x) {
+ tests.push(["createElement " + x, test_create_element, [x]]);
+ tests.push(["setAttribute " +x, test_set_attribute, [x]]);
+ tests.push(["getAttribute " +x, test_get_attribute, [x]]);
+ tests.push(["getElementsByTagName a:" +x, test_get_elements_tag_name,
+ [outer_product(namespaces, ["a"], name_inputs),
+ x]]);
+ tests.push(["getElementsByTagName " +x, test_get_elements_tag_name,
+ [outer_product(namespaces, [null], name_inputs),
+ x]]);
+ });
+ outer_product(namespaces, name_inputs, name_inputs).forEach(function(x) {
+ tests.push(["createElementNS " + x, test_create_element_ns, x]);
+ tests.push(["setAttributeNS " + x, test_set_attribute_ns, x]);
+ tests.push(["getAttributeNS " + x, test_get_attribute_ns, x]);
+ });
+ outer_product([null].concat(namespaces), name_inputs).forEach(function(x) {
+ tests.push(["getElementsByTagNameNS " + x, test_get_elements_tag_name_ns,
+ outer_product(namespaces, name_inputs), x]);
+ });
+ name_inputs.forEach(function(x) {
+ tests.push(["createElementNS " + x, test_create_element_ns, [null, null, x]]);
+ tests.push(["setAttributeNS " + x, test_set_attribute_ns, [null, null, x]]);
+ tests.push(["getAttributeNS " + x, test_get_attribute_ns, [null, null, x]]);
+ });
+
+ });
+function outer_product() {
+ var rv = [];
+ function compute_outer_product() {
+ var args = Array.prototype.slice.call(arguments);
+ var index = args[0];
+ if (index < args.length) {
+ args[index].forEach(function(x) {
+ compute_outer_product.apply(this, [index+1].concat(args.slice(1, index), x, args.slice(index+1)));
+ });
+ } else {
+ rv.push(args.slice(1));
+ }
+ }
+ compute_outer_product.apply(this, [1].concat(Array.prototype.slice.call(arguments)));
+ return rv;
+}
+
+function expected_case(input) {
+ //is_html gets set by a global on the page loading the tests
+ if (is_html) {
+ return ascii_lowercase(input);
+ } else {
+ return input;
+ }
+}
+
+function ascii_lowercase(input) {
+ return input.replace(/[A-Z]/g, function(x) {
+ return x.toLowerCase();
+ });
+}
+
+function get_qualified_name(el) {
+ if (el.prefix) {
+ return el.prefix + ":" + el.localName;
+ }
+ return el.localName;
+}
+
+function test_create_element(name) {
+ var node = document.createElement(name);
+ assert_equals(node.localName, expected_case(name));
+}
+
+function test_create_element_ns(namespace, prefix, local_name) {
+ var qualified_name = prefix ? prefix + ":" + local_name : local_name;
+ var node = document.createElementNS(namespace, qualified_name);
+ assert_equals(node.prefix, prefix, "prefix");
+ assert_equals(node.localName, local_name, "localName");
+}
+
+function test_set_attribute(name) {
+ var node = document.createElement("div");
+ node.setAttribute(name, "test");
+ assert_equals(node.attributes[0].localName, expected_case(name));
+}
+
+function test_set_attribute_ns(namespace, prefix, local_name) {
+ var qualified_name = prefix ? prefix + ":" + local_name : local_name;
+ var node = document.createElement("div");
+ node.setAttributeNS(namespace, qualified_name, "test");
+ var attr = node.attributes[0];
+ assert_equals(attr.prefix, prefix, "prefix");
+ assert_equals(attr.localName, local_name, "localName");
+}
+
+function test_get_attribute(name) {
+ var node = document.createElement("div");
+ node.setAttribute(name, "test");
+ var expected_name = expected_case(name);
+ assert_equals(node.getAttribute(expected_name), "test");
+ if (expected_name != name) {
+ assert_equals(node.getAttribute(expected_name), "test");
+ } else if (name !== ascii_lowercase(name)) {
+ assert_equals(node.getAttribute(ascii_lowercase(name)), null);
+ }
+}
+
+function test_get_attribute_ns(namespace, prefix, local_name) {
+ var qualified_name = prefix ? prefix + ":" + local_name : local_name;
+ var node = document.createElement("div");
+ node.setAttributeNS(namespace, qualified_name, "test");
+ var expected_name = local_name;
+ assert_equals(node.getAttributeNS(namespace, expected_name), "test");
+ if (local_name !== ascii_lowercase(local_name)) {
+ assert_equals(node.getAttributeNS(namespace, ascii_lowercase(local_name)), null);
+ }
+}
+
+function test_get_elements_tag_name(elements_to_create, search_string) {
+ var container = document.createElement("div");
+ elements_to_create.forEach(function(x) {
+ var qualified_name = x[1] ? x[1] + ":" + x[2] : x[2];
+ var element = document.createElementNS(x[0], qualified_name);
+ container.appendChild(element);
+ });
+ var expected = Array.prototype.filter.call(container.childNodes,
+ function(node) {
+ if (is_html && node.namespaceURI === "http://www.w3.org/1999/xhtml") {
+ return get_qualified_name(node) === expected_case(search_string);
+ } else {
+ return get_qualified_name(node) === search_string;
+ }
+ });
+ document.documentElement.appendChild(container);
+ try {
+ assert_array_equals(document.getElementsByTagName(search_string), expected);
+ } finally {
+ document.documentElement.removeChild(container);
+ }
+}
+
+function test_get_elements_tag_name_ns(elements_to_create, search_input) {
+ var search_uri = search_input[0];
+ var search_name = search_input[1];
+ var container = document.createElement("div");
+ elements_to_create.forEach(function(x) {
+ var qualified_name = x[1] ? x[1] + ":" + x[2] : x[2];
+ var element = document.createElementNS(x[0], qualified_name);
+ container.appendChild(element);
+ });
+ var expected = Array.prototype.filter.call(container.childNodes,
+ function(node) {
+ return node.namespaceURI === search_uri;
+ return node.localName === search_name;
+ });
+ document.documentElement.appendChild(container);
+ try {
+ assert_array_equals(document.getElementsByTagNameNS(search_uri, search_name), expected);
+ } catch(e) {
+ throw e;
+ } finally {
+ document.documentElement.removeChild(container);
+ }
+}
+
+function test_func() {
+ var func = arguments[0];
+ var rest = arguments[1];
+ func.apply(this, rest);
+}
+
+generate_tests(test_func, tests);
diff --git a/testing/web-platform/tests/dom/nodes/characterset-helper.js b/testing/web-platform/tests/dom/nodes/characterset-helper.js
new file mode 100644
index 0000000000..ecbe556ae2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/characterset-helper.js
@@ -0,0 +1,62 @@
+function runCharacterSetTests(encodingMap) {
+ // Add spaces and mix up case
+ Object.keys(encodingMap).forEach(function(name) {
+ var lower = encodingMap[name];
+ var upper = encodingMap[name].map(function(s) { return s.toUpperCase() });
+ var mixed = encodingMap[name].map(function(s) {
+ var ret = "";
+ for (var i = 0; i < s.length; i += 2) {
+ ret += s[i].toUpperCase();
+ if (i + 1 < s.length) {
+ ret += s[i + 1];
+ }
+ }
+ return ret;
+ });
+ var spacey = encodingMap[name].map(function(s) {
+ return " \t\n\f\r" + s + " \t\n\f\r";
+ });
+ encodingMap[name] = [];
+ for (var i = 0; i < lower.length; i++) {
+ encodingMap[name].push(lower[i]);
+ /*
+ if (lower[i] != upper[i]) {
+ encodingMap[name].push(upper[i]);
+ }
+ if (lower[i] != mixed[i] && upper[i] != mixed[i]) {
+ encodingMap[name].push(mixed[i]);
+ }
+ encodingMap[name].push(spacey[i]);
+ */
+ }
+ });
+
+ Object.keys(encodingMap).forEach(function(name) {
+ encodingMap[name].forEach(function(label) {
+ var iframe = document.createElement("iframe");
+ var t = async_test("Name " + format_value(name) +
+ " has label " + format_value(label) + " (characterSet)");
+ var t2 = async_test("Name " + format_value(name) +
+ " has label " + format_value(label) + " (inputEncoding)");
+ var t3 = async_test("Name " + format_value(name) +
+ " has label " + format_value(label) + " (charset)");
+ iframe.src = "encoding.py?label=" + label;
+ iframe.onload = function() {
+ t.step(function() {
+ assert_equals(iframe.contentDocument.characterSet, name);
+ });
+ t2.step(function() {
+ assert_equals(iframe.contentDocument.inputEncoding, name);
+ });
+ t3.step(function() {
+ assert_equals(iframe.contentDocument.charset, name);
+ });
+ document.body.removeChild(iframe);
+ t.done();
+ t2.done();
+ t3.done();
+ };
+ document.body.appendChild(iframe);
+ });
+ });
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/nodes/creators.js b/testing/web-platform/tests/dom/nodes/creators.js
new file mode 100644
index 0000000000..8b7415d13f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/creators.js
@@ -0,0 +1,5 @@
+var creators = {
+ "element": "createElement",
+ "text": "createTextNode",
+ "comment": "createComment"
+};
diff --git a/testing/web-platform/tests/dom/nodes/encoding.py b/testing/web-platform/tests/dom/nodes/encoding.py
new file mode 100644
index 0000000000..15edff7061
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/encoding.py
@@ -0,0 +1,7 @@
+from html import escape
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ label = request.GET.first(b'label')
+ return u"""<!doctype html><meta charset="%s">""" % escape(isomorphic_decode(label))
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-01.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-01.htm
new file mode 100644
index 0000000000..457d6c400f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-01.htm
@@ -0,0 +1,13 @@
+<!doctype html>
+<html class="a">
+ <head>
+ <title>document.getElementsByClassName(): simple</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a">
+ <div id="log"></div>
+ <script> test(function() {assert_array_equals(document.getElementsByClassName("\ta\n"),
+ [document.documentElement, document.body])}) </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-02.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-02.htm
new file mode 100644
index 0000000000..d5e513fa88
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-02.htm
@@ -0,0 +1,14 @@
+<!doctype html>
+<html class="a
+b">
+ <head>
+ <title>document.getElementsByClassName(): also simple</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a
+">
+ <div id="log"></div>
+ <script> test(function() {assert_array_equals(document.getElementsByClassName("a\n"), [document.documentElement, document.body])}) </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-03.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-03.htm
new file mode 100644
index 0000000000..a9e2d3af1a
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-03.htm
@@ -0,0 +1,18 @@
+<!doctype html>
+<html class="a">
+ <head>
+ <title>document.getElementsByClassName(): changing classes</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a">
+ <div id="log"></div>
+ <script>
+ test(function() {
+ var collection = document.getElementsByClassName("a")
+ document.body.className = "b"
+ assert_array_equals(collection, [document.documentElement])
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-04.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-04.htm
new file mode 100644
index 0000000000..0c62fed2c5
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-04.htm
@@ -0,0 +1,18 @@
+<!doctype html>
+<html class="a">
+ <head>
+ <title>document.getElementsByClassName(): changing classes</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a">
+ <div id="log"></div>
+ <script>
+ test(function() {
+ var collection = document.getElementsByClassName("a");
+ document.body.className += "\tb";
+ assert_array_equals(collection, [document.documentElement, document.body]);
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-05.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-05.htm
new file mode 100644
index 0000000000..98245f6427
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-05.htm
@@ -0,0 +1,18 @@
+<!doctype html>
+<html class="a">
+ <head>
+ <title>document.getElementsByClassName(): changing classes</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a">
+ <div id="log"></div>
+ <script>
+ test(function() {
+ var collection = document.getElementsByClassName("a");
+ document.body.removeAttribute("class");
+ assert_array_equals(collection, [document.documentElement]);
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-06.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-06.htm
new file mode 100644
index 0000000000..4975a89e09
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-06.htm
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <title>document.getElementsByClassName(): adding element with class</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a">
+ <div id="log"></div>
+ <script>
+ test(function() {
+ var collection = document.getElementsByClassName("a");
+ var ele = document.createElement("foo");
+ ele.setAttribute("class", "a");
+ document.body.appendChild(ele);
+ assert_array_equals(collection, [document.body, ele]);
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-07.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-07.htm
new file mode 100644
index 0000000000..b0102fb2e6
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-07.htm
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+ <head>
+ <title>document.getElementsByClassName(): multiple classes</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a b">
+ <div id="log"></div>
+ <script> test(function() {
+ assert_array_equals(document.getElementsByClassName("b\t\f\n\na\rb"), [document.body]);
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-08.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-08.htm
new file mode 100644
index 0000000000..a248af4929
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-08.htm
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+ <head>
+ <title>document.getElementsByClassName(): multiple classes</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a">
+ <div id="log"></div>
+ <script> test(function() {
+ document.getElementsByClassName("a\fa"), [document.body]
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-09.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-09.htm
new file mode 100644
index 0000000000..9011f3068c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-09.htm
@@ -0,0 +1,15 @@
+<!doctype html>
+<html class="a A">
+ <head>
+ <title>document.getElementsByClassName(): case sensitive</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a a">
+ <div id="log"></div>
+ <script>test(function() {
+ assert_array_equals(document.getElementsByClassName("A a"), [document.documentElement])
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-10.xml b/testing/web-platform/tests/dom/nodes/getElementsByClassName-10.xml
new file mode 100644
index 0000000000..b3e3122cb0
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-10.xml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:g="http://www.w3.org/2000/svg">
+ <head>
+ <title>document.getElementsByClassName(): compound</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id="log"/>
+ <div id="tests">
+ <x class="a"/>
+ <g:x class="a"/>
+ </div>
+ <script>test(function() {
+ assert_array_equals(document.getElementsByClassName("a"),
+ document.getElementById("tests").children);
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-11.xml b/testing/web-platform/tests/dom/nodes/getElementsByClassName-11.xml
new file mode 100644
index 0000000000..8593fa7a0e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-11.xml
@@ -0,0 +1,24 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:g="http://www.w3.org/2000/svg" xmlns:h="http://www.w3.org/1999/xhtml" xmlns:t="http://tc.labs.opera.com/#test">
+ <head>
+ <title>document.getElementsByClassName(): "tricky" compound</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id="log" />
+ <div id="tests">
+ <x class="a"/>
+ <g:x class="a"/>
+ <x t:class="a" h:class="a" g:class="a"/>
+ <g:x t:class="a" h:class="a" g:class="a"/>
+ <t:x class="a" t:class="a" h:class="a" g:class="a"/>
+ </div>
+ <script>
+ test(function() {
+ var collection = document.getElementsByClassName("a");
+ var test = document.getElementById("tests").children;
+ assert_array_equals(collection, [test[0], test[1], test[4]]);
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-12.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-12.htm
new file mode 100644
index 0000000000..3b7f328b47
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-12.htm
@@ -0,0 +1,15 @@
+<!doctype html>
+<html class="a">
+ <head>
+ <title>element.getElementsByClassName(): simple</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a">
+ <div id="log"></div>
+ <script>test(function() {
+ assert_array_equals(document.body.getElementsByClassName("a"), [])
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-13.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-13.htm
new file mode 100644
index 0000000000..f3af106aed
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-13.htm
@@ -0,0 +1,19 @@
+<!doctype html>
+<html class="a">
+ <head>
+ <title>element.getElementsByClassName(): adding an element</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a">
+ <div id="log"></div>
+ <script>test(function() {
+ var collection = document.body.getElementsByClassName("a");
+ var ele = document.createElement("x-y-z");
+ ele.className = "a";
+ document.body.appendChild(ele);
+ assert_array_equals(collection, [ele]);
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-14.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-14.htm
new file mode 100644
index 0000000000..83addb7ba5
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-14.htm
@@ -0,0 +1,26 @@
+<!-- quirks mode -->
+<html class="a A">
+ <head>
+ <title>document.getElementsByClassName(): case-insensitive (quirks mode)</title>
+ <link rel="help" href="https://dom.spec.whatwg.org/#concept-getelementsbyclassname">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a a">
+ <div id="log"></div>
+ <div class="k"></div>
+ <div class="K"></div>
+ <div class="&#x212a;" id="kelvin"></div>
+ <script>
+test(function() {
+ assert_array_equals(document.getElementsByClassName("A a"),
+ [document.documentElement, document.body]);
+})
+
+test(function() {
+ assert_array_equals(document.getElementsByClassName("\u212a"),
+ [document.getElementById("kelvin")]);
+}, 'Unicode-case should be sensitive even in quirks mode.');
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-15.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-15.htm
new file mode 100644
index 0000000000..89614de30d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-15.htm
@@ -0,0 +1,18 @@
+<!doctype html>
+<html class="a
+b">
+ <head>
+ <title>document.getElementsByClassName(array): "a\n"</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a
+">
+ <div id="log"></div>
+ <script>test(function () {
+ assert_array_equals(document.getElementsByClassName(["a\n"]),
+ [document.documentElement, document.body]);
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-16.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-16.htm
new file mode 100644
index 0000000000..3f987a7ae0
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-16.htm
@@ -0,0 +1,16 @@
+<!doctype html>
+<html class="a
+b">
+ <head>
+ <title>document.getElementsByClassName(array): "b","a"</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="b,a">
+ <div id="log"></div>
+ <script>test(function() {
+ assert_array_equals(document.getElementsByClassName(["b", "a"]), [document.body]);
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-17.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-17.htm
new file mode 100644
index 0000000000..ae5ebda6e2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-17.htm
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+ <head>
+ <title>document.getElementsByClassName(array): "b a"</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a b">
+ <div id="log"></div>
+ <script>test(function() {
+ assert_array_equals(document.getElementsByClassName(["b a"]), [document.body]);
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-18.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-18.htm
new file mode 100644
index 0000000000..9f6cf75a50
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-18.htm
@@ -0,0 +1,17 @@
+<!doctype html>
+<html class="a,b">
+ <head>
+ <title>element.getElementsByClassName(array): "a", "b"</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body class="a,b x">
+ <div id="log"></div>
+ <p id="r" class="a,bx"></p>
+ <script class="xa,b">test(function() {
+ assert_array_equals(document.documentElement.getElementsByClassName(["\fa","b\n"]),
+ [document.body])
+ })
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-19.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-19.htm
new file mode 100644
index 0000000000..da233c743a
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-19.htm
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html><head>
+ <title>getElementsByClassName</title>
+ <meta content="get elements in document" name="description">
+ <link href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname" rel="help">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+ <body>
+ <div id="log"></div>
+ <div>
+ <div>
+ <a class="text link" href="#foo">test link #foo</a>
+ </div>
+ <b class="text">text</b>
+ </div>
+ <table>
+ <caption class="text caption">text caption</caption>
+ <thead>
+ <tr>
+ <td class="TEXT head">TEXT head</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="td text1">td text1</td>
+ </tr>
+ <tr>
+ <td class="td text">td text</td>
+ </tr>
+ <tr>
+ <td class="td te xt">td te xt</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td class="TEXT foot">TEXT foot</td>
+ </tr>
+ </tfoot>
+ </table>
+ <div class="xt te">xt te</div>
+
+ <script type="text/javascript">
+ test(function ()
+ {
+ var collection = document.getElementsByClassName("text");
+ assert_equals(collection.length, 4);
+ assert_equals(collection[0].parentNode.nodeName, "DIV");
+ assert_equals(collection[1].parentNode.nodeName, "DIV");
+ assert_equals(collection[2].parentNode.nodeName, "TABLE");
+ assert_equals(collection[3].parentNode.nodeName, "TR");
+ }, "get elements in document");
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-20.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-20.htm
new file mode 100644
index 0000000000..6429e37bb5
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-20.htm
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html><head>
+ <title>getElementsByClassName</title>
+ <meta content="get elements in document then add element to collection" name="description">
+ <link href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname" rel="help">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+ <body>
+ <div id="log"></div>
+ <div>
+ <div>
+ <a class="text link" href="#foo">test link #foo</a>
+ </div>
+ <b class="text">text</b>
+ </div>
+ <table>
+ <caption class="text caption">text caption</caption>
+ <thead>
+ <tr>
+ <td class="TEXT head">TEXT head</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="td text1">td text1</td>
+ </tr>
+ <tr>
+ <td class="td text">td text</td>
+ </tr>
+ <tr>
+ <td class="td te xt">td te xt</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td class="TEXT foot">TEXT foot</td>
+ </tr>
+ </tfoot>
+ </table>
+ <div class="xt te">xt te</div>
+
+ <script type="text/javascript">
+ test(function()
+ {
+ var collection = document.getElementsByClassName("text");
+ assert_equals(collection.length, 4);
+ var newDiv = document.createElement("div");
+ newDiv.setAttribute("class", "text");
+ newDiv.innerHTML = "text newDiv";
+ document.getElementsByTagName("table")[0].tBodies[0].rows[0].cells[0].appendChild(newDiv);
+
+ assert_equals(collection.length, 5);
+ assert_equals(collection[0].parentNode.nodeName, "DIV");
+ assert_equals(collection[1].parentNode.nodeName, "DIV");
+ assert_equals(collection[2].parentNode.nodeName, "TABLE");
+ assert_equals(collection[3].parentNode.nodeName, "TD");
+ assert_equals(collection[4].parentNode.nodeName, "TR");
+ }, "get elements in document then add element to collection");
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-21.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-21.htm
new file mode 100644
index 0000000000..339ff2d3e1
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-21.htm
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html><head>
+ <title>getElementsByClassName</title>
+ <meta content="delete element from collection" name="description">
+ <link href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname" rel="help">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+ <body>
+ <div id="log"></div>
+ <div>
+ <div>
+ <a class="text link" href="#foo">test link #foo</a>
+ </div>
+ <b class="text">text</b>
+ </div>
+ <table>
+ <caption class="text caption">text caption</caption>
+ <thead>
+ <tr>
+ <td class="TEXT head">TEXT head</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="td text1">td text1</td>
+ </tr>
+ <tr>
+ <td class="td text">td text</td>
+ </tr>
+ <tr>
+ <td class="td te xt">td te xt</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td class="TEXT foot">TEXT foot</td>
+ </tr>
+ </tfoot>
+ </table>
+ <div class="xt te">xt te</div>
+
+ <script type="text/javascript">
+ test(function()
+ {
+ var collection = document.getElementsByClassName("text1");
+ assert_equals(collection.length, 1)
+ document.getElementsByTagName("table")[0].deleteRow(1);
+ assert_equals(collection.length, 0);
+ }, "delete element from collection");
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-22.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-22.htm
new file mode 100644
index 0000000000..c203967007
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-22.htm
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html><head>
+ <title>getElementsByClassName</title>
+ <meta content="move item in collection order" name="description">
+ <link href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname" rel="help">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+ <body>
+ <div id="log"></div>
+ <div>
+ <div>
+ <a class="text link" href="#foo">test link #foo</a>
+ </div>
+ <b class="text">text</b>
+ </div>
+ <table>
+ <caption class="text caption">text caption</caption>
+ <thead>
+ <tr>
+ <td class="TEXT head">TEXT head</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="td text1">td text1</td>
+ </tr>
+ <tr>
+ <td class="td text">td text</td>
+ </tr>
+ <tr>
+ <td class="td te xt">td te xt</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td class="TEXT foot">TEXT foot</td>
+ </tr>
+ </tfoot>
+ </table>
+ <div class="xt te">xt te</div>
+
+ <script type="text/javascript">
+ test(function()
+ {
+ var collection = document.getElementsByClassName("text");
+ assert_equals(collection.length, 4);
+ var boldText = document.getElementsByTagName("b")[0];
+ document.getElementsByTagName("table")[0].tBodies[0].rows[0].cells[0].appendChild(boldText);
+
+ assert_equals(collection.length, 4);
+ assert_equals(collection[0].parentNode.nodeName, "DIV");
+ assert_equals(collection[1].parentNode.nodeName, "TABLE");
+ assert_equals(collection[2].parentNode.nodeName, "TD");
+ assert_equals(collection[3].parentNode.nodeName, "TR");
+ }, "move item in collection order");
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-23.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-23.htm
new file mode 100644
index 0000000000..0af8a09952
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-23.htm
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html><head>
+ <title>getElementsByClassName</title>
+ <meta content="multiple defined classes" name="description">
+ <link href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname" rel="help">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+ <body>
+ <div id="log"></div>
+ <div>
+ <div>
+ <a class="text link" href="#foo">test link #foo</a>
+ </div>
+ <b class="text">text</b>
+ </div>
+ <table>
+ <caption class="text caption">text caption</caption>
+ <thead>
+ <tr>
+ <td class="TEXT head">TEXT head</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="td text1">td text1</td>
+ </tr>
+ <tr>
+ <td class="td text">td text</td>
+ </tr>
+ <tr>
+ <td class="td te xt">td te xt</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td class="TEXT foot">TEXT foot</td>
+ </tr>
+ </tfoot>
+ </table>
+ <div class="xt te">xt te</div>
+
+ <script type="text/javascript">
+ test(function()
+ {
+ var collection = document.getElementsByClassName("te xt");
+ assert_equals(collection.length, 2);
+ assert_equals(collection[0].parentNode.nodeName, "TR");
+ assert_equals(collection[1].parentNode.nodeName, "BODY");
+ }, "multiple defined classes");
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-24.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-24.htm
new file mode 100644
index 0000000000..838987ad0c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-24.htm
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <meta charset='utf-8'>
+ <title>getElementsByClassName</title>
+ <meta content="handle unicode chars" name="description">
+ <link href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname" rel="help">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+ <body>
+ <div id="log"></div>
+ <div>
+ <div>
+ <a class="ΔЙあ叶葉 말 link" href="#foo">ΔЙあ叶葉 말 link</a>
+ </div>
+ <b class="text">text</b>
+ </div>
+ <div class="ΔЙあ叶葉 קم">ΔЙあ叶葉 קم</div>
+
+ <script type="text/javascript">
+ test(function()
+ {
+ var collection = document.getElementsByClassName("ΔЙあ叶葉");
+ assert_equals(collection.length, 2);
+ assert_equals(collection[0].parentNode.nodeName, "DIV");
+ assert_equals(collection[1].parentNode.nodeName, "BODY");
+ }, "handle unicode chars");
+ </script>
+
+</body></html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-25.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-25.htm
new file mode 100644
index 0000000000..21f2a9ff7a
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-25.htm
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html><head>
+ <title>getElementsByClassName</title>
+ <meta content="verify spacing is handled correctly" name="description">
+ <link href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname" rel="help">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+ <body>
+ <div id="log"></div>
+ <div>
+ <div>
+ <a class="text link" href="#foo">test link #foo</a>
+ </div>
+ <b class="text">text</b>
+ </div>
+ <table>
+ <caption class="text caption">text caption</caption>
+ <thead>
+ <tr>
+ <td class="TEXT head">TEXT head</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="td text1">td text1</td>
+ </tr>
+ <tr>
+ <td class="td text">td text</td>
+ </tr>
+ <tr>
+ <td class="td te xt">td te xt</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td class="TEXT foot">TEXT foot</td>
+ </tr>
+ </tfoot>
+ </table>
+ <div class="xt te">xt te</div>
+
+ <script type="text/javascript">
+ test(function()
+ {
+ var collection = document.getElementsByClassName("text ");
+ assert_equals(collection.length, 4);
+ var boldText = document.getElementsByTagName("b")[0];
+ document.getElementsByTagName("table")[0].tBodies[0].rows[0].cells[0].appendChild(boldText);
+ assert_equals(collection.length, 4);
+ assert_equals(collection[0].parentNode.nodeName, "DIV");
+ assert_equals(collection[1].parentNode.nodeName, "TABLE");
+ assert_equals(collection[2].parentNode.nodeName, "TD");
+ assert_equals(collection[3].parentNode.nodeName, "TR");
+ }, "verify spacing is handled correctly");
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-26.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-26.htm
new file mode 100644
index 0000000000..0d7ff1ba1f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-26.htm
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html><head>
+ <title>getElementsByClassName</title>
+ <meta content="multiple class attributes" name="description">
+ <link href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname" rel="help">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+ <body>
+ <div id="log"></div>
+ <div class="te xt">
+ te xt
+ <div class="te">
+ te; xt
+ <a class="text link" href="#foo">test link #foo</a>
+ </div>
+ <b class="text">text</b>
+ </div>
+ <div class="xt te">xt te</div>
+ <script type="text/javascript">
+ test(function()
+ {
+ var collection = document.getElementsByClassName("te xt");
+
+ assert_equals(collection.length, 2);
+ assert_equals(collection[0].parentNode.nodeName, "BODY");
+ assert_equals(collection[1].parentNode.nodeName, "BODY");
+ }, "multiple class attributes");
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-27.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-27.htm
new file mode 100644
index 0000000000..95fc674428
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-27.htm
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html><head>
+ <title>getElementsByClassName</title>
+ <meta content="generic element listed" name="description">
+ <link href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname" rel="help">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+ <body>
+ <div id="log"></div>
+ <div class="te xt">
+ te xt
+ <div class="te">
+ te; xt
+ <a class="text link" href="#foo">test link #foo</a>
+ <foo class="te xt">dummy tag</foo>
+ </div>
+ <b class="text">text</b>
+ </div>
+ <div class="xt te">xt te</div>
+ <script type="text/javascript">
+ test(function()
+ {
+ var collection = document.getElementsByClassName("te xt");
+
+ assert_equals(collection.length, 3);
+ assert_equals(collection[0].parentNode.nodeName, "BODY");
+ assert_equals(collection[1].parentNode.nodeName, "DIV");
+ assert_equals(collection[2].parentNode.nodeName, "BODY");
+ }, "generic element listed");
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-28.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-28.htm
new file mode 100644
index 0000000000..1fc94d807c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-28.htm
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html><head>
+ <title>getElementsByClassName</title>
+ <meta content="generic element listed" name="description">
+ <link href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname" rel="help">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+ <body>
+ <div id="log"></div>
+ <div class="te xt">
+ te xt
+ <div class="te">
+ te; xt
+ <a class="text link" href="#foo">test link #foo</a>
+ <fooU00003Abar class="te xt namespace">te xt namespace
+ </fooU00003Abar></div>
+ <b class="text">text</b>
+ </div>
+ <div class="xt te">xt te</div>
+ <script type="text/javascript">
+ test(function()
+ {
+ var collection = document.getElementsByClassName("te xt");
+ assert_equals(collection.length, 3);
+ assert_equals(collection[0].parentNode.nodeName, "BODY");
+ assert_equals(collection[1].parentNode.nodeName, "DIV");
+ assert_equals(collection[2].parentNode.nodeName, "BODY");
+ }, "generic element listed");
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-29.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-29.htm
new file mode 100644
index 0000000000..ad489752cc
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-29.htm
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html><head>
+ <title>getElementsByClassName</title>
+ <meta content="get class from children of element" name="description">
+ <link href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname" rel="help">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+ <body>
+ <div id='log'></div>
+ <div>
+ <div>
+ <a class="text link" href="#foo">test link #foo</a>
+ </div>
+ <b class="text">text</b>
+ </div>
+ <table>
+ <caption class="text caption">text caption</caption>
+ <thead>
+ <tr>
+ <td class="TEXT head">TEXT head</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="td text1">td text1</td>
+ </tr>
+ <tr>
+ <td class="td text">td text</td>
+ </tr>
+ <tr>
+ <td class="td te xt">td te xt</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td class="TEXT foot">TEXT foot</td>
+ </tr>
+ </tfoot>
+ </table>
+ <div class="xt te">xt te</div>
+
+ <script type="text/javascript">
+ test(function()
+ {
+ var collection = document.getElementsByTagName("table")[0].getElementsByClassName("te xt");
+ assert_equals(collection.length, 1);
+ assert_equals(collection[0].parentNode.nodeName, "TR");
+ }, "get class from children of element");
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-30.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-30.htm
new file mode 100644
index 0000000000..c0b4faf2d4
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-30.htm
@@ -0,0 +1,190 @@
+<!DOCTYPE html>
+<html><head class="foo">
+ <title class="foo">getElementsByClassName</title>
+ <meta class="foo" content="big element listing" name="description">
+ <link class="foo">
+ <base class="foo">
+ <script class="foo"></script>
+ <style class="foo"></style>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname" rel="help">
+</head>
+ <body class="foo">
+ <div id='log'></div>
+ <a class="foo">a</a>
+ <abbr class="foo">abbr</abbr>
+ <acronym class="foo">acronym</acronym>
+ <address class="foo">address</address>
+ <applet class="foo">applet</applet>
+ <b class="foo">b</b>
+ <bdo class="foo">bdo</bdo>
+ <big class="foo">big</big>
+ <blockquote class="foo">blockquote</blockquote>
+ <br class="foo">
+ <button class="foo">button</button>
+ <center class="foo">center</center>
+ <cite class="foo">cite</cite>
+ <code class="foo">code</code>
+ <del class="foo">del</del>
+ <dfn class="foo">dfn</dfn>
+ <dir class="foo">dir
+ <li class="foo">li</li>
+ </dir>
+ <div class="foo">div</div>
+ <dl class="foo">
+ <dt class="foo">
+ </dt><dd class="foo">dd</dd>
+ </dl>
+ <em class="foo">em</em>
+ <font class="foo">font</font>
+ <form class="foo">
+ <label class="foo">label</label>
+ <fieldset class="foo">
+ <legend class="foo">legend</legend>
+ </fieldset>
+ </form>
+ <h1 class="foo">h1</h1>
+ <hr class="foo">
+ <i class="foo">i</i>
+ <iframe class="foo">iframe</iframe>
+ <img class="foo">
+ <input class="foo">
+ <ins class="foo">ins</ins>
+ <kbd class="foo">kbd</kbd>
+ <map class="foo">
+ <area class="foo"></area>
+ </map>
+ <menu class="foo">menu</menu>
+ <noscript class="foo">noscript</noscript>
+ <object class="foo">
+ <param class="foo">
+ </object>
+ <ol class="foo">ol</ol>
+ <p class="foo">p</p>
+ <pre class="foo">pre</pre>
+ <q class="foo">q</q>
+ <s class="foo">s</s>
+ <samp class="foo">samp</samp>
+ <select class="foo">
+ <optgroup class="foo">optgroup</optgroup>
+ <option class="foo">option</option>
+ </select>
+ <small class="foo">small</small>
+ <span class="foo">span</span>
+ <strike class="foo">strike</strike>
+ <strong class="foo">strong</strong>
+ <sub class="foo">sub</sub>
+ <sup class="foo">sup</sup>
+ colgroup<table class="foo">
+ <caption class="foo">caption</caption>
+ <colgroup><col class="foo">
+ </colgroup><colgroup class="foo"></colgroup>
+ <thead class="foo">
+ <tr><th class="foo">th</th>
+ </tr></thead>
+ <tbody class="foo">
+ <tr class="foo">
+ <td class="foo">td</td>
+ </tr>
+ </tbody>
+ <tfoot class="foo"></tfoot>
+ </table>
+ <textarea class="foo">textarea</textarea>
+ <tt class="foo">tt</tt>
+ <u class="foo">u</u>
+ <ul class="foo">ul</ul>
+ <var class="foo">var</var>
+ <script type="text/javascript">
+ test(function ()
+ {
+ var arrElements = [
+ "HEAD",
+ "TITLE",
+ "META",
+ "LINK",
+ "BASE",
+ "SCRIPT",
+ "STYLE",
+ "BODY",
+ "A",
+ "ABBR",
+ "ACRONYM",
+ "ADDRESS",
+ "APPLET",
+ "B",
+ "BDO",
+ "BIG",
+ "BLOCKQUOTE",
+ "BR",
+ "BUTTON",
+ "CENTER",
+ "CITE",
+ "CODE",
+ "DEL",
+ "DFN",
+ "DIR",
+ "LI",
+ "DIV",
+ "DL",
+ "DT",
+ "DD",
+ "EM",
+ "FONT",
+ "FORM",
+ "LABEL",
+ "FIELDSET",
+ "LEGEND",
+ "H1",
+ "HR",
+ "I",
+ "IFRAME",
+ "IMG",
+ "INPUT",
+ "INS",
+ "KBD",
+ "MAP",
+ "AREA",
+ "MENU",
+ "NOSCRIPT",
+ "OBJECT",
+ "PARAM",
+ "OL",
+ "P",
+ "PRE",
+ "Q",
+ "S",
+ "SAMP",
+ "SELECT",
+ "OPTGROUP",
+ "OPTION",
+ "SMALL",
+ "SPAN",
+ "STRIKE",
+ "STRONG",
+ "SUB",
+ "SUP",
+ "TABLE",
+ "CAPTION",
+ "COL",
+ "COLGROUP",
+ "THEAD",
+ "TH",
+ "TBODY",
+ "TR",
+ "TD",
+ "TFOOT",
+ "TEXTAREA",
+ "TT",
+ "U",
+ "UL",
+ "VAR"];
+
+ var collection = document.getElementsByClassName("foo");
+ for (var x = 0; x < collection.length; x++)
+ {
+ assert_equals(collection[x].nodeName, arrElements[x]);
+ }
+}, "big element listing");
+ </script>
+</body></html>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-31.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassName-31.htm
new file mode 100644
index 0000000000..0e1ac014ae
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-31.htm
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class=foo>
+<meta charset=utf-8>
+<title>getElementsByClassName across documents</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script class=foo>
+async_test(function() {
+ var iframe = document.createElement("iframe");
+ iframe.onload = this.step_func_done(function() {
+ var collection = iframe.contentDocument.getElementsByClassName("foo");
+ assert_equals(collection.length, 3);
+ assert_equals(collection[0].localName, "html");
+ assert_equals(collection[1].localName, "head");
+ assert_equals(collection[2].localName, "body");
+ });
+ iframe.src = "getElementsByClassNameFrame.htm";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-32.html b/testing/web-platform/tests/dom/nodes/getElementsByClassName-32.html
new file mode 100644
index 0000000000..29eb41353c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-32.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<title>Node.prototype.getElementsByClassName tests imported from jsdom</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div class="df-article" id="1">
+</div>
+<div class="df-article" id="2">
+</div>
+<div class="df-article" id="3">
+</div>
+
+<script>
+"use strict";
+
+test(() => {
+
+ const p = document.createElement("p");
+ p.className = "unknown";
+ document.body.appendChild(p);
+
+ const elements = document.getElementsByClassName("first-p");
+ assert_array_equals(elements, []);
+
+}, "cannot find the class name");
+
+test(() => {
+
+ const p = document.createElement("p");
+ p.className = "first-p";
+ document.body.appendChild(p);
+
+ const elements = document.getElementsByClassName("first-p");
+ assert_array_equals(elements, [p]);
+
+}, "finds the class name");
+
+
+test(() => {
+
+ const p = document.createElement("p");
+ p.className = "the-p second third";
+ document.body.appendChild(p);
+
+ const elements1 = document.getElementsByClassName("the-p");
+ assert_array_equals(elements1, [p]);
+
+ const elements2 = document.getElementsByClassName("second");
+ assert_array_equals(elements2, [p]);
+
+ const elements3 = document.getElementsByClassName("third");
+ assert_array_equals(elements3, [p]);
+
+}, "finds the same element with multiple class names");
+
+test(() => {
+
+ const elements = document.getElementsByClassName("df-article");
+
+ assert_equals(elements.length, 3);
+ assert_array_equals(Array.prototype.map.call(elements, el => el.id), ["1", "2", "3"]);
+
+}, "does not get confused by numeric IDs");
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-empty-set.html b/testing/web-platform/tests/dom/nodes/getElementsByClassName-empty-set.html
new file mode 100644
index 0000000000..75b8d5a9f8
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-empty-set.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<title>Node.prototype.getElementsByClassName with no real class names</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<span class=" ">test</span>
+
+<script>
+"use strict";
+
+test(() => {
+ const elements = document.getElementsByClassName("");
+ assert_array_equals(elements, []);
+}, "Passing an empty string to getElementsByClassName should return an empty HTMLCollection");
+
+test(() => {
+ const elements = document.getElementsByClassName(" ");
+ assert_array_equals(elements, []);
+}, "Passing a space to getElementsByClassName should return an empty HTMLCollection");
+
+test(() => {
+ const elements = document.getElementsByClassName(" ");
+ assert_array_equals(elements, []);
+}, "Passing three spaces to getElementsByClassName should return an empty HTMLCollection");
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassName-whitespace-class-names.html b/testing/web-platform/tests/dom/nodes/getElementsByClassName-whitespace-class-names.html
new file mode 100644
index 0000000000..59bfd2e6b1
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassName-whitespace-class-names.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<title>Node.prototype.getElementsByClassName with no real class names</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<span class="&#x000B;">LINE TABULATION</span>
+<span class="&#x0085;">NEXT LINE</span>
+<span class="&#x00A0;">NO-BREAK SPACE</span>
+<span class="&#x1680;">OGHAM SPACE MARK</span>
+<span class="&#x2000;">EN QUAD</span>
+<span class="&#x2001;">EM QUAD</span>
+<span class="&#x2002;">EN SPACE</span>
+<span class="&#x2003;">EM SPACE</span>
+<span class="&#x2004;">THREE-PER-EM SPACE</span>
+<span class="&#x2005;">FOUR-PER-EM SPACE</span>
+<span class="&#x2006;">SIX-PER-EM SPACE</span>
+<span class="&#x2007;">FIGURE SPACE</span>
+<span class="&#x2008;">PUNCTUATION SPACE</span>
+<span class="&#x2009;">THIN SPACE</span>
+<span class="&#x200A;">HAIR SPACE</span>
+<span class="&#x2028;">LINE SEPARATOR</span>
+<span class="&#x2029;">PARAGRAPH SEPARATOR</span>
+<span class="&#x202F;">NARROW NO-BREAK SPACE</span>
+<span class="&#x205F;">MEDIUM MATHEMATICAL SPACE</span>
+<span class="&#x3000;">IDEOGRAPHIC SPACE</span>
+
+<span class="&#x180E;">MONGOLIAN VOWEL SEPARATOR</span>
+<span class="&#x200B;">ZERO WIDTH SPACE</span>
+<span class="&#x200C;">ZERO WIDTH NON-JOINER</span>
+<span class="&#x200D;">ZERO WIDTH JOINER</span>
+<span class="&#x2060;">WORD JOINER</span>
+<span class="&#xFEFF;">ZERO WIDTH NON-BREAKING SPACE</span>
+
+<script>
+"use strict";
+
+const spans = document.querySelectorAll("span");
+
+for (const span of spans) {
+ test(() => {
+ const className = span.getAttribute("class");
+ assert_equals(className.length, 1, "Sanity check: the class name was retrieved and is a single character");
+ const shouldBeSpan = document.getElementsByClassName(className);
+ assert_array_equals(shouldBeSpan, [span]);
+ }, `Passing a ${span.textContent} to getElementsByClassName still finds the span`);
+}
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/getElementsByClassNameFrame.htm b/testing/web-platform/tests/dom/nodes/getElementsByClassNameFrame.htm
new file mode 100644
index 0000000000..544df60a99
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/getElementsByClassNameFrame.htm
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html class=foo>
+<head class=foo>
+<meta charset=utf-8>
+<title>getElementsByClassName</title>
+<body class=foo>
diff --git a/testing/web-platform/tests/dom/nodes/insert-adjacent.html b/testing/web-platform/tests/dom/nodes/insert-adjacent.html
new file mode 100644
index 0000000000..68b6f4ee66
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insert-adjacent.html
@@ -0,0 +1,79 @@
+<!doctype html>
+<meta charset="utf-8">
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#element {
+ display: none;
+}
+</style>
+
+<div id="element"></div>
+<div id="log"></div>
+
+<script>
+var possiblePositions = {
+ 'beforebegin': 'previousSibling'
+ , 'afterbegin': 'firstChild'
+ , 'beforeend': 'lastChild'
+ , 'afterend': 'nextSibling'
+}
+var texts = {
+ 'beforebegin': 'raclette'
+ , 'afterbegin': 'tartiflette'
+ , 'beforeend': 'lasagne'
+ , 'afterend': 'gateau aux pommes'
+}
+
+var el = document.querySelector('#element');
+
+Object.keys(possiblePositions).forEach(function(position) {
+ var div = document.createElement('h3');
+ test(function() {
+ div.id = texts[position];
+ el.insertAdjacentElement(position, div);
+ assert_equals(el[possiblePositions[position]].id, texts[position]);
+ }, 'insertAdjacentElement(' + position + ', ' + div + ' )');
+
+ test(function() {
+ el.insertAdjacentText(position, texts[position]);
+ assert_equals(el[possiblePositions[position]].textContent, texts[position]);
+ }, 'insertAdjacentText(' + position + ', ' + texts[position] + ' )');
+});
+
+test(function() {
+ assert_throws_js(TypeError, function() {
+ el.insertAdjacentElement('afterbegin',
+ document.implementation.createDocumentType("html"))
+ })
+}, 'invalid object argument insertAdjacentElement')
+test(function() {
+ var el = document.implementation.createHTMLDocument().documentElement;
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() {
+ el.insertAdjacentElement('beforebegin', document.createElement('banane'))
+ })
+}, 'invalid caller object insertAdjacentElement')
+test(function() {
+ var el = document.implementation.createHTMLDocument().documentElement;
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() {
+ el.insertAdjacentText('beforebegin', 'tomate farcie')
+ })
+}, 'invalid caller object insertAdjacentText')
+test(function() {
+ var div = document.createElement('h3');
+ assert_throws_dom("SYNTAX_ERR", function() {
+ el.insertAdjacentElement('heeeee', div)
+ })
+}, "invalid syntax for insertAdjacentElement")
+test(function() {
+ assert_throws_dom("SYNTAX_ERR", function() {
+ el.insertAdjacentText('hoooo', 'magret de canard')
+ })
+}, "invalid syntax for insertAdjacentText")
+test(function() {
+ var div = document.createElement('div');
+ assert_equals(div.insertAdjacentElement("beforebegin", el), null);
+}, 'insertAdjacentText should return null');
+
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/mutationobservers.js b/testing/web-platform/tests/dom/nodes/mutationobservers.js
new file mode 100644
index 0000000000..a95529ab39
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/mutationobservers.js
@@ -0,0 +1,76 @@
+// Compares a mutation record to a predefined one
+// mutationToCheck is a mutation record from the user agent
+// expectedRecord is a mutation record minted by the test
+// for expectedRecord, if properties are omitted, they get default ones
+function checkRecords(target, mutationToCheck, expectedRecord) {
+ var mr1;
+ var mr2;
+
+
+ function checkField(property, isArray) {
+ var field = mr2[property];
+ if (isArray === undefined) {
+ isArray = false;
+ }
+ if (field instanceof Function) {
+ field = field();
+ } else if (field === undefined) {
+ if (isArray) {
+ field = new Array();
+ } else {
+ field = null;
+ }
+ }
+ if (isArray) {
+ assert_array_equals(mr1[property], field, property + " didn't match");
+ } else {
+ assert_equals(mr1[property], field, property + " didn't match");
+ }
+ }
+
+ assert_equals(mutationToCheck.length, expectedRecord.length, "mutation records must match");
+ for (var item = 0; item < mutationToCheck.length; item++) {
+ mr1 = mutationToCheck[item];
+ mr2 = expectedRecord[item];
+
+ if (mr2.target instanceof Function) {
+ assert_equals(mr1.target, mr2.target(), "target node must match");
+ } else if (mr2.target !== undefined) {
+ assert_equals(mr1.target, mr2.target, "target node must match");
+ } else {
+ assert_equals(mr1.target, target, "target node must match");
+ }
+
+ checkField("type");
+ checkField("addedNodes", true);
+ checkField("removedNodes", true);
+ checkField("previousSibling");
+ checkField("nextSibling");
+ checkField("attributeName");
+ checkField("attributeNamespace");
+ checkField("oldValue");
+ };
+}
+
+function runMutationTest(node, mutationObserverOptions, mutationRecordSequence, mutationFunction, description, target) {
+ var test = async_test(description);
+
+
+ function moc(mrl, obs) {
+ test.step(
+ function () {
+ if (target === undefined) target = node;
+ checkRecords(target, mrl, mutationRecordSequence);
+ test.done();
+ }
+ );
+ }
+
+ test.step(
+ function () {
+ (new MutationObserver(moc)).observe(node, mutationObserverOptions);
+ mutationFunction();
+ }
+ );
+ return mutationRecordSequence.length
+}
diff --git a/testing/web-platform/tests/dom/nodes/node-appendchild-crash.html b/testing/web-platform/tests/dom/nodes/node-appendchild-crash.html
new file mode 100644
index 0000000000..245de87f2d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/node-appendchild-crash.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://crbug.com/1210480">
+<meta name="assert" content="The renderer should not crash.">
+
+<iframe id=iframe></iframe>
+<select>Text Node
+ <option id=option></option>
+</select>
+
+<script>
+ window.onload=function() {
+ iframe.addEventListener('DOMNodeInsertedIntoDocument',function() {});
+ option.remove();
+ iframe.contentDocument.body.appendChild(document.body);
+ }
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/pre-insertion-validation-hierarchy.js b/testing/web-platform/tests/dom/nodes/pre-insertion-validation-hierarchy.js
new file mode 100644
index 0000000000..6ef2576df2
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/pre-insertion-validation-hierarchy.js
@@ -0,0 +1,86 @@
+/**
+ * Validations where `child` argument is irrelevant.
+ * @param {Function} methodName
+ */
+function preInsertionValidateHierarchy(methodName) {
+ function insert(parent, node) {
+ if (parent[methodName].length > 1) {
+ // This is for insertBefore(). We can't blindly pass `null` for all methods
+ // as doing so will move nodes before validation.
+ parent[methodName](node, null);
+ } else {
+ parent[methodName](node);
+ }
+ }
+
+ // Step 2
+ test(() => {
+ const doc = document.implementation.createHTMLDocument("title");
+ assert_throws_dom("HierarchyRequestError", () => insert(doc.body, doc.body));
+ assert_throws_dom("HierarchyRequestError", () => insert(doc.body, doc.documentElement));
+ }, "If node is a host-including inclusive ancestor of parent, then throw a HierarchyRequestError DOMException.");
+
+ // Step 4
+ test(() => {
+ const doc = document.implementation.createHTMLDocument("title");
+ const doc2 = document.implementation.createHTMLDocument("title2");
+ assert_throws_dom("HierarchyRequestError", () => insert(doc, doc2));
+ }, "If node is not a DocumentFragment, DocumentType, Element, Text, ProcessingInstruction, or Comment node, then throw a HierarchyRequestError DOMException.");
+
+ // Step 5, in case of inserting a text node into a document
+ test(() => {
+ const doc = document.implementation.createHTMLDocument("title");
+ assert_throws_dom("HierarchyRequestError", () => insert(doc, doc.createTextNode("text")));
+ }, "If node is a Text node and parent is a document, then throw a HierarchyRequestError DOMException.");
+
+ // Step 5, in case of inserting a doctype into a non-document
+ test(() => {
+ const doc = document.implementation.createHTMLDocument("title");
+ const doctype = doc.childNodes[0];
+ assert_throws_dom("HierarchyRequestError", () => insert(doc.createElement("a"), doctype));
+ }, "If node is a doctype and parent is not a document, then throw a HierarchyRequestError DOMException.")
+
+ // Step 6, in case of DocumentFragment including multiple elements
+ test(() => {
+ const doc = document.implementation.createHTMLDocument("title");
+ doc.documentElement.remove();
+ const df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ df.appendChild(doc.createElement("b"));
+ assert_throws_dom("HierarchyRequestError", () => insert(doc, df));
+ }, "If node is a DocumentFragment with multiple elements and parent is a document, then throw a HierarchyRequestError DOMException.");
+
+ // Step 6, in case of DocumentFragment has multiple elements when document already has an element
+ test(() => {
+ const doc = document.implementation.createHTMLDocument("title");
+ const df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ assert_throws_dom("HierarchyRequestError", () => insert(doc, df));
+ }, "If node is a DocumentFragment with an element and parent is a document with another element, then throw a HierarchyRequestError DOMException.");
+
+ // Step 6, in case of an element
+ test(() => {
+ const doc = document.implementation.createHTMLDocument("title");
+ const el = doc.createElement("a");
+ assert_throws_dom("HierarchyRequestError", () => insert(doc, el));
+ }, "If node is an Element and parent is a document with another element, then throw a HierarchyRequestError DOMException.");
+
+ // Step 6, in case of a doctype when document already has another doctype
+ test(() => {
+ const doc = document.implementation.createHTMLDocument("title");
+ const doctype = doc.childNodes[0].cloneNode();
+ doc.documentElement.remove();
+ assert_throws_dom("HierarchyRequestError", () => insert(doc, doctype));
+ }, "If node is a doctype and parent is a document with another doctype, then throw a HierarchyRequestError DOMException.");
+
+ // Step 6, in case of a doctype when document has an element
+ if (methodName !== "prepend") {
+ // Skip `.prepend` as this doesn't throw if `child` is an element
+ test(() => {
+ const doc = document.implementation.createHTMLDocument("title");
+ const doctype = doc.childNodes[0].cloneNode();
+ doc.childNodes[0].remove();
+ assert_throws_dom("HierarchyRequestError", () => insert(doc, doctype));
+ }, "If node is a doctype and parent is a document with an element, then throw a HierarchyRequestError DOMException.");
+ }
+}
diff --git a/testing/web-platform/tests/dom/nodes/pre-insertion-validation-notfound.js b/testing/web-platform/tests/dom/nodes/pre-insertion-validation-notfound.js
new file mode 100644
index 0000000000..705283fa23
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/pre-insertion-validation-notfound.js
@@ -0,0 +1,108 @@
+function getNonParentNodes() {
+ return [
+ document.implementation.createDocumentType("html", "", ""),
+ document.createTextNode("text"),
+ document.implementation.createDocument(null, "foo", null).createProcessingInstruction("foo", "bar"),
+ document.createComment("comment"),
+ document.implementation.createDocument(null, "foo", null).createCDATASection("data"),
+ ];
+}
+
+function getNonInsertableNodes() {
+ return [
+ document.implementation.createHTMLDocument("title")
+ ];
+}
+
+function getNonDocumentParentNodes() {
+ return [
+ document.createElement("div"),
+ document.createDocumentFragment(),
+ ];
+}
+
+// Test that the steps happen in the right order, to the extent that it's
+// observable. The variable names "parent", "child", and "node" match the
+// corresponding variables in the replaceChild algorithm in these tests.
+
+// Step 1 happens before step 3.
+test(function() {
+ var illegalParents = getNonParentNodes();
+ var child = document.createElement("div");
+ var node = document.createElement("div");
+ illegalParents.forEach(function (parent) {
+ assert_throws_dom("HierarchyRequestError", function() {
+ insertFunc.call(parent, node, child);
+ });
+ });
+}, "Should check the 'parent' type before checking whether 'child' is a child of 'parent'");
+
+// Step 2 happens before step 3.
+test(function() {
+ var parent = document.createElement("div");
+ var child = document.createElement("div");
+ var node = document.createElement("div");
+
+ node.appendChild(parent);
+ assert_throws_dom("HierarchyRequestError", function() {
+ insertFunc.call(parent, node, child);
+ });
+}, "Should check that 'node' is not an ancestor of 'parent' before checking whether 'child' is a child of 'parent'");
+
+// Step 3 happens before step 4.
+test(function() {
+ var parent = document.createElement("div");
+ var child = document.createElement("div");
+
+ var illegalChildren = getNonInsertableNodes();
+ illegalChildren.forEach(function (node) {
+ assert_throws_dom("NotFoundError", function() {
+ insertFunc.call(parent, node, child);
+ });
+ });
+}, "Should check whether 'child' is a child of 'parent' before checking whether 'node' is of a type that can have a parent.");
+
+
+// Step 3 happens before step 5.
+test(function() {
+ var child = document.createElement("div");
+
+ var node = document.createTextNode("");
+ var parent = document.implementation.createDocument(null, "foo", null);
+ assert_throws_dom("NotFoundError", function() {
+ insertFunc.call(parent, node, child);
+ });
+
+ node = document.implementation.createDocumentType("html", "", "");
+ getNonDocumentParentNodes().forEach(function (parent) {
+ assert_throws_dom("NotFoundError", function() {
+ insertFunc.call(parent, node, child);
+ });
+ });
+}, "Should check whether 'child' is a child of 'parent' before checking whether 'node' is of a type that can have a parent of the type that 'parent' is.");
+
+// Step 3 happens before step 6.
+test(function() {
+ var child = document.createElement("div");
+ var parent = document.implementation.createDocument(null, null, null);
+
+ var node = document.createDocumentFragment();
+ node.appendChild(document.createElement("div"));
+ node.appendChild(document.createElement("div"));
+ assert_throws_dom("NotFoundError", function() {
+ insertFunc.call(parent, node, child);
+ });
+
+ node = document.createElement("div");
+ parent.appendChild(document.createElement("div"));
+ assert_throws_dom("NotFoundError", function() {
+ insertFunc.call(parent, node, child);
+ });
+
+ parent.firstChild.remove();
+ parent.appendChild(document.implementation.createDocumentType("html", "", ""));
+ node = document.implementation.createDocumentType("html", "", "")
+ assert_throws_dom("NotFoundError", function() {
+ insertFunc.call(parent, node, child);
+ });
+}, "Should check whether 'child' is a child of 'parent' before checking whether 'node' can be inserted into the document given the kids the document has right now.");
diff --git a/testing/web-platform/tests/dom/nodes/prepend-on-Document.html b/testing/web-platform/tests/dom/nodes/prepend-on-Document.html
new file mode 100644
index 0000000000..1d6d43a463
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/prepend-on-Document.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>DocumentType.prepend</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-parentnode-prepend">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+function test_prepend_on_Document() {
+
+ var node = document.implementation.createDocument(null, null);
+ test(function() {
+ var parent = node.cloneNode();
+ parent.prepend();
+ assert_array_equals(parent.childNodes, []);
+ }, 'Document.prepend() without any argument, on a Document having no child.');
+
+ test(function() {
+ var parent = node.cloneNode();
+ var x = document.createElement('x');
+ parent.prepend(x);
+ assert_array_equals(parent.childNodes, [x]);
+ }, 'Document.prepend() with only one element as an argument, on a Document having no child.');
+
+ test(function() {
+ var parent = node.cloneNode();
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ parent.appendChild(x);
+ assert_throws_dom('HierarchyRequestError', function() { parent.prepend(y); });
+ assert_array_equals(parent.childNodes, [x]);
+ }, 'Document.append() with only one element as an argument, on a Document having one child.');
+
+ test(function() {
+ var parent = node.cloneNode();
+ assert_throws_dom('HierarchyRequestError', function() { parent.prepend('text'); });
+ assert_array_equals(parent.childNodes, []);
+ }, 'Document.prepend() with text as an argument, on a Document having no child.');
+
+ test(function() {
+ var parent = node.cloneNode();
+ var x = document.createElement('x');
+ var y = document.createElement('y');
+ assert_throws_dom('HierarchyRequestError', function() { parent.prepend(x, y); });
+ assert_array_equals(parent.childNodes, []);
+ }, 'Document.prepend() with two elements as the argument, on a Document having no child.');
+
+}
+
+test_prepend_on_Document();
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/productions.js b/testing/web-platform/tests/dom/nodes/productions.js
new file mode 100644
index 0000000000..218797fc45
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/productions.js
@@ -0,0 +1,3 @@
+var invalid_names = ["", "invalid^Name", "\\", "'", '"', "0", "0:a"] // XXX
+var valid_names = ["x", "X", ":", "a:0"]
+var invalid_qnames = [":a", "b:", "x:y:z"] // XXX
diff --git a/testing/web-platform/tests/dom/nodes/query-target-in-load-event.html b/testing/web-platform/tests/dom/nodes/query-target-in-load-event.html
new file mode 100644
index 0000000000..2835286f96
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/query-target-in-load-event.html
@@ -0,0 +1,13 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe></iframe>
+
+<script>
+ let test = async_test('document.querySelector(":target") must work when called in the window.load event');
+ let iframe = document.querySelector("iframe");
+ window.addEventListener("message", test.step_func_done(event => {
+ assert_equals(event.data, "PASS");
+ }));
+ iframe.src = "./query-target-in-load-event.part.html#target";
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/query-target-in-load-event.part.html b/testing/web-platform/tests/dom/nodes/query-target-in-load-event.part.html
new file mode 100644
index 0000000000..7eb1baf15f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/query-target-in-load-event.part.html
@@ -0,0 +1,10 @@
+<!-- Used by ./query-target-in-load-event.html -->
+<script>
+ window.onload = function() {
+ let target = document.querySelector(":target");
+ let expected = document.querySelector("#target");
+ window.parent.postMessage(target == expected ? "PASS" : "FAIL", "*");
+ };
+</script>
+
+<div id="target"></div>
diff --git a/testing/web-platform/tests/dom/nodes/remove-and-adopt-thcrash.html b/testing/web-platform/tests/dom/nodes/remove-and-adopt-thcrash.html
new file mode 100644
index 0000000000..d37015ec9f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/remove-and-adopt-thcrash.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>Test for a Chrome crash when adopting a node into another document</title>
+<link rel="help" href="https://crbug.com/981384">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="d1"></div>
+<div id="d2"></div>
+<script>
+ test(() => {
+ d1.appendChild(document.createElement("iframe"));
+ d2.remove();
+ const adopted_div = d1;
+ const popup = window.open();
+ assert_equals(adopted_div.ownerDocument, document);
+ popup.document.body.appendChild(document.body);
+ assert_equals(adopted_div.ownerDocument, popup.document);
+ }, "Check that removing a node and then adopting its parent into a different window/document doesn't crash.");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/remove-from-shadow-host-and-adopt-into-iframe-ref.html b/testing/web-platform/tests/dom/nodes/remove-from-shadow-host-and-adopt-into-iframe-ref.html
new file mode 100644
index 0000000000..98de2b6883
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/remove-from-shadow-host-and-adopt-into-iframe-ref.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<title>DOM Test Reference</title>
+<p>You should see the word PASS below.</p>
+<div>PASS</div>
diff --git a/testing/web-platform/tests/dom/nodes/remove-from-shadow-host-and-adopt-into-iframe.html b/testing/web-platform/tests/dom/nodes/remove-from-shadow-host-and-adopt-into-iframe.html
new file mode 100644
index 0000000000..612aed637d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/remove-from-shadow-host-and-adopt-into-iframe.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html class="reftest-wait">
+ <head>
+ <title>Adopting a shadow host child into an iframe</title>
+ <link rel="help" href="https://dom.spec.whatwg.org/#concept-node-adopt">
+ <link rel="match" href="remove-from-shadow-host-and-adopt-into-iframe-ref.html">
+ <style>
+ iframe { border: 0; }
+ </style>
+ <script src="/common/reftest-wait.js"></script>
+ <script>
+ onload = () => {
+ const root = host.attachShadow({mode:"open"});
+ root.innerHTML = "<slot>";
+ // force a layout
+ host.offsetTop;
+ iframe.contentWindow.document.body.style.margin = 0;
+ iframe.contentWindow.document.body.appendChild(adopted);
+ host.remove();
+ takeScreenshot();
+ }
+ </script>
+ </head>
+ <body>
+ <p>You should see the word PASS below.</p>
+ <iframe id="iframe"></iframe>
+ <div id="host"><span id="adopted">PASS</span></div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/remove-unscopable.html b/testing/web-platform/tests/dom/nodes/remove-unscopable.html
new file mode 100644
index 0000000000..0238b0fa97
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/remove-unscopable.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="testDiv" onclick="result1 = remove; result2 = this.remove;"></div>
+<script>
+var result1;
+var result2;
+var unscopables = [
+ "before",
+ "after",
+ "replaceWith",
+ "remove",
+ "prepend",
+ "append"
+];
+for (var i in unscopables) {
+ var name = unscopables[i];
+ window[name] = "Hello there";
+ result1 = result2 = undefined;
+ test(function () {
+ assert_true(Element.prototype[Symbol.unscopables][name]);
+ var div = document.querySelector('#testDiv');
+ div.setAttribute(
+ "onclick", "result1 = " + name + "; result2 = this." + name + ";");
+ div.dispatchEvent(new Event("click"));
+ assert_equals(typeof result1, "string");
+ assert_equals(typeof result2, "function");
+ }, name + "() should be unscopable");
+}
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/rootNode.html b/testing/web-platform/tests/dom/nodes/rootNode.html
new file mode 100644
index 0000000000..784831da0e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/rootNode.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+<title>Node.prototype.getRootNode()</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-node-getrootnode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+
+test(function () {
+ var shadowHost = document.createElement('div');
+ document.body.appendChild(shadowHost);
+
+ var shadowRoot = shadowHost.attachShadow({mode: 'open'});
+ shadowRoot.innerHTML = '<div class="shadowChild">content</div>';
+
+ var shadowChild = shadowRoot.querySelector('.shadowChild');
+ assert_equals(shadowChild.getRootNode({composed: true}), document, "getRootNode() must return context object's shadow-including root if options's composed is true");
+ assert_equals(shadowChild.getRootNode({composed: false}), shadowRoot, "getRootNode() must return context object's root if options's composed is false");
+ assert_equals(shadowChild.getRootNode(), shadowRoot, "getRootNode() must return context object's root if options's composed is default false");
+
+}, "getRootNode() must return context object's shadow-including root if options's composed is true, and context object's root otherwise");
+
+test(function () {
+ var element = document.createElement('div');
+ assert_equals(element.getRootNode(), element, 'getRootNode() on an element without a parent must return the element itself');
+
+ var text = document.createTextNode('');
+ assert_equals(text.getRootNode(), text, 'getRootNode() on a text node without a parent must return the text node itself');
+
+ var processingInstruction = document.createProcessingInstruction('target', 'data');
+ assert_equals(processingInstruction.getRootNode(), processingInstruction, 'getRootNode() on a processing instruction node without a parent must return the processing instruction node itself');
+
+ assert_equals(document.getRootNode(), document, 'getRootNode() on a document node must return the document itself');
+
+}, 'getRootNode() must return the context object when it does not have any parent');
+
+test(function () {
+ var parent = document.createElement('div');
+
+ var element = document.createElement('div');
+ parent.appendChild(element);
+ assert_equals(element.getRootNode(), parent, 'getRootNode() on an element with a single ancestor must return the parent node');
+
+ var text = document.createTextNode('');
+ parent.appendChild(text);
+ assert_equals(text.getRootNode(), parent, 'getRootNode() on a text node with a single ancestor must return the parent node');
+
+ var processingInstruction = document.createProcessingInstruction('target', 'data');
+ parent.appendChild(processingInstruction)
+ assert_equals(processingInstruction.getRootNode(), parent, 'getRootNode() on a processing instruction node with a single ancestor must return the parent node');
+
+}, 'getRootNode() must return the parent node of the context object when the context object has a single ancestor not in a document');
+
+test(function () {
+ var parent = document.createElement('div');
+ document.body.appendChild(parent);
+
+ var element = document.createElement('div');
+ parent.appendChild(element);
+ assert_equals(element.getRootNode(), document, 'getRootNode() on an element inside a document must return the document');
+
+ var text = document.createTextNode('');
+ parent.appendChild(text);
+ assert_equals(text.getRootNode(), document, 'getRootNode() on a text node inside a document must return the document');
+
+ var processingInstruction = document.createProcessingInstruction('target', 'data');
+ parent.appendChild(processingInstruction)
+ assert_equals(processingInstruction.getRootNode(), document, 'getRootNode() on a processing instruction node inside a document must return the document');
+}, 'getRootNode() must return the document when a node is in document');
+
+test(function () {
+ var fragment = document.createDocumentFragment();
+ var parent = document.createElement('div');
+ fragment.appendChild(parent);
+
+ var element = document.createElement('div');
+ parent.appendChild(element);
+ assert_equals(element.getRootNode(), fragment, 'getRootNode() on an element inside a document fragment must return the fragment');
+
+ var text = document.createTextNode('');
+ parent.appendChild(text);
+ assert_equals(text.getRootNode(), fragment, 'getRootNode() on a text node inside a document fragment must return the fragment');
+
+ var processingInstruction = document.createProcessingInstruction('target', 'data');
+ parent.appendChild(processingInstruction)
+ assert_equals(processingInstruction.getRootNode(), fragment,
+ 'getRootNode() on a processing instruction node inside a document fragment must return the fragment');
+}, 'getRootNode() must return a document fragment when a node is in the fragment');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/nodes/selectors.js b/testing/web-platform/tests/dom/nodes/selectors.js
new file mode 100644
index 0000000000..5e05547c9a
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/selectors.js
@@ -0,0 +1,755 @@
+// Bit-mapped flags to indicate which tests the selector is suitable for
+var TEST_QSA = 0x01; // querySelector() and querySelectorAll() tests
+var TEST_FIND = 0x04; // find() and findAll() tests, may be unsuitable for querySelector[All]
+var TEST_MATCH = 0x10; // matches() tests
+
+/*
+ * All of these invalid selectors should result in a SyntaxError being thrown by the APIs.
+ *
+ * name: A descriptive name of the selector being tested
+ * selector: The selector to test
+ */
+var invalidSelectors = [
+ {name: "Empty String", selector: ""},
+ {name: "Invalid character", selector: "["},
+ {name: "Invalid character", selector: "]"},
+ {name: "Invalid character", selector: "("},
+ {name: "Invalid character", selector: ")"},
+ {name: "Invalid character", selector: "{"},
+ {name: "Invalid character", selector: "}"},
+ {name: "Invalid character", selector: "<"},
+ {name: "Invalid character", selector: ">"},
+ {name: "Invalid ID", selector: "#"},
+ {name: "Invalid group of selectors", selector: "div,"},
+ {name: "Invalid class", selector: "."},
+ {name: "Invalid class", selector: ".5cm"},
+ {name: "Invalid class", selector: "..test"},
+ {name: "Invalid class", selector: ".foo..quux"},
+ {name: "Invalid class", selector: ".bar."},
+ {name: "Invalid combinator", selector: "div % address, p"},
+ {name: "Invalid combinator", selector: "div ++ address, p"},
+ {name: "Invalid combinator", selector: "div ~~ address, p"},
+ {name: "Invalid [att=value] selector", selector: "[*=test]"},
+ {name: "Invalid [att=value] selector", selector: "[*|*=test]"},
+ {name: "Invalid [att=value] selector", selector: "[class= space unquoted ]"},
+ {name: "Unknown pseudo-class", selector: "div:example"},
+ {name: "Unknown pseudo-class", selector: ":example"},
+ {name: "Unknown pseudo-class", selector: "div:linkexample"},
+ {name: "Unknown pseudo-element", selector: "div::example"},
+ {name: "Unknown pseudo-element", selector: "::example"},
+ {name: "Invalid pseudo-element", selector: ":::before"},
+ {name: "Invalid pseudo-element", selector: ":: before"},
+ {name: "Undeclared namespace", selector: "ns|div"},
+ {name: "Undeclared namespace", selector: ":not(ns|div)"},
+ {name: "Invalid namespace", selector: "^|div"},
+ {name: "Invalid namespace", selector: "$|div"},
+ {name: "Relative selector", selector: ">*"},
+];
+
+/*
+ * All of these should be valid selectors, expected to match zero or more elements in the document.
+ * None should throw any errors.
+ *
+ * name: A descriptive name of the selector being tested
+ * selector: The selector to test
+ * expect: A list of IDs of the elements expected to be matched. List must be given in tree order.
+ * exclude: An array of contexts to exclude from testing. The valid values are:
+ * ["document", "element", "fragment", "detached", "html", "xhtml"]
+ * The "html" and "xhtml" values represent the type of document being queried. These are useful
+ * for tests that are affected by differences between HTML and XML, such as case sensitivity.
+ * level: An integer indicating the CSS or Selectors level in which the selector being tested was introduced.
+ * testType: A bit-mapped flag indicating the type of test.
+ *
+ * Note: Interactive pseudo-classes (:active :hover and :focus) have not been tested in this test suite.
+ */
+var validSelectors = [
+ // Type Selector
+ {name: "Type selector, matching html element", selector: "html", expect: ["html"], exclude: ["element", "fragment", "detached"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Type selector, matching html element", selector: "html", expect: [] /*no matches*/, exclude: ["document"], level: 1, testType: TEST_QSA},
+ {name: "Type selector, matching body element", selector: "body", expect: ["body"], exclude: ["element", "fragment", "detached"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Type selector, matching body element", selector: "body", expect: [] /*no matches*/, exclude: ["document"], level: 1, testType: TEST_QSA},
+
+ // Universal Selector
+ {name: "Universal selector, matching all elements", selector: "*", expect: ["universal", "universal-p1", "universal-code1", "universal-hr1", "universal-pre1", "universal-span1", "universal-p2", "universal-a1", "universal-address1", "universal-code2", "universal-a2"], level: 2, testType: TEST_MATCH},
+ {name: "Universal selector, matching all children of element with specified ID", selector: "#universal>*", expect: ["universal-p1", "universal-hr1", "universal-pre1", "universal-p2", "universal-address1"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Universal selector, matching all grandchildren of element with specified ID", selector: "#universal>*>*", expect: ["universal-code1", "universal-span1", "universal-a1", "universal-code2"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Universal selector, matching all children of empty element with specified ID", selector: "#empty>*", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: "Universal selector, matching all descendants of element with specified ID", selector: "#universal *", expect: ["universal-p1", "universal-code1", "universal-hr1", "universal-pre1", "universal-span1", "universal-p2", "universal-a1", "universal-address1", "universal-code2", "universal-a2"], level: 2, testType: TEST_QSA | TEST_MATCH},
+
+ // Attribute Selectors
+ // - presence [att]
+ {name: "Attribute presence selector, matching align attribute with value", selector: ".attr-presence-div1[align]", expect: ["attr-presence-div1"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute presence selector, matching align attribute with empty value", selector: ".attr-presence-div2[align]", expect: ["attr-presence-div2"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute presence selector, matching title attribute, case insensitivity", selector: "#attr-presence [*|TiTlE]", expect: ["attr-presence-a1", "attr-presence-span1", "attr-presence-i1"], exclude: ["xhtml"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute presence selector, not matching title attribute, case sensitivity", selector: "#attr-presence [*|TiTlE]", expect: [], exclude: ["html"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute presence selector, matching custom data-* attribute", selector: "[data-attr-presence]", expect: ["attr-presence-pre1", "attr-presence-blockquote1"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute presence selector, not matching attribute with similar name", selector: ".attr-presence-div3[align], .attr-presence-div4[align]", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: "Attribute presence selector, matching attribute with non-ASCII characters", selector: "ul[data-中文]", expect: ["attr-presence-ul1"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute presence selector, not matching default option without selected attribute", selector: "#attr-presence-select1 option[selected]", expect: [] /* no matches */, level: 2, testType: TEST_QSA},
+ {name: "Attribute presence selector, matching option with selected attribute", selector: "#attr-presence-select2 option[selected]", expect: ["attr-presence-select2-option4"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute presence selector, matching multiple options with selected attributes", selector: "#attr-presence-select3 option[selected]", expect: ["attr-presence-select3-option2", "attr-presence-select3-option3"], level: 2, testType: TEST_QSA | TEST_MATCH},
+
+ // - value [att=val]
+ {name: "Attribute value selector, matching align attribute with value", selector: "#attr-value [align=\"center\"]", expect: ["attr-value-div1"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute value selector, matching align attribute with value, unclosed bracket", selector: "#attr-value [align=\"center\"", expect: ["attr-value-div1"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute value selector, matching align attribute with empty value", selector: "#attr-value [align=\"\"]", expect: ["attr-value-div2"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute value selector, not matching align attribute with partial value", selector: "#attr-value [align=\"c\"]", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: "Attribute value selector, not matching align attribute with incorrect value", selector: "#attr-value [align=\"centera\"]", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: "Attribute value selector, matching custom data-* attribute with unicode escaped value", selector: "[data-attr-value=\"\\e9\"]", expect: ["attr-value-div3"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute value selector, matching custom data-* attribute with escaped character", selector: "[data-attr-value\_foo=\"\\e9\"]", expect: ["attr-value-div4"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute value selector with single-quoted value, matching multiple inputs with type attributes", selector: "#attr-value input[type='hidden'],#attr-value input[type='radio']", expect: ["attr-value-input3", "attr-value-input4", "attr-value-input6", "attr-value-input8", "attr-value-input9"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute value selector with double-quoted value, matching multiple inputs with type attributes", selector: "#attr-value input[type=\"hidden\"],#attr-value input[type='radio']", expect: ["attr-value-input3", "attr-value-input4", "attr-value-input6", "attr-value-input8", "attr-value-input9"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute value selector with unquoted value, matching multiple inputs with type attributes", selector: "#attr-value input[type=hidden],#attr-value input[type=radio]", expect: ["attr-value-input3", "attr-value-input4", "attr-value-input6", "attr-value-input8", "attr-value-input9"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute value selector, matching attribute with value using non-ASCII characters", selector: "[data-attr-value=中文]", expect: ["attr-value-div5"], level: 2, testType: TEST_QSA | TEST_MATCH},
+
+ // - whitespace-separated list [att~=val]
+ {name: "Attribute whitespace-separated list selector, matching class attribute with value", selector: "#attr-whitespace [class~=\"div1\"]", expect: ["attr-whitespace-div1"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute whitespace-separated list selector, not matching class attribute with empty value", selector: "#attr-whitespace [class~=\"\"]", expect: [] /*no matches*/ , level: 2, testType: TEST_QSA},
+ {name: "Attribute whitespace-separated list selector, not matching class attribute with partial value", selector: "[data-attr-whitespace~=\"div\"]", expect: [] /*no matches*/ , level: 2, testType: TEST_QSA},
+ {name: "Attribute whitespace-separated list selector, matching custom data-* attribute with unicode escaped value", selector: "[data-attr-whitespace~=\"\\0000e9\"]", expect: ["attr-whitespace-div4"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute whitespace-separated list selector, matching custom data-* attribute with escaped character", selector: "[data-attr-whitespace\_foo~=\"\\e9\"]", expect: ["attr-whitespace-div5"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute whitespace-separated list selector with single-quoted value, matching multiple links with rel attributes", selector: "#attr-whitespace a[rel~='bookmark'], #attr-whitespace a[rel~='nofollow']", expect: ["attr-whitespace-a1", "attr-whitespace-a2", "attr-whitespace-a3", "attr-whitespace-a5", "attr-whitespace-a7"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute whitespace-separated list selector with double-quoted value, matching multiple links with rel attributes", selector: "#attr-whitespace a[rel~=\"bookmark\"],#attr-whitespace a[rel~='nofollow']", expect: ["attr-whitespace-a1", "attr-whitespace-a2", "attr-whitespace-a3", "attr-whitespace-a5", "attr-whitespace-a7"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute whitespace-separated list selector with unquoted value, matching multiple links with rel attributes", selector: "#attr-whitespace a[rel~=bookmark], #attr-whitespace a[rel~=nofollow]", expect: ["attr-whitespace-a1", "attr-whitespace-a2", "attr-whitespace-a3", "attr-whitespace-a5", "attr-whitespace-a7"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute whitespace-separated list selector with double-quoted value, not matching value with space", selector: "#attr-whitespace a[rel~=\"book mark\"]", expect: [] /* no matches */, level: 2, testType: TEST_QSA},
+ {name: "Attribute whitespace-separated list selector, matching title attribute with value using non-ASCII characters", selector: "#attr-whitespace [title~=中文]", expect: ["attr-whitespace-p1"], level: 2, testType: TEST_QSA | TEST_MATCH},
+
+ // - hyphen-separated list [att|=val]
+ {name: "Attribute hyphen-separated list selector, not matching unspecified lang attribute", selector: "#attr-hyphen-div1[lang|=\"en\"]", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: "Attribute hyphen-separated list selector, matching lang attribute with exact value", selector: "#attr-hyphen-div2[lang|=\"fr\"]", expect: ["attr-hyphen-div2"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute hyphen-separated list selector, matching lang attribute with partial value", selector: "#attr-hyphen-div3[lang|=\"en\"]", expect: ["attr-hyphen-div3"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute hyphen-separated list selector, not matching incorrect value", selector: "#attr-hyphen-div4[lang|=\"es-AR\"]", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+
+ // - substring begins-with [att^=val] (Level 3)
+ {name: "Attribute begins with selector, matching href attributes beginning with specified substring", selector: "#attr-begins a[href^=\"http://www\"]", expect: ["attr-begins-a1", "attr-begins-a3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute begins with selector, matching lang attributes beginning with specified substring, ", selector: "#attr-begins [lang^=\"en-\"]", expect: ["attr-begins-div2", "attr-begins-div4"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute begins with selector, not matching class attribute with empty value", selector: "#attr-begins [class^=\"\"]", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+ {name: "Attribute begins with selector, not matching class attribute not beginning with specified substring", selector: "#attr-begins [class^=apple]", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+ {name: "Attribute begins with selector with single-quoted value, matching class attribute beginning with specified substring", selector: "#attr-begins [class^=' apple']", expect: ["attr-begins-p1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute begins with selector with double-quoted value, matching class attribute beginning with specified substring", selector: "#attr-begins [class^=\" apple\"]", expect: ["attr-begins-p1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute begins with selector with unquoted value, not matching class attribute not beginning with specified substring", selector: "#attr-begins [class^= apple]", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+
+ // - substring ends-with [att$=val] (Level 3)
+ {name: "Attribute ends with selector, matching href attributes ending with specified substring", selector: "#attr-ends a[href$=\".org\"]", expect: ["attr-ends-a1", "attr-ends-a3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute ends with selector, matching lang attributes ending with specified substring, ", selector: "#attr-ends [lang$=\"-CH\"]", expect: ["attr-ends-div2", "attr-ends-div4"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute ends with selector, not matching class attribute with empty value", selector: "#attr-ends [class$=\"\"]", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+ {name: "Attribute ends with selector, not matching class attribute not ending with specified substring", selector: "#attr-ends [class$=apple]", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+ {name: "Attribute ends with selector with single-quoted value, matching class attribute ending with specified substring", selector: "#attr-ends [class$='apple ']", expect: ["attr-ends-p1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute ends with selector with double-quoted value, matching class attribute ending with specified substring", selector: "#attr-ends [class$=\"apple \"]", expect: ["attr-ends-p1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute ends with selector with unquoted value, not matching class attribute not ending with specified substring", selector: "#attr-ends [class$=apple ]", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+
+ // - substring contains [att*=val] (Level 3)
+ {name: "Attribute contains selector, matching href attributes beginning with specified substring", selector: "#attr-contains a[href*=\"http://www\"]", expect: ["attr-contains-a1", "attr-contains-a3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute contains selector, matching href attributes ending with specified substring", selector: "#attr-contains a[href*=\".org\"]", expect: ["attr-contains-a1", "attr-contains-a2"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute contains selector, matching href attributes containing specified substring", selector: "#attr-contains a[href*=\".example.\"]", expect: ["attr-contains-a1", "attr-contains-a3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute contains selector, matching lang attributes beginning with specified substring, ", selector: "#attr-contains [lang*=\"en-\"]", expect: ["attr-contains-div2", "attr-contains-div6"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute contains selector, matching lang attributes ending with specified substring, ", selector: "#attr-contains [lang*=\"-CH\"]", expect: ["attr-contains-div3", "attr-contains-div5"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute contains selector, not matching class attribute with empty value", selector: "#attr-contains [class*=\"\"]", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+ {name: "Attribute contains selector with single-quoted value, matching class attribute beginning with specified substring", selector: "#attr-contains [class*=' apple']", expect: ["attr-contains-p1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute contains selector with single-quoted value, matching class attribute ending with specified substring", selector: "#attr-contains [class*='orange ']", expect: ["attr-contains-p1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute contains selector with single-quoted value, matching class attribute containing specified substring", selector: "#attr-contains [class*='ple banana ora']", expect: ["attr-contains-p1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute contains selector with double-quoted value, matching class attribute beginning with specified substring", selector: "#attr-contains [class*=\" apple\"]", expect: ["attr-contains-p1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute contains selector with double-quoted value, matching class attribute ending with specified substring", selector: "#attr-contains [class*=\"orange \"]", expect: ["attr-contains-p1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute contains selector with double-quoted value, matching class attribute containing specified substring", selector: "#attr-contains [class*=\"ple banana ora\"]", expect: ["attr-contains-p1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute contains selector with unquoted value, matching class attribute beginning with specified substring", selector: "#attr-contains [class*= apple]", expect: ["attr-contains-p1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute contains selector with unquoted value, matching class attribute ending with specified substring", selector: "#attr-contains [class*=orange ]", expect: ["attr-contains-p1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "Attribute contains selector with unquoted value, matching class attribute containing specified substring", selector: "#attr-contains [class*= banana ]", expect: ["attr-contains-p1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // Pseudo-classes
+ // - :root (Level 3)
+ {name: ":root pseudo-class selector, matching document root element", selector: ":root", expect: ["html"], exclude: ["element", "fragment", "detached"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":root pseudo-class selector, not matching document root element", selector: ":root", expect: [] /*no matches*/, exclude: ["document"], level: 3, testType: TEST_QSA},
+
+ // - :nth-child(n) (Level 3)
+ // XXX write descriptions
+ {name: ":nth-child selector, matching the third child element", selector: "#pseudo-nth-table1 :nth-child(3)", expect: ["pseudo-nth-td3", "pseudo-nth-td9", "pseudo-nth-tr3", "pseudo-nth-td15"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":nth-child selector, matching every third child element", selector: "#pseudo-nth li:nth-child(3n)", expect: ["pseudo-nth-li3", "pseudo-nth-li6", "pseudo-nth-li9", "pseudo-nth-li12"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":nth-child selector, matching every second child element, starting from the fourth", selector: "#pseudo-nth li:nth-child(2n+4)", expect: ["pseudo-nth-li4", "pseudo-nth-li6", "pseudo-nth-li8", "pseudo-nth-li10", "pseudo-nth-li12"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":nth-child selector, matching every fourth child element, starting from the third", selector: "#pseudo-nth-p1 :nth-child(4n-1)", expect: ["pseudo-nth-em2", "pseudo-nth-span3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :nth-last-child (Level 3)
+ {name: ":nth-last-child selector, matching the third last child element", selector: "#pseudo-nth-table1 :nth-last-child(3)", expect: ["pseudo-nth-tr1", "pseudo-nth-td4", "pseudo-nth-td10", "pseudo-nth-td16"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":nth-last-child selector, matching every third child element from the end", selector: "#pseudo-nth li:nth-last-child(3n)", expect: ["pseudo-nth-li1", "pseudo-nth-li4", "pseudo-nth-li7", "pseudo-nth-li10"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":nth-last-child selector, matching every second child element from the end, starting from the fourth last", selector: "#pseudo-nth li:nth-last-child(2n+4)", expect: ["pseudo-nth-li1", "pseudo-nth-li3", "pseudo-nth-li5", "pseudo-nth-li7", "pseudo-nth-li9"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":nth-last-child selector, matching every fourth element from the end, starting from the third last", selector: "#pseudo-nth-p1 :nth-last-child(4n-1)", expect: ["pseudo-nth-span2", "pseudo-nth-span4"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :nth-of-type(n) (Level 3)
+ {name: ":nth-of-type selector, matching the third em element", selector: "#pseudo-nth-p1 em:nth-of-type(3)", expect: ["pseudo-nth-em3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":nth-of-type selector, matching every second element of their type", selector: "#pseudo-nth-p1 :nth-of-type(2n)", expect: ["pseudo-nth-em2", "pseudo-nth-span2", "pseudo-nth-span4", "pseudo-nth-strong2", "pseudo-nth-em4"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":nth-of-type selector, matching every second elemetn of their type, starting from the first", selector: "#pseudo-nth-p1 span:nth-of-type(2n-1)", expect: ["pseudo-nth-span1", "pseudo-nth-span3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :nth-last-of-type(n) (Level 3)
+ {name: ":nth-last-of-type selector, matching the third last em element", selector: "#pseudo-nth-p1 em:nth-last-of-type(3)", expect: ["pseudo-nth-em2"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":nth-last-of-type selector, matching every second last element of their type", selector: "#pseudo-nth-p1 :nth-last-of-type(2n)", expect: ["pseudo-nth-span1", "pseudo-nth-em1", "pseudo-nth-strong1", "pseudo-nth-em3", "pseudo-nth-span3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":nth-last-of-type selector, matching every second last element of their type, starting from the last", selector: "#pseudo-nth-p1 span:nth-last-of-type(2n-1)", expect: ["pseudo-nth-span2", "pseudo-nth-span4"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :first-of-type (Level 3)
+ {name: ":first-of-type selector, matching the first em element", selector: "#pseudo-nth-p1 em:first-of-type", expect: ["pseudo-nth-em1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":first-of-type selector, matching the first of every type of element", selector: "#pseudo-nth-p1 :first-of-type", expect: ["pseudo-nth-span1", "pseudo-nth-em1", "pseudo-nth-strong1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":first-of-type selector, matching the first td element in each table row", selector: "#pseudo-nth-table1 tr :first-of-type", expect: ["pseudo-nth-td1", "pseudo-nth-td7", "pseudo-nth-td13"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :last-of-type (Level 3)
+ {name: ":last-of-type selector, matching the last em elemnet", selector: "#pseudo-nth-p1 em:last-of-type", expect: ["pseudo-nth-em4"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":last-of-type selector, matching the last of every type of element", selector: "#pseudo-nth-p1 :last-of-type", expect: ["pseudo-nth-span4", "pseudo-nth-strong2", "pseudo-nth-em4"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":last-of-type selector, matching the last td element in each table row", selector: "#pseudo-nth-table1 tr :last-of-type", expect: ["pseudo-nth-td6", "pseudo-nth-td12", "pseudo-nth-td18"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :first-child
+ {name: ":first-child pseudo-class selector, matching first child div element", selector: "#pseudo-first-child div:first-child", expect: ["pseudo-first-child-div1"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: ":first-child pseudo-class selector, doesn't match non-first-child elements", selector: ".pseudo-first-child-div2:first-child, .pseudo-first-child-div3:first-child", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: ":first-child pseudo-class selector, matching first-child of multiple elements", selector: "#pseudo-first-child span:first-child", expect: ["pseudo-first-child-span1", "pseudo-first-child-span3", "pseudo-first-child-span5"], level: 2, testType: TEST_QSA | TEST_MATCH},
+
+ // - :last-child (Level 3)
+ {name: ":last-child pseudo-class selector, matching last child div element", selector: "#pseudo-last-child div:last-child", expect: ["pseudo-last-child-div3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":last-child pseudo-class selector, doesn't match non-last-child elements", selector: ".pseudo-last-child-div1:last-child, .pseudo-last-child-div2:first-child", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+ {name: ":last-child pseudo-class selector, matching first-child of multiple elements", selector: "#pseudo-last-child span:last-child", expect: ["pseudo-last-child-span2", "pseudo-last-child-span4", "pseudo-last-child-span6"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :only-child (Level 3)
+ {name: ":pseudo-only-child pseudo-class selector, matching all only-child elements", selector: "#pseudo-only :only-child", expect: ["pseudo-only-span1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":pseudo-only-child pseudo-class selector, matching only-child em elements", selector: "#pseudo-only em:only-child", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+
+ // - :only-of-type (Level 3)
+ {name: ":pseudo-only-of-type pseudo-class selector, matching all elements with no siblings of the same type", selector: "#pseudo-only :only-of-type", expect: ["pseudo-only-span1", "pseudo-only-em1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":pseudo-only-of-type pseudo-class selector, matching em elements with no siblings of the same type", selector: "#pseudo-only em:only-of-type", expect: ["pseudo-only-em1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :empty (Level 3)
+ {name: ":empty pseudo-class selector, matching empty p elements", selector: "#pseudo-empty p:empty", expect: ["pseudo-empty-p1", "pseudo-empty-p2"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":empty pseudo-class selector, matching all empty elements", selector: "#pseudo-empty :empty", expect: ["pseudo-empty-p1", "pseudo-empty-p2", "pseudo-empty-span1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :link and :visited
+ // Implementations may treat all visited links as unvisited, so these cannot be tested separately.
+ // The only guarantee is that ":link,:visited" matches the set of all visited and unvisited links and that they are individually mutually exclusive sets.
+ {name: ":link and :visited pseudo-class selectors, matching a and area elements with href attributes", selector: "#pseudo-link :link, #pseudo-link :visited", expect: ["pseudo-link-a1", "pseudo-link-a2", "pseudo-link-area1"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: ":link and :visited pseudo-class selectors, matching no elements", selector: "#head :link, #head :visited", expect: [] /*no matches*/, exclude: ["element", "fragment", "detached"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: ":link and :visited pseudo-class selectors, not matching link elements with href attributes", selector: "#head :link, #head :visited", expect: [] /*no matches*/, exclude: ["document"], level: 1, testType: TEST_QSA},
+ {name: ":link and :visited pseudo-class selectors, chained, mutually exclusive pseudo-classes match nothing", selector: ":link:visited", expect: [] /*no matches*/, exclude: ["document"], level: 1, testType: TEST_QSA},
+
+ // - :target (Level 3)
+ {name: ":target pseudo-class selector, matching the element referenced by the URL fragment identifier", selector: ":target", expect: [] /*no matches*/, exclude: ["document", "element"], level: 3, testType: TEST_QSA},
+ {name: ":target pseudo-class selector, matching the element referenced by the URL fragment identifier", selector: ":target", expect: ["target"], exclude: ["fragment", "detached"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :lang()
+ {name: ":lang pseudo-class selector, matching inherited language", selector: "#pseudo-lang-div1:lang(en)", expect: ["pseudo-lang-div1"], exclude: ["detached", "fragment"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: ":lang pseudo-class selector, not matching element with no inherited language", selector: "#pseudo-lang-div1:lang(en)", expect: [] /*no matches*/, exclude: ["document", "element"], level: 2, testType: TEST_QSA},
+ {name: ":lang pseudo-class selector, matching specified language with exact value", selector: "#pseudo-lang-div2:lang(fr)", expect: ["pseudo-lang-div2"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: ":lang pseudo-class selector, matching specified language with partial value", selector: "#pseudo-lang-div3:lang(en)", expect: ["pseudo-lang-div3"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: ":lang pseudo-class selector, not matching incorrect language", selector: "#pseudo-lang-div4:lang(es-AR)", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+
+ // - :enabled (Level 3)
+ {name: ":enabled pseudo-class selector, matching all enabled form controls", selector: "#pseudo-ui :enabled", expect: ["pseudo-ui-input1", "pseudo-ui-input2", "pseudo-ui-input3", "pseudo-ui-input4", "pseudo-ui-input5", "pseudo-ui-input6",
+ "pseudo-ui-input7", "pseudo-ui-input8", "pseudo-ui-input9", "pseudo-ui-textarea1", "pseudo-ui-button1"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":enabled pseudo-class selector, not matching link elements", selector: "#pseudo-link :enabled", expect: [] /*no matches*/, unexpected: ["pseudo-link-a1","pseudo-link-a2","pseudo-link-a3","pseudo-link-map1","pseudo-link-area1","pseudo-link-area2"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :disabled (Level 3)
+ {name: ":disabled pseudo-class selector, matching all disabled form controls", selector: "#pseudo-ui :disabled", expect: ["pseudo-ui-input10", "pseudo-ui-input11", "pseudo-ui-input12", "pseudo-ui-input13", "pseudo-ui-input14", "pseudo-ui-input15",
+ "pseudo-ui-input16", "pseudo-ui-input17", "pseudo-ui-input18", "pseudo-ui-textarea2", "pseudo-ui-button2"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":disabled pseudo-class selector, not matching link elements", selector: "#pseudo-link :disabled", expect: [] /*no matches*/, unexpected: ["pseudo-link-a1","pseudo-link-a2","pseudo-link-a3","pseudo-link-map1","pseudo-link-area1","pseudo-link-area2"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :checked (Level 3)
+ {name: ":checked pseudo-class selector, matching checked radio buttons and checkboxes", selector: "#pseudo-ui :checked", expect: ["pseudo-ui-input4", "pseudo-ui-input6", "pseudo-ui-input13", "pseudo-ui-input15"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :not(s) (Level 3)
+ {name: ":not pseudo-class selector, matching ", selector: "#not>:not(div)", expect: ["not-p1", "not-p2", "not-p3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":not pseudo-class selector, matching ", selector: "#not * :not(:first-child)", expect: ["not-em1", "not-em2", "not-em3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: ":not pseudo-class selector, matching nothing", selector: ":not(*)", expect: [] /* no matches */, level: 3, testType: TEST_QSA},
+ {name: ":not pseudo-class selector, matching nothing", selector: ":not(*|*)", expect: [] /* no matches */, level: 3, testType: TEST_QSA},
+ {name: ":not pseudo-class selector argument surrounded by spaces, matching ", selector: "#not>:not( div )", expect: ["not-p1", "not-p2", "not-p3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // Pseudo-elements
+ // - ::first-line
+ {name: ":first-line pseudo-element (one-colon syntax) selector, not matching any elements", selector: "#pseudo-element:first-line", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: "::first-line pseudo-element (two-colon syntax) selector, not matching any elements", selector: "#pseudo-element::first-line", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+
+ // - ::first-letter
+ {name: ":first-letter pseudo-element (one-colon syntax) selector, not matching any elements", selector: "#pseudo-element:first-letter", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: "::first-letter pseudo-element (two-colon syntax) selector, not matching any elements", selector: "#pseudo-element::first-letter", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+
+ // - ::before
+ {name: ":before pseudo-element (one-colon syntax) selector, not matching any elements", selector: "#pseudo-element:before", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: "::before pseudo-element (two-colon syntax) selector, not matching any elements", selector: "#pseudo-element::before", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+
+ // - ::after
+ {name: ":after pseudo-element (one-colon syntax) selector, not matching any elements", selector: "#pseudo-element:after", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: "::after pseudo-element (two-colon syntax) selector, not matching any elements", selector: "#pseudo-element::after", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+
+ // Class Selectors
+ {name: "Class selector, matching element with specified class", selector: ".class-p", expect: ["class-p1","class-p2", "class-p3"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Class selector, chained, matching only elements with all specified classes", selector: "#class .apple.orange.banana", expect: ["class-div1", "class-div2", "class-p4", "class-div3", "class-p6", "class-div4"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Class Selector, chained, with type selector", selector: "div.apple.banana.orange", expect: ["class-div1", "class-div2", "class-div3", "class-div4"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Class selector, matching element with class value using non-ASCII characters (1)", selector: ".\u53F0\u5317Ta\u0301ibe\u030Ci", expect: ["class-span1"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Class selector, matching multiple elements with class value using non-ASCII characters", selector: ".\u53F0\u5317", expect: ["class-span1","class-span2"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Class selector, chained, matching element with multiple class values using non-ASCII characters (1)", selector: ".\u53F0\u5317Ta\u0301ibe\u030Ci.\u53F0\u5317", expect: ["class-span1"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Class selector, matching element with class with escaped character", selector: ".foo\\:bar", expect: ["class-span3"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Class selector, matching element with class with escaped character", selector: ".test\\.foo\\[5\\]bar", expect: ["class-span4"], level: 1, testType: TEST_QSA | TEST_MATCH},
+
+ // ID Selectors
+ {name: "ID selector, matching element with specified id", selector: "#id #id-div1", expect: ["id-div1"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "ID selector, chained, matching element with specified id", selector: "#id-div1, #id-div1", expect: ["id-div1"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "ID selector, chained, matching element with specified id", selector: "#id-div1, #id-div2", expect: ["id-div1", "id-div2"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "ID Selector, chained, with type selector", selector: "div#id-div1, div#id-div2", expect: ["id-div1", "id-div2"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "ID selector, not matching non-existent descendant", selector: "#id #none", expect: [] /*no matches*/, level: 1, testType: TEST_QSA},
+ {name: "ID selector, not matching non-existent ancestor", selector: "#none #id-div1", expect: [] /*no matches*/, level: 1, testType: TEST_QSA},
+ {name: "ID selector, matching multiple elements with duplicate id", selector: "#id-li-duplicate", expect: ["id-li-duplicate", "id-li-duplicate", "id-li-duplicate", "id-li-duplicate"], level: 1, testType: TEST_QSA | TEST_MATCH},
+
+ {name: "ID selector, matching id value using non-ASCII characters (1)", selector: "#\u53F0\u5317Ta\u0301ibe\u030Ci", expect: ["\u53F0\u5317Ta\u0301ibe\u030Ci"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "ID selector, matching id value using non-ASCII characters (2)", selector: "#\u53F0\u5317", expect: ["\u53F0\u5317"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "ID selector, matching id values using non-ASCII characters (1)", selector: "#\u53F0\u5317Ta\u0301ibe\u030Ci, #\u53F0\u5317", expect: ["\u53F0\u5317Ta\u0301ibe\u030Ci", "\u53F0\u5317"], level: 1, testType: TEST_QSA | TEST_MATCH},
+
+ // XXX runMatchesTest() in level2-lib.js can't handle this because obtaining the expected nodes requires escaping characters when generating the selector from 'expect' values
+ {name: "ID selector, matching element with id with escaped character", selector: "#\\#foo\\:bar", expect: ["#foo:bar"], level: 1, testType: TEST_QSA},
+ {name: "ID selector, matching element with id with escaped character", selector: "#test\\.foo\\[5\\]bar", expect: ["test.foo[5]bar"], level: 1, testType: TEST_QSA},
+
+ // Namespaces
+ // XXX runMatchesTest() in level2-lib.js can't handle these because non-HTML elements don't have a recognised id
+ {name: "Namespace selector, matching element with any namespace", selector: "#any-namespace *|div", expect: ["any-namespace-div1", "any-namespace-div2", "any-namespace-div3", "any-namespace-div4"], level: 3, testType: TEST_QSA},
+ {name: "Namespace selector, matching div elements in no namespace only", selector: "#no-namespace |div", expect: ["no-namespace-div3"], level: 3, testType: TEST_QSA},
+ {name: "Namespace selector, matching any elements in no namespace only", selector: "#no-namespace |*", expect: ["no-namespace-div3"], level: 3, testType: TEST_QSA},
+
+ // Combinators
+ // - Descendant combinator ' '
+ {name: "Descendant combinator, matching element that is a descendant of an element with id", selector: "#descendant div", expect: ["descendant-div1", "descendant-div2", "descendant-div3", "descendant-div4"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Descendant combinator, matching element with id that is a descendant of an element", selector: "body #descendant-div1", expect: ["descendant-div1"], exclude: ["detached", "fragment"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Descendant combinator, matching element with id that is a descendant of an element", selector: "div #descendant-div1", expect: ["descendant-div1"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Descendant combinator, matching element with id that is a descendant of an element with id", selector: "#descendant #descendant-div2", expect: ["descendant-div2"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Descendant combinator, matching element with class that is a descendant of an element with id", selector: "#descendant .descendant-div2", expect: ["descendant-div2"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Descendant combinator, matching element with class that is a descendant of an element with class", selector: ".descendant-div1 .descendant-div3", expect: ["descendant-div3"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Descendant combinator, not matching element with id that is not a descendant of an element with id", selector: "#descendant-div1 #descendant-div4", expect: [] /*no matches*/, level: 1, testType: TEST_QSA},
+ {name: "Descendant combinator, whitespace characters", selector: "#descendant\t\r\n#descendant-div2", expect: ["descendant-div2"], level: 1, testType: TEST_QSA | TEST_MATCH},
+
+ /* The future of this combinator is uncertain, see
+ * https://github.com/w3c/csswg-drafts/issues/641
+ * These tests are commented out until a final decision is made on whether to
+ * keep the feature in the spec.
+ */
+
+ // // - Descendant combinator '>>'
+ // {name: "Descendant combinator '>>', matching element that is a descendant of an element with id", selector: "#descendant>>div", expect: ["descendant-div1", "descendant-div2", "descendant-div3", "descendant-div4"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with id that is a descendant of an element", selector: "body>>#descendant-div1", expect: ["descendant-div1"], exclude: ["detached", "fragment"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with id that is a descendant of an element", selector: "div>>#descendant-div1", expect: ["descendant-div1"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with id that is a descendant of an element with id", selector: "#descendant>>#descendant-div2", expect: ["descendant-div2"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with class that is a descendant of an element with id", selector: "#descendant>>.descendant-div2", expect: ["descendant-div2"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with class that is a descendant of an element with class", selector: ".descendant-div1>>.descendant-div3", expect: ["descendant-div3"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ // {name: "Descendant combinator '>>', not matching element with id that is not a descendant of an element with id", selector: "#descendant-div1>>#descendant-div4", expect: [] /*no matches*/, level: 1, testType: TEST_QSA},
+
+ // - Child combinator '>'
+ {name: "Child combinator, matching element that is a child of an element with id", selector: "#child>div", expect: ["child-div1", "child-div4"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Child combinator, matching element with id that is a child of an element", selector: "div>#child-div1", expect: ["child-div1"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Child combinator, matching element with id that is a child of an element with id", selector: "#child>#child-div1", expect: ["child-div1"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Child combinator, matching element with id that is a child of an element with class", selector: "#child-div1>.child-div2", expect: ["child-div2"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Child combinator, matching element with class that is a child of an element with class", selector: ".child-div1>.child-div2", expect: ["child-div2"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Child combinator, not matching element with id that is not a child of an element with id", selector: "#child>#child-div3", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: "Child combinator, not matching element with id that is not a child of an element with class", selector: "#child-div1>.child-div3", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: "Child combinator, not matching element with class that is not a child of an element with class", selector: ".child-div1>.child-div3", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: "Child combinator, surrounded by whitespace", selector: "#child-div1\t\r\n>\t\r\n#child-div2", expect: ["child-div2"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Child combinator, whitespace after", selector: "#child-div1>\t\r\n#child-div2", expect: ["child-div2"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Child combinator, whitespace before", selector: "#child-div1\t\r\n>#child-div2", expect: ["child-div2"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Child combinator, no whitespace", selector: "#child-div1>#child-div2", expect: ["child-div2"], level: 2, testType: TEST_QSA | TEST_MATCH},
+
+ // - Adjacent sibling combinator '+'
+ {name: "Adjacent sibling combinator, matching element that is an adjacent sibling of an element with id", selector: "#adjacent-div2+div", expect: ["adjacent-div4"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Adjacent sibling combinator, matching element with id that is an adjacent sibling of an element", selector: "div+#adjacent-div4", expect: ["adjacent-div4"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Adjacent sibling combinator, matching element with id that is an adjacent sibling of an element with id", selector: "#adjacent-div2+#adjacent-div4", expect: ["adjacent-div4"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Adjacent sibling combinator, matching element with class that is an adjacent sibling of an element with id", selector: "#adjacent-div2+.adjacent-div4", expect: ["adjacent-div4"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Adjacent sibling combinator, matching element with class that is an adjacent sibling of an element with class", selector: ".adjacent-div2+.adjacent-div4", expect: ["adjacent-div4"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Adjacent sibling combinator, matching p element that is an adjacent sibling of a div element", selector: "#adjacent div+p", expect: ["adjacent-p2"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Adjacent sibling combinator, not matching element with id that is not an adjacent sibling of an element with id", selector: "#adjacent-div2+#adjacent-p2, #adjacent-div2+#adjacent-div1", expect: [] /*no matches*/, level: 2, testType: TEST_QSA},
+ {name: "Adjacent sibling combinator, surrounded by whitespace", selector: "#adjacent-p2\t\r\n+\t\r\n#adjacent-p3", expect: ["adjacent-p3"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Adjacent sibling combinator, whitespace after", selector: "#adjacent-p2+\t\r\n#adjacent-p3", expect: ["adjacent-p3"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Adjacent sibling combinator, whitespace before", selector: "#adjacent-p2\t\r\n+#adjacent-p3", expect: ["adjacent-p3"], level: 2, testType: TEST_QSA | TEST_MATCH},
+ {name: "Adjacent sibling combinator, no whitespace", selector: "#adjacent-p2+#adjacent-p3", expect: ["adjacent-p3"], level: 2, testType: TEST_QSA | TEST_MATCH},
+
+ // - General sibling combinator ~ (Level 3)
+ {name: "General sibling combinator, matching element that is a sibling of an element with id", selector: "#sibling-div2~div", expect: ["sibling-div4", "sibling-div6"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "General sibling combinator, matching element with id that is a sibling of an element", selector: "div~#sibling-div4", expect: ["sibling-div4"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "General sibling combinator, matching element with id that is a sibling of an element with id", selector: "#sibling-div2~#sibling-div4", expect: ["sibling-div4"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "General sibling combinator, matching element with class that is a sibling of an element with id", selector: "#sibling-div2~.sibling-div", expect: ["sibling-div4", "sibling-div6"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "General sibling combinator, matching p element that is a sibling of a div element", selector: "#sibling div~p", expect: ["sibling-p2", "sibling-p3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "General sibling combinator, not matching element with id that is not a sibling after a p element", selector: "#sibling>p~div", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+ {name: "General sibling combinator, not matching element with id that is not a sibling after an element with id", selector: "#sibling-div2~#sibling-div3, #sibling-div2~#sibling-div1", expect: [] /*no matches*/, level: 3, testType: TEST_QSA},
+ {name: "General sibling combinator, surrounded by whitespace", selector: "#sibling-p2\t\r\n~\t\r\n#sibling-p3", expect: ["sibling-p3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "General sibling combinator, whitespace after", selector: "#sibling-p2~\t\r\n#sibling-p3", expect: ["sibling-p3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "General sibling combinator, whitespace before", selector: "#sibling-p2\t\r\n~#sibling-p3", expect: ["sibling-p3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+ {name: "General sibling combinator, no whitespace", selector: "#sibling-p2~#sibling-p3", expect: ["sibling-p3"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // Group of selectors (comma)
+ {name: "Syntax, group of selectors separator, surrounded by whitespace", selector: "#group em\t\r \n,\t\r \n#group strong", expect: ["group-em1", "group-strong1"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Syntax, group of selectors separator, whitespace after", selector: "#group em,\t\r\n#group strong", expect: ["group-em1", "group-strong1"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Syntax, group of selectors separator, whitespace before", selector: "#group em\t\r\n,#group strong", expect: ["group-em1", "group-strong1"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ {name: "Syntax, group of selectors separator, no whitespace", selector: "#group em,#group strong", expect: ["group-em1", "group-strong1"], level: 1, testType: TEST_QSA | TEST_MATCH},
+
+ // ::slotted (shouldn't match anything, but is a valid selector)
+ {name: "Slotted selector", selector: "::slotted(foo)", expect: [], level: 3, testType: TEST_QSA},
+ {name: "Slotted selector (no matching closing paren)", selector: "::slotted(foo", expect: [], level: 3, testType: TEST_QSA},
+];
+
+
+/*
+ * These selectors are intended to be used with the find(), findAll() and matches() methods. Expected results
+ * should be determined under the assumption that :scope will be prepended to the selector where appropriate,
+ * in accordance with the specification.
+ *
+ * All of these should be valid relative selectors, expected to match zero or more elements in the document.
+ * None should throw any errors.
+ *
+ * name: A descriptive name of the selector being tested
+ *
+ * selector: The selector to test
+ *
+ * ctx: A selector to obtain the context object to use for tests invoking context.find(),
+ * and to use as a single reference node for tests invoking document.find().
+ * Note: context = root.querySelector(ctx);
+ *
+ * ref: A selector to obtain the reference nodes to be used for the selector.
+ * Note: If root is the document or an in-document element:
+ * refNodes = document.querySelectorAll(ref);
+ * Otherwise, if root is a fragment or detached element:
+ * refNodes = root.querySelectorAll(ref);
+ *
+ * expect: A list of IDs of the elements expected to be matched. List must be given in tree order.
+ *
+ * unexpected: A list of IDs of some elements that are not expected to match the given selector.
+ * This is used to verify that unexpected.matches(selector, refNode) does not match.
+ *
+ * exclude: An array of contexts to exclude from testing. The valid values are:
+ * ["document", "element", "fragment", "detached", "html", "xhtml"]
+ * The "html" and "xhtml" values represent the type of document being queried. These are useful
+ * for tests that are affected by differences between HTML and XML, such as case sensitivity.
+ *
+ * level: An integer indicating the CSS or Selectors level in which the selector being tested was introduced.
+ *
+ * testType: A bit-mapped flag indicating the type of test.
+ *
+ * The test function for these tests accepts a specified root node, on which the methods will be invoked during the tests.
+ *
+ * Based on whether either 'context' or 'refNodes', or both, are specified the tests will execute the following methods:
+ *
+ * Where testType is TEST_FIND:
+ *
+ * context.findAll(selector, refNodes)
+ * context.findAll(selector) // Only if refNodes is not specified
+ * root.findAll(selector, context) // Only if refNodes is not specified
+ * root.findAll(selector, refNodes) // Only if context is not specified
+ * root.findAll(selector) // Only if neither context nor refNodes is specified
+ *
+ * Where testType is TEST_QSA
+ *
+ * context.querySelectorAll(selector)
+ * root.querySelectorAll(selector) // Only if neither context nor refNodes is specified
+ *
+ * Equivalent tests will be run for .find() as well.
+ * Note: Do not specify a testType of TEST_QSA where either implied :scope or explicit refNodes
+ * are required.
+ *
+ * Where testType is TEST_MATCH:
+ * For each expected result given, within the specified root:
+ *
+ * expect.matches(selector, context) // Only where refNodes is not specified
+ * expect.matches(selector, refNodes)
+ * expect.matches(selector) // Only if neither context nor refNodes is specified
+ *
+ * The tests involving refNodes for both find(), findAll() and matches() will each be run by passing the
+ * collection as a NodeList, an Array and, if there is only a single element, an Element node.
+ *
+ * Note: Interactive pseudo-classes (:active :hover and :focus) have not been tested in this test suite.
+ */
+
+var scopedSelectors = [
+ //{name: "", selector: "", ctx: "", ref: "", expect: [], level: 1, testType: TEST_FIND | TEST_MATCH},
+
+ // Attribute Selectors
+ // - presence [att]
+ {name: "Attribute presence selector, matching align attribute with value", selector: ".attr-presence-div1[align]", ctx: "#attr-presence", expect: ["attr-presence-div1"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute presence selector, matching align attribute with empty value", selector: ".attr-presence-div2[align]", ctx: "#attr-presence", expect: ["attr-presence-div2"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute presence selector, matching title attribute, case insensitivity", selector: "[TiTlE]", ctx: "#attr-presence", expect: ["attr-presence-a1", "attr-presence-span1"], exclude: ["xhtml"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute presence selector, not matching title attribute, case sensitivity", selector: "[TiTlE]", ctx: "#attr-presence", expect: [], exclude: ["html"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute presence selector, matching custom data-* attribute", selector: "[data-attr-presence]", ctx: "#attr-presence", expect: ["attr-presence-pre1", "attr-presence-blockquote1"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute presence selector, not matching attribute with similar name", selector: ".attr-presence-div3[align], .attr-presence-div4[align]", ctx: "#attr-presence", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+ {name: "Attribute presence selector, matching attribute with non-ASCII characters", selector: "ul[data-中文]", ctx: "#attr-presence", expect: ["attr-presence-ul1"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute presence selector, not matching default option without selected attribute", selector: "#attr-presence-select1 option[selected]", ctx: "#attr-presence", expect: [] /* no matches */, level: 2, testType: TEST_FIND},
+ {name: "Attribute presence selector, matching option with selected attribute", selector: "#attr-presence-select2 option[selected]", ctx: "#attr-presence", expect: ["attr-presence-select2-option4"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute presence selector, matching multiple options with selected attributes", selector: "#attr-presence-select3 option[selected]", ctx: "#attr-presence", expect: ["attr-presence-select3-option2", "attr-presence-select3-option3"], level: 2, testType: TEST_FIND | TEST_MATCH},
+
+ // - value [att=val]
+ {name: "Attribute value selector, matching align attribute with value", selector: "[align=\"center\"]", ctx: "#attr-value", expect: ["attr-value-div1"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute value selector, matching align attribute with empty value", selector: "[align=\"\"]", ctx: "#attr-value", expect: ["attr-value-div2"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute value selector, not matching align attribute with partial value", selector: "[align=\"c\"]", ctx: "#attr-value", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+ {name: "Attribute value selector, not matching align attribute with incorrect value", selector: "[align=\"centera\"]", ctx: "#attr-value", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+ {name: "Attribute value selector, matching custom data-* attribute with unicode escaped value", selector: "[data-attr-value=\"\\e9\"]", ctx: "#attr-value", expect: ["attr-value-div3"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute value selector, matching custom data-* attribute with escaped character", selector: "[data-attr-value\_foo=\"\\e9\"]", ctx: "#attr-value", expect: ["attr-value-div4"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute value selector with single-quoted value, matching multiple inputs with type attributes", selector: "input[type='hidden'],#attr-value input[type='radio']", ctx: "#attr-value", expect: ["attr-value-input3", "attr-value-input4", "attr-value-input6", "attr-value-input8", "attr-value-input9"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute value selector with double-quoted value, matching multiple inputs with type attributes", selector: "input[type=\"hidden\"],#attr-value input[type='radio']", ctx: "#attr-value", expect: ["attr-value-input3", "attr-value-input4", "attr-value-input6", "attr-value-input8", "attr-value-input9"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute value selector with unquoted value, matching multiple inputs with type attributes", selector: "input[type=hidden],#attr-value input[type=radio]", ctx: "#attr-value", expect: ["attr-value-input3", "attr-value-input4", "attr-value-input6", "attr-value-input8", "attr-value-input9"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute value selector, matching attribute with value using non-ASCII characters", selector: "[data-attr-value=中文]", ctx: "#attr-value", expect: ["attr-value-div5"], level: 2, testType: TEST_FIND | TEST_MATCH},
+
+ // - whitespace-separated list [att~=val]
+ {name: "Attribute whitespace-separated list selector, matching class attribute with value", selector: "[class~=\"div1\"]", ctx: "#attr-whitespace", expect: ["attr-whitespace-div1"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute whitespace-separated list selector, not matching class attribute with empty value", selector: "[class~=\"\"]", ctx: "#attr-whitespace", expect: [] /*no matches*/ , level: 2, testType: TEST_FIND},
+ {name: "Attribute whitespace-separated list selector, not matching class attribute with partial value", selector: "[data-attr-whitespace~=\"div\"]", ctx: "#attr-whitespace", expect: [] /*no matches*/ , level: 2, testType: TEST_FIND},
+ {name: "Attribute whitespace-separated list selector, matching custom data-* attribute with unicode escaped value", selector: "[data-attr-whitespace~=\"\\0000e9\"]", ctx: "#attr-whitespace", expect: ["attr-whitespace-div4"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute whitespace-separated list selector, matching custom data-* attribute with escaped character", selector: "[data-attr-whitespace\_foo~=\"\\e9\"]", ctx: "#attr-whitespace", expect: ["attr-whitespace-div5"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute whitespace-separated list selector with single-quoted value, matching multiple links with rel attributes", selector: "a[rel~='bookmark'], #attr-whitespace a[rel~='nofollow']", ctx: "#attr-whitespace", expect: ["attr-whitespace-a1", "attr-whitespace-a2", "attr-whitespace-a3", "attr-whitespace-a5", "attr-whitespace-a7"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute whitespace-separated list selector with double-quoted value, matching multiple links with rel attributes", selector: "a[rel~=\"bookmark\"],#attr-whitespace a[rel~='nofollow']", ctx: "#attr-whitespace", expect: ["attr-whitespace-a1", "attr-whitespace-a2", "attr-whitespace-a3", "attr-whitespace-a5", "attr-whitespace-a7"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute whitespace-separated list selector with unquoted value, matching multiple links with rel attributes", selector: "a[rel~=bookmark], #attr-whitespace a[rel~=nofollow]", ctx: "#attr-whitespace", expect: ["attr-whitespace-a1", "attr-whitespace-a2", "attr-whitespace-a3", "attr-whitespace-a5", "attr-whitespace-a7"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute whitespace-separated list selector with double-quoted value, not matching value with space", selector: "a[rel~=\"book mark\"]", ctx: "#attr-whitespace", expect: [] /* no matches */, level: 2, testType: TEST_FIND},
+ {name: "Attribute whitespace-separated list selector, matching title attribute with value using non-ASCII characters", selector: "[title~=中文]", ctx: "#attr-whitespace", expect: ["attr-whitespace-p1"], level: 2, testType: TEST_FIND | TEST_MATCH},
+
+ // - hyphen-separated list [att|=val]
+ {name: "Attribute hyphen-separated list selector, not matching unspecified lang attribute", selector: "#attr-hyphen-div1[lang|=\"en\"]", ctx: "#attr-hyphen", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+ {name: "Attribute hyphen-separated list selector, matching lang attribute with exact value", selector: "#attr-hyphen-div2[lang|=\"fr\"]", ctx: "#attr-hyphen", expect: ["attr-hyphen-div2"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute hyphen-separated list selector, matching lang attribute with partial value", selector: "#attr-hyphen-div3[lang|=\"en\"]", ctx: "#attr-hyphen", expect: ["attr-hyphen-div3"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute hyphen-separated list selector, not matching incorrect value", selector: "#attr-hyphen-div4[lang|=\"es-AR\"]", ctx: "#attr-hyphen", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+
+ // - substring begins-with [att^=val] (Level 3)
+ {name: "Attribute begins with selector, matching href attributes beginning with specified substring", selector: "a[href^=\"http://www\"]", ctx: "#attr-begins", expect: ["attr-begins-a1", "attr-begins-a3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute begins with selector, matching lang attributes beginning with specified substring, ", selector: "[lang^=\"en-\"]", ctx: "#attr-begins", expect: ["attr-begins-div2", "attr-begins-div4"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute begins with selector, not matching class attribute with empty value", selector: "[class^=\"\"]", ctx: "#attr-begins", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+ {name: "Attribute begins with selector, not matching class attribute not beginning with specified substring", selector: "[class^=apple]", ctx: "#attr-begins", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+ {name: "Attribute begins with selector with single-quoted value, matching class attribute beginning with specified substring", selector: "[class^=' apple']", ctx: "#attr-begins", expect: ["attr-begins-p1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute begins with selector with double-quoted value, matching class attribute beginning with specified substring", selector: "[class^=\" apple\"]", ctx: "#attr-begins", expect: ["attr-begins-p1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute begins with selector with unquoted value, not matching class attribute not beginning with specified substring", selector: "[class^= apple]", ctx: "#attr-begins", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+
+ // - substring ends-with [att$=val] (Level 3)
+ {name: "Attribute ends with selector, matching href attributes ending with specified substring", selector: "a[href$=\".org\"]", ctx: "#attr-ends", expect: ["attr-ends-a1", "attr-ends-a3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute ends with selector, matching lang attributes ending with specified substring, ", selector: "[lang$=\"-CH\"]", ctx: "#attr-ends", expect: ["attr-ends-div2", "attr-ends-div4"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute ends with selector, not matching class attribute with empty value", selector: "[class$=\"\"]", ctx: "#attr-ends", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+ {name: "Attribute ends with selector, not matching class attribute not ending with specified substring", selector: "[class$=apple]", ctx: "#attr-ends", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+ {name: "Attribute ends with selector with single-quoted value, matching class attribute ending with specified substring", selector: "[class$='apple ']", ctx: "#attr-ends", expect: ["attr-ends-p1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute ends with selector with double-quoted value, matching class attribute ending with specified substring", selector: "[class$=\"apple \"]", ctx: "#attr-ends", expect: ["attr-ends-p1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute ends with selector with unquoted value, not matching class attribute not ending with specified substring", selector: "[class$=apple ]", ctx: "#attr-ends", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+
+ // - substring contains [att*=val] (Level 3)
+ {name: "Attribute contains selector, matching href attributes beginning with specified substring", selector: "a[href*=\"http://www\"]", ctx: "#attr-contains", expect: ["attr-contains-a1", "attr-contains-a3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute contains selector, matching href attributes ending with specified substring", selector: "a[href*=\".org\"]", ctx: "#attr-contains", expect: ["attr-contains-a1", "attr-contains-a2"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute contains selector, matching href attributes containing specified substring", selector: "a[href*=\".example.\"]", ctx: "#attr-contains", expect: ["attr-contains-a1", "attr-contains-a3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute contains selector, matching lang attributes beginning with specified substring, ", selector: "[lang*=\"en-\"]", ctx: "#attr-contains", expect: ["attr-contains-div2", "attr-contains-div6"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute contains selector, matching lang attributes ending with specified substring, ", selector: "[lang*=\"-CH\"]", ctx: "#attr-contains", expect: ["attr-contains-div3", "attr-contains-div5"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute contains selector, not matching class attribute with empty value", selector: "[class*=\"\"]", ctx: "#attr-contains", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+ {name: "Attribute contains selector with single-quoted value, matching class attribute beginning with specified substring", selector: "[class*=' apple']", ctx: "#attr-contains", expect: ["attr-contains-p1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute contains selector with single-quoted value, matching class attribute ending with specified substring", selector: "[class*='orange ']", ctx: "#attr-contains", expect: ["attr-contains-p1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute contains selector with single-quoted value, matching class attribute containing specified substring", selector: "[class*='ple banana ora']", ctx: "#attr-contains", expect: ["attr-contains-p1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute contains selector with double-quoted value, matching class attribute beginning with specified substring", selector: "[class*=\" apple\"]", ctx: "#attr-contains", expect: ["attr-contains-p1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute contains selector with double-quoted value, matching class attribute ending with specified substring", selector: "[class*=\"orange \"]", ctx: "#attr-contains", expect: ["attr-contains-p1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute contains selector with double-quoted value, matching class attribute containing specified substring", selector: "[class*=\"ple banana ora\"]", ctx: "#attr-contains", expect: ["attr-contains-p1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute contains selector with unquoted value, matching class attribute beginning with specified substring", selector: "[class*= apple]", ctx: "#attr-contains", expect: ["attr-contains-p1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute contains selector with unquoted value, matching class attribute ending with specified substring", selector: "[class*=orange ]", ctx: "#attr-contains", expect: ["attr-contains-p1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "Attribute contains selector with unquoted value, matching class attribute containing specified substring", selector: "[class*= banana ]", ctx: "#attr-contains", expect: ["attr-contains-p1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+
+ // Pseudo-classes
+ // - :root (Level 3)
+ {name: ":root pseudo-class selector, matching document root element", selector: ":root", expect: ["html"], exclude: ["element", "fragment", "detached"], level: 3, testType: TEST_FIND},
+ {name: ":root pseudo-class selector, not matching document root element", selector: ":root", expect: [] /*no matches*/, exclude: ["document"], level: 3, testType: TEST_FIND},
+ {name: ":root pseudo-class selector, not matching document root element", selector: ":root", ctx: "#html", expect: [] /*no matches*/, exclude: ["fragment", "detached"], level: 3, testType: TEST_FIND},
+
+ // - :nth-child(n) (Level 3)
+ {name: ":nth-child selector, matching the third child element", selector: ":nth-child(3)", ctx: "#pseudo-nth-table1", expect: ["pseudo-nth-td3", "pseudo-nth-td9", "pseudo-nth-tr3", "pseudo-nth-td15"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":nth-child selector, matching every third child element", selector: "li:nth-child(3n)", ctx: "#pseudo-nth", expect: ["pseudo-nth-li3", "pseudo-nth-li6", "pseudo-nth-li9", "pseudo-nth-li12"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":nth-child selector, matching every second child element, starting from the fourth", selector: "li:nth-child(2n+4)", ctx: "#pseudo-nth", expect: ["pseudo-nth-li4", "pseudo-nth-li6", "pseudo-nth-li8", "pseudo-nth-li10", "pseudo-nth-li12"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":nth-child selector, matching every second child element, starting from the fourth, with whitespace", selector: "li:nth-child(2n \t\r\n+ \t\r\n4)", ctx: "#pseudo-nth", expect: ["pseudo-nth-li4", "pseudo-nth-li6", "pseudo-nth-li8", "pseudo-nth-li10", "pseudo-nth-li12"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":nth-child selector, matching every fourth child element, starting from the third", selector: ":nth-child(4n-1)", ctx: "#pseudo-nth-p1", expect: ["pseudo-nth-em2", "pseudo-nth-span3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":nth-child selector, matching every fourth child element, starting from the third, with whitespace", selector: ":nth-child(4n \t\r\n- \t\r\n1)", ctx: "#pseudo-nth-p1", expect: ["pseudo-nth-em2", "pseudo-nth-span3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":nth-child selector used twice, matching ", selector: ":nth-child(1) :nth-child(1)", ctx: "#pseudo-nth", expect: ["pseudo-nth-table1", "pseudo-nth-tr1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+
+ // - :nth-last-child (Level 3)
+ {name: ":nth-last-child selector, matching the third last child element", selector: ":nth-last-child(3)", ctx: "#pseudo-nth-table1", expect: ["pseudo-nth-tr1", "pseudo-nth-td4", "pseudo-nth-td10", "pseudo-nth-td16"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":nth-last-child selector, matching every third child element from the end", selector: "li:nth-last-child(3n)", ctx: "pseudo-nth", expect: ["pseudo-nth-li1", "pseudo-nth-li4", "pseudo-nth-li7", "pseudo-nth-li10"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":nth-last-child selector, matching every second child element from the end, starting from the fourth last", selector: "li:nth-last-child(2n+4)", ctx: "pseudo-nth", expect: ["pseudo-nth-li1", "pseudo-nth-li3", "pseudo-nth-li5", "pseudo-nth-li7", "pseudo-nth-li9"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":nth-last-child selector, matching every fourth element from the end, starting from the third last", selector: ":nth-last-child(4n-1)", ctx: "#pseudo-nth-p1", expect: ["pseudo-nth-span2", "pseudo-nth-span4"], level: 3, testType: TEST_FIND | TEST_MATCH},
+
+ // - :nth-of-type(n) (Level 3)
+ {name: ":nth-of-type selector, matching the third em element", selector: "em:nth-of-type(3)", ctx: "#pseudo-nth-p1", expect: ["pseudo-nth-em3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":nth-of-type selector, matching every second element of their type", selector: ":nth-of-type(2n)", ctx: "#pseudo-nth-p1", expect: ["pseudo-nth-em2", "pseudo-nth-span2", "pseudo-nth-span4", "pseudo-nth-strong2", "pseudo-nth-em4"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":nth-of-type selector, matching every second elemetn of their type, starting from the first", selector: "span:nth-of-type(2n-1)", ctx: "#pseudo-nth-p1", expect: ["pseudo-nth-span1", "pseudo-nth-span3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+
+ // - :nth-last-of-type(n) (Level 3)
+ {name: ":nth-last-of-type selector, matching the third last em element", selector: "em:nth-last-of-type(3)", ctx: "#pseudo-nth-p1", expect: ["pseudo-nth-em2"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":nth-last-of-type selector, matching every second last element of their type", selector: ":nth-last-of-type(2n)", ctx: "#pseudo-nth-p1", expect: ["pseudo-nth-span1", "pseudo-nth-em1", "pseudo-nth-strong1", "pseudo-nth-em3", "pseudo-nth-span3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":nth-last-of-type selector, matching every second last element of their type, starting from the last", selector: "span:nth-last-of-type(2n-1)", ctx: "#pseudo-nth-p1", expect: ["pseudo-nth-span2", "pseudo-nth-span4"], level: 3, testType: TEST_FIND | TEST_MATCH},
+
+ // - :first-of-type (Level 3)
+ {name: ":first-of-type selector, matching the first em element", selector: "em:first-of-type", ctx: "#pseudo-nth-p1", expect: ["pseudo-nth-em1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":first-of-type selector, matching the first of every type of element", selector: ":first-of-type", ctx: "#pseudo-nth-p1", expect: ["pseudo-nth-span1", "pseudo-nth-em1", "pseudo-nth-strong1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":first-of-type selector, matching the first td element in each table row", selector: "tr :first-of-type", ctx: "#pseudo-nth-table1", expect: ["pseudo-nth-td1", "pseudo-nth-td7", "pseudo-nth-td13"], level: 3, testType: TEST_FIND | TEST_MATCH},
+
+ // - :last-of-type (Level 3)
+ {name: ":last-of-type selector, matching the last em elemnet", selector: "em:last-of-type", ctx: "#pseudo-nth-p1", expect: ["pseudo-nth-em4"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":last-of-type selector, matching the last of every type of element", selector: ":last-of-type", ctx: "#pseudo-nth-p1", expect: ["pseudo-nth-span4", "pseudo-nth-strong2", "pseudo-nth-em4"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":last-of-type selector, matching the last td element in each table row", selector: "tr :last-of-type", ctx: "#pseudo-nth-table1", expect: ["pseudo-nth-td6", "pseudo-nth-td12", "pseudo-nth-td18"], level: 3, testType: TEST_FIND | TEST_MATCH},
+
+ // - :first-child
+ {name: ":first-child pseudo-class selector, matching first child div element", selector: "div:first-child", ctx: "#pseudo-first-child", expect: ["pseudo-first-child-div1"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: ":first-child pseudo-class selector, doesn't match non-first-child elements", selector: ".pseudo-first-child-div2:first-child, .pseudo-first-child-div3:first-child", ctx: "#pseudo-first-child", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+ {name: ":first-child pseudo-class selector, matching first-child of multiple elements", selector: "span:first-child", ctx: "#pseudo-first-child", expect: ["pseudo-first-child-span1", "pseudo-first-child-span3", "pseudo-first-child-span5"], level: 2, testType: TEST_FIND | TEST_MATCH},
+
+ // - :last-child (Level 3)
+ {name: ":last-child pseudo-class selector, matching last child div element", selector: "div:last-child", ctx: "#pseudo-last-child", expect: ["pseudo-last-child-div3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":last-child pseudo-class selector, doesn't match non-last-child elements", selector: ".pseudo-last-child-div1:last-child, .pseudo-last-child-div2:first-child", ctx: "#pseudo-last-child", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+ {name: ":last-child pseudo-class selector, matching first-child of multiple elements", selector: "span:last-child", ctx: "#pseudo-last-child", expect: ["pseudo-last-child-span2", "pseudo-last-child-span4", "pseudo-last-child-span6"], level: 3, testType: TEST_FIND | TEST_MATCH},
+
+ // - :only-child (Level 3)
+ {name: ":pseudo-only-child pseudo-class selector, matching all only-child elements", selector: ":only-child", ctx: "#pseudo-only", expect: ["pseudo-only-span1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":pseudo-only-child pseudo-class selector, matching only-child em elements", selector: "em:only-child", ctx: "#pseudo-only", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+
+ // - :only-of-type (Level 3)
+ {name: ":pseudo-only-of-type pseudo-class selector, matching all elements with no siblings of the same type", selector: " :only-of-type", ctx: "#pseudo-only", expect: ["pseudo-only-span1", "pseudo-only-em1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":pseudo-only-of-type pseudo-class selector, matching em elements with no siblings of the same type", selector: " em:only-of-type", ctx: "#pseudo-only", expect: ["pseudo-only-em1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+
+ // - :empty (Level 3)
+ {name: ":empty pseudo-class selector, matching empty p elements", selector: "p:empty", ctx: "#pseudo-empty", expect: ["pseudo-empty-p1", "pseudo-empty-p2"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":empty pseudo-class selector, matching all empty elements", selector: ":empty", ctx: "#pseudo-empty", expect: ["pseudo-empty-p1", "pseudo-empty-p2", "pseudo-empty-span1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+
+ // - :link and :visited
+ // Implementations may treat all visited links as unvisited, so these cannot be tested separately.
+ // The only guarantee is that ":link,:visited" matches the set of all visited and unvisited links and that they are individually mutually exclusive sets.
+ {name: ":link and :visited pseudo-class selectors, matching a and area elements with href attributes", selector: " :link, #pseudo-link :visited", ctx: "#pseudo-link", expect: ["pseudo-link-a1", "pseudo-link-a2", "pseudo-link-area1"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: ":link and :visited pseudo-class selectors, matching no elements", selector: " :link, #head :visited", ctx: "#head", expect: [] /*no matches*/, exclude: ["element", "fragment", "detached"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: ":link and :visited pseudo-class selectors, not matching link elements with href attributes", selector: " :link, #head :visited", ctx: "#head", expect: [] /*no matches*/, exclude: ["document"], level: 1, testType: TEST_FIND},
+ {name: ":link and :visited pseudo-class selectors, chained, mutually exclusive pseudo-classes match nothing", selector: ":link:visited", ctx: "#html", expect: [] /*no matches*/, exclude: ["document"], level: 1, testType: TEST_FIND},
+
+// XXX Figure out context or refNodes for this
+ // - :target (Level 3)
+ {name: ":target pseudo-class selector, matching the element referenced by the URL fragment identifier", selector: ":target", ctx: "", expect: [] /*no matches*/, exclude: ["document", "element"], level: 3, testType: TEST_FIND},
+ {name: ":target pseudo-class selector, matching the element referenced by the URL fragment identifier", selector: ":target", ctx: "", expect: ["target"], exclude: ["fragment", "detached"], level: 3, testType: TEST_FIND},
+
+// XXX Fix ctx in tests below
+
+ // - :lang()
+ {name: ":lang pseudo-class selector, matching inherited language (1)", selector: "#pseudo-lang-div1:lang(en)", ctx: "", expect: ["pseudo-lang-div1"], exclude: ["detached", "fragment"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: ":lang pseudo-class selector, not matching element with no inherited language", selector: "#pseudo-lang-div1:lang(en)", ctx: "", expect: [] /*no matches*/, exclude: ["document", "element"], level: 2, testType: TEST_FIND},
+ {name: ":lang pseudo-class selector, matching specified language with exact value (1)", selector: "#pseudo-lang-div2:lang(fr)", ctx: "", expect: ["pseudo-lang-div2"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: ":lang pseudo-class selector, matching specified language with partial value (1)", selector: "#pseudo-lang-div3:lang(en)", ctx: "", expect: ["pseudo-lang-div3"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: ":lang pseudo-class selector, not matching incorrect language", selector: "#pseudo-lang-div4:lang(es-AR)", ctx: "", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+
+ // - :enabled (Level 3)
+ {name: ":enabled pseudo-class selector, matching all enabled form controls (1)", selector: "#pseudo-ui :enabled", ctx: "", expect: ["pseudo-ui-input1", "pseudo-ui-input2", "pseudo-ui-input3", "pseudo-ui-input4", "pseudo-ui-input5", "pseudo-ui-input6",
+ "pseudo-ui-input7", "pseudo-ui-input8", "pseudo-ui-input9", "pseudo-ui-textarea1", "pseudo-ui-button1"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":enabled pseudo-class selector, not matching link elements (1)", selector: "#pseudo-link :enabled", ctx: "", expect: [] /*no matches*/, unexpected: ["pseudo-link-a1","pseudo-link-a2","pseudo-link-a3","pseudo-link-map1","pseudo-link-area1","pseudo-link-area2"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :disabled (Level 3)
+ {name: ":disabled pseudo-class selector, matching all disabled form controls (1)", selector: "#pseudo-ui :disabled", ctx: "", expect: ["pseudo-ui-input10", "pseudo-ui-input11", "pseudo-ui-input12", "pseudo-ui-input13", "pseudo-ui-input14", "pseudo-ui-input15",
+ "pseudo-ui-input16", "pseudo-ui-input17", "pseudo-ui-input18", "pseudo-ui-textarea2", "pseudo-ui-button2"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":disabled pseudo-class selector, not matching link elements (1)", selector: "#pseudo-link :disabled", ctx: "", expect: [] /*no matches*/, unexpected: ["pseudo-link-a1","pseudo-link-a2","pseudo-link-a3","pseudo-link-map1","pseudo-link-area1","pseudo-link-area2"], level: 3, testType: TEST_QSA | TEST_MATCH},
+
+ // - :checked (Level 3)
+ {name: ":checked pseudo-class selector, matching checked radio buttons and checkboxes (1)", selector: "#pseudo-ui :checked", ctx: "", expect: ["pseudo-ui-input4", "pseudo-ui-input6", "pseudo-ui-input13", "pseudo-ui-input15"], level: 3, testType: TEST_FIND | TEST_MATCH},
+
+ // - :not(s) (Level 3)
+ {name: ":not pseudo-class selector, matching (1)", selector: "#not>:not(div)", ctx: "", expect: ["not-p1", "not-p2", "not-p3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":not pseudo-class selector, matching (1)", selector: "#not * :not(:first-child)", ctx: "", expect: ["not-em1", "not-em2", "not-em3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: ":not pseudo-class selector, matching nothing", selector: ":not(*)", ctx: "", expect: [] /* no matches */, level: 3, testType: TEST_FIND},
+ {name: ":not pseudo-class selector, matching nothing", selector: ":not(*|*)", ctx: "", expect: [] /* no matches */, level: 3, testType: TEST_FIND},
+
+ // Pseudo-elements
+ // - ::first-line
+ {name: ":first-line pseudo-element (one-colon syntax) selector, not matching any elements", selector: "#pseudo-element:first-line", ctx: "", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+ {name: "::first-line pseudo-element (two-colon syntax) selector, not matching any elements", selector: "#pseudo-element::first-line", ctx: "", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+
+ // - ::first-letter
+ {name: ":first-letter pseudo-element (one-colon syntax) selector, not matching any elements", selector: "#pseudo-element:first-letter", ctx: "", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+ {name: "::first-letter pseudo-element (two-colon syntax) selector, not matching any elements", selector: "#pseudo-element::first-letter", ctx: "", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+
+ // - ::before
+ {name: ":before pseudo-element (one-colon syntax) selector, not matching any elements", selector: "#pseudo-element:before", ctx: "", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+ {name: "::before pseudo-element (two-colon syntax) selector, not matching any elements", selector: "#pseudo-element::before", ctx: "", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+
+ // - ::after
+ {name: ":after pseudo-element (one-colon syntax) selector, not matching any elements", selector: "#pseudo-element:after", ctx: "", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+ {name: "::after pseudo-element (two-colon syntax) selector, not matching any elements", selector: "#pseudo-element::after", ctx: "", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+
+ // Class Selectors
+ {name: "Class selector, matching element with specified class (1)", selector: ".class-p", ctx: "", expect: ["class-p1","class-p2", "class-p3"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Class selector, chained, matching only elements with all specified classes (1)", selector: "#class .apple.orange.banana", ctx: "", expect: ["class-div1", "class-div2", "class-p4", "class-div3", "class-p6", "class-div4"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Class Selector, chained, with type selector (1)", selector: "div.apple.banana.orange", ctx: "", expect: ["class-div1", "class-div2", "class-div3", "class-div4"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Class selector, matching element with class value using non-ASCII characters (2)", selector: ".\u53F0\u5317Ta\u0301ibe\u030Ci", ctx: "", expect: ["class-span1"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Class selector, matching multiple elements with class value using non-ASCII characters (1)", selector: ".\u53F0\u5317", ctx: "", expect: ["class-span1","class-span2"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Class selector, chained, matching element with multiple class values using non-ASCII characters (2)", selector: ".\u53F0\u5317Ta\u0301ibe\u030Ci.\u53F0\u5317", ctx: "", expect: ["class-span1"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Class selector, matching element with class with escaped character (1)", selector: ".foo\\:bar", ctx: "", expect: ["class-span3"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Class selector, matching element with class with escaped character (1)", selector: ".test\\.foo\\[5\\]bar", ctx: "", expect: ["class-span4"], level: 1, testType: TEST_FIND | TEST_MATCH},
+
+ // ID Selectors
+ {name: "ID selector, matching element with specified id (1)", selector: "#id #id-div1", ctx: "", expect: ["id-div1"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "ID selector, chained, matching element with specified id (1)", selector: "#id-div1, #id-div1", ctx: "", expect: ["id-div1"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "ID selector, chained, matching element with specified id (1)", selector: "#id-div1, #id-div2", ctx: "", expect: ["id-div1", "id-div2"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "ID Selector, chained, with type selector (1)", selector: "div#id-div1, div#id-div2", ctx: "", expect: ["id-div1", "id-div2"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "ID selector, not matching non-existent descendant", selector: "#id #none", ctx: "", expect: [] /*no matches*/, level: 1, testType: TEST_FIND},
+ {name: "ID selector, not matching non-existent ancestor", selector: "#none #id-div1", ctx: "", expect: [] /*no matches*/, level: 1, testType: TEST_FIND},
+ {name: "ID selector, matching multiple elements with duplicate id (1)", selector: "#id-li-duplicate", ctx: "", expect: ["id-li-duplicate", "id-li-duplicate", "id-li-duplicate", "id-li-duplicate"], level: 1, testType: TEST_FIND | TEST_MATCH},
+
+ {name: "ID selector, matching id value using non-ASCII characters (3)", selector: "#\u53F0\u5317Ta\u0301ibe\u030Ci", ctx: "", expect: ["\u53F0\u5317Ta\u0301ibe\u030Ci"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "ID selector, matching id value using non-ASCII characters (4)", selector: "#\u53F0\u5317", ctx: "", expect: ["\u53F0\u5317"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "ID selector, matching id values using non-ASCII characters (2)", selector: "#\u53F0\u5317Ta\u0301ibe\u030Ci, #\u53F0\u5317", ctx: "", expect: ["\u53F0\u5317Ta\u0301ibe\u030Ci", "\u53F0\u5317"], level: 1, testType: TEST_FIND | TEST_MATCH},
+
+ // XXX runMatchesTest() in level2-lib.js can't handle this because obtaining the expected nodes requires escaping characters when generating the selector from 'expect' values
+ {name: "ID selector, matching element with id with escaped character", selector: "#\\#foo\\:bar", ctx: "", expect: ["#foo:bar"], level: 1, testType: TEST_FIND},
+ {name: "ID selector, matching element with id with escaped character", selector: "#test\\.foo\\[5\\]bar", ctx: "", expect: ["test.foo[5]bar"], level: 1, testType: TEST_FIND},
+
+ // Namespaces
+ // XXX runMatchesTest() in level2-lib.js can't handle these because non-HTML elements don't have a recognised id
+ {name: "Namespace selector, matching element with any namespace", selector: "#any-namespace *|div", ctx: "", expect: ["any-namespace-div1", "any-namespace-div2", "any-namespace-div3", "any-namespace-div4"], level: 3, testType: TEST_FIND},
+ {name: "Namespace selector, matching div elements in no namespace only", selector: "#no-namespace |div", ctx: "", expect: ["no-namespace-div3"], level: 3, testType: TEST_FIND},
+ {name: "Namespace selector, matching any elements in no namespace only", selector: "#no-namespace |*", ctx: "", expect: ["no-namespace-div3"], level: 3, testType: TEST_FIND},
+
+ // Combinators
+ // - Descendant combinator ' '
+ {name: "Descendant combinator, matching element that is a descendant of an element with id (1)", selector: "#descendant div", ctx: "", expect: ["descendant-div1", "descendant-div2", "descendant-div3", "descendant-div4"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Descendant combinator, matching element with id that is a descendant of an element (1)", selector: "body #descendant-div1", ctx: "", expect: ["descendant-div1"], exclude: ["detached", "fragment"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Descendant combinator, matching element with id that is a descendant of an element (1)", selector: "div #descendant-div1", ctx: "", expect: ["descendant-div1"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Descendant combinator, matching element with id that is a descendant of an element with id (1)", selector: "#descendant #descendant-div2", ctx: "", expect: ["descendant-div2"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Descendant combinator, matching element with class that is a descendant of an element with id (1)", selector: "#descendant .descendant-div2", ctx: "", expect: ["descendant-div2"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Descendant combinator, matching element with class that is a descendant of an element with class (1)", selector: ".descendant-div1 .descendant-div3", ctx: "", expect: ["descendant-div3"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Descendant combinator, not matching element with id that is not a descendant of an element with id", selector: "#descendant-div1 #descendant-div4", ctx: "", expect: [] /*no matches*/, level: 1, testType: TEST_FIND},
+ {name: "Descendant combinator, whitespace characters (1)", selector: "#descendant\t\r\n#descendant-div2", ctx: "", expect: ["descendant-div2"], level: 1, testType: TEST_FIND | TEST_MATCH},
+
+ // // - Descendant combinator '>>'
+ // {name: "Descendant combinator '>>', matching element that is a descendant of an element with id (1)", selector: "#descendant>>div", ctx: "", expect: ["descendant-div1", "descendant-div2", "descendant-div3", "descendant-div4"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with id that is a descendant of an element (1)", selector: "body>>#descendant-div1", ctx: "", expect: ["descendant-div1"], exclude: ["detached", "fragment"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with id that is a descendant of an element (1)", selector: "div>>#descendant-div1", ctx: "", expect: ["descendant-div1"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with id that is a descendant of an element with id (1)", selector: "#descendant>>#descendant-div2", ctx: "", expect: ["descendant-div2"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with class that is a descendant of an element with id (1)", selector: "#descendant>>.descendant-div2", ctx: "", expect: ["descendant-div2"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ // {name: "Descendant combinator, '>>', matching element with class that is a descendant of an element with class (1)", selector: ".descendant-div1>>.descendant-div3", ctx: "", expect: ["descendant-div3"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ // {name: "Descendant combinator '>>', not matching element with id that is not a descendant of an element with id", selector: "#descendant-div1>>#descendant-div4", ctx: "", expect: [] /*no matches*/, level: 1, testType: TEST_FIND},
+
+ // - Child combinator '>'
+ {name: "Child combinator, matching element that is a child of an element with id (1)", selector: "#child>div", ctx: "", expect: ["child-div1", "child-div4"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Child combinator, matching element with id that is a child of an element (1)", selector: "div>#child-div1", ctx: "", expect: ["child-div1"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Child combinator, matching element with id that is a child of an element with id (1)", selector: "#child>#child-div1", ctx: "", expect: ["child-div1"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Child combinator, matching element with id that is a child of an element with class (1)", selector: "#child-div1>.child-div2", ctx: "", expect: ["child-div2"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Child combinator, matching element with class that is a child of an element with class (1)", selector: ".child-div1>.child-div2", ctx: "", expect: ["child-div2"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Child combinator, not matching element with id that is not a child of an element with id", selector: "#child>#child-div3", ctx: "", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+ {name: "Child combinator, not matching element with id that is not a child of an element with class", selector: "#child-div1>.child-div3", ctx: "", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+ {name: "Child combinator, not matching element with class that is not a child of an element with class", selector: ".child-div1>.child-div3", ctx: "", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+ {name: "Child combinator, surrounded by whitespace (1)", selector: "#child-div1\t\r\n>\t\r\n#child-div2", ctx: "", expect: ["child-div2"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Child combinator, whitespace after (1)", selector: "#child-div1>\t\r\n#child-div2", ctx: "", expect: ["child-div2"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Child combinator, whitespace before (1)", selector: "#child-div1\t\r\n>#child-div2", ctx: "", expect: ["child-div2"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Child combinator, no whitespace (1)", selector: "#child-div1>#child-div2", ctx: "", expect: ["child-div2"], level: 2, testType: TEST_FIND | TEST_MATCH},
+
+ // - Adjacent sibling combinator '+'
+ {name: "Adjacent sibling combinator, matching element that is an adjacent sibling of an element with id (1)", selector: "#adjacent-div2+div", ctx: "", expect: ["adjacent-div4"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Adjacent sibling combinator, matching element with id that is an adjacent sibling of an element (1)", selector: "div+#adjacent-div4", ctx: "", expect: ["adjacent-div4"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Adjacent sibling combinator, matching element with id that is an adjacent sibling of an element with id (1)", selector: "#adjacent-div2+#adjacent-div4", ctx: "", expect: ["adjacent-div4"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Adjacent sibling combinator, matching element with class that is an adjacent sibling of an element with id (1)", selector: "#adjacent-div2+.adjacent-div4", ctx: "", expect: ["adjacent-div4"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Adjacent sibling combinator, matching element with class that is an adjacent sibling of an element with class (1)", selector: ".adjacent-div2+.adjacent-div4", ctx: "", expect: ["adjacent-div4"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Adjacent sibling combinator, matching p element that is an adjacent sibling of a div element (1)", selector: "#adjacent div+p", ctx: "", expect: ["adjacent-p2"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Adjacent sibling combinator, not matching element with id that is not an adjacent sibling of an element with id", selector: "#adjacent-div2+#adjacent-p2, #adjacent-div2+#adjacent-div1", ctx: "", expect: [] /*no matches*/, level: 2, testType: TEST_FIND},
+ {name: "Adjacent sibling combinator, surrounded by whitespace (1)", selector: "#adjacent-p2\t\r\n+\t\r\n#adjacent-p3", ctx: "", expect: ["adjacent-p3"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Adjacent sibling combinator, whitespace after (1)", selector: "#adjacent-p2+\t\r\n#adjacent-p3", ctx: "", expect: ["adjacent-p3"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Adjacent sibling combinator, whitespace before (1)", selector: "#adjacent-p2\t\r\n+#adjacent-p3", ctx: "", expect: ["adjacent-p3"], level: 2, testType: TEST_FIND | TEST_MATCH},
+ {name: "Adjacent sibling combinator, no whitespace (1)", selector: "#adjacent-p2+#adjacent-p3", ctx: "", expect: ["adjacent-p3"], level: 2, testType: TEST_FIND | TEST_MATCH},
+
+ // - General sibling combinator ~ (Level 3)
+ {name: "General sibling combinator, matching element that is a sibling of an element with id (1)", selector: "#sibling-div2~div", ctx: "", expect: ["sibling-div4", "sibling-div6"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "General sibling combinator, matching element with id that is a sibling of an element (1)", selector: "div~#sibling-div4", ctx: "", expect: ["sibling-div4"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "General sibling combinator, matching element with id that is a sibling of an element with id (1)", selector: "#sibling-div2~#sibling-div4", ctx: "", expect: ["sibling-div4"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "General sibling combinator, matching element with class that is a sibling of an element with id (1)", selector: "#sibling-div2~.sibling-div", ctx: "", expect: ["sibling-div4", "sibling-div6"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "General sibling combinator, matching p element that is a sibling of a div element (1)", selector: "#sibling div~p", ctx: "", expect: ["sibling-p2", "sibling-p3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "General sibling combinator, not matching element with id that is not a sibling after a p element (1)", selector: "#sibling>p~div", ctx: "", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+ {name: "General sibling combinator, not matching element with id that is not a sibling after an element with id", selector: "#sibling-div2~#sibling-div3, #sibling-div2~#sibling-div1", ctx: "", expect: [] /*no matches*/, level: 3, testType: TEST_FIND},
+ {name: "General sibling combinator, surrounded by whitespace (1)", selector: "#sibling-p2\t\r\n~\t\r\n#sibling-p3", ctx: "", expect: ["sibling-p3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "General sibling combinator, whitespace after (1)", selector: "#sibling-p2~\t\r\n#sibling-p3", ctx: "", expect: ["sibling-p3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "General sibling combinator, whitespace before (1)", selector: "#sibling-p2\t\r\n~#sibling-p3", ctx: "", expect: ["sibling-p3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+ {name: "General sibling combinator, no whitespace (1)", selector: "#sibling-p2~#sibling-p3", ctx: "", expect: ["sibling-p3"], level: 3, testType: TEST_FIND | TEST_MATCH},
+
+ // Group of selectors (comma)
+ {name: "Syntax, group of selectors separator, surrounded by whitespace (1)", selector: "#group em\t\r \n,\t\r \n#group strong", ctx: "", expect: ["group-em1", "group-strong1"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Syntax, group of selectors separator, whitespace after (1)", selector: "#group em,\t\r\n#group strong", ctx: "", expect: ["group-em1", "group-strong1"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Syntax, group of selectors separator, whitespace before (1)", selector: "#group em\t\r\n,#group strong", ctx: "", expect: ["group-em1", "group-strong1"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ {name: "Syntax, group of selectors separator, no whitespace (1)", selector: "#group em,#group strong", ctx: "", expect: ["group-em1", "group-strong1"], level: 1, testType: TEST_FIND | TEST_MATCH},
+];
diff --git a/testing/web-platform/tests/dom/nodes/support/NodeList-static-length-tampered.js b/testing/web-platform/tests/dom/nodes/support/NodeList-static-length-tampered.js
new file mode 100644
index 0000000000..51167e2ddc
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/support/NodeList-static-length-tampered.js
@@ -0,0 +1,46 @@
+"use strict";
+
+function makeStaticNodeList(length) {
+ const fooRoot = document.createElement("div");
+
+ for (var i = 0; i < length; i++) {
+ const el = document.createElement("span");
+ el.className = "foo";
+ fooRoot.append(el);
+ }
+
+ document.body.append(fooRoot);
+ return fooRoot.querySelectorAll(".foo");
+}
+
+const indexOfNodeList = new Function("nodeList", `
+ const __cacheBust = ${Math.random()};
+
+ const el = nodeList[50];
+
+ let index = -1;
+
+ for (var i = 0; i < 1e5 / 2; i++) {
+ for (var j = 0; j < nodeList.length; j++) {
+ if (nodeList[j] === el) {
+ index = j;
+ break;
+ }
+ }
+ }
+
+ return index;
+`);
+
+const arrayIndexOfNodeList = new Function("nodeList", `
+ const __cacheBust = ${Math.random()};
+
+ const el = nodeList[50];
+ const {indexOf} = Array.prototype;
+
+ for (var i = 0; i < 1e5; i++) {
+ var index = indexOf.call(nodeList, el);
+ }
+
+ return index;
+`);
diff --git a/testing/web-platform/tests/dom/nodes/svg-template-querySelector.html b/testing/web-platform/tests/dom/nodes/svg-template-querySelector.html
new file mode 100644
index 0000000000..5d2f634143
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/svg-template-querySelector.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>querySelector on template fragments with SVG elements</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<template id="template1"><div></div></template>
+<template id="template2"><svg></svg></template>
+<template id="template3"><div><svg></svg></div></template>
+
+<script>
+"use strict";
+
+test(() => {
+ const fragment = document.querySelector("#template1").content;
+ assert_not_equals(fragment.querySelector("div"), null);
+}, "querySelector works on template contents fragments with HTML elements (sanity check)");
+
+test(() => {
+ const fragment = document.querySelector("#template2").content;
+ assert_not_equals(fragment.querySelector("svg"), null);
+}, "querySelector works on template contents fragments with SVG elements");
+
+test(() => {
+ const fragment = document.querySelector("#template3").content;
+ assert_not_equals(fragment.firstChild.querySelector("svg"), null);
+}, "querySelector works on template contents fragments with nested SVG elements");
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-adopt-test.html b/testing/web-platform/tests/dom/ranges/Range-adopt-test.html
new file mode 100644
index 0000000000..3735fc38fd
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-adopt-test.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script>
+function createRangeWithUnparentedContainerOfSingleElement() {
+ const range = document.createRange();
+ const container = document.createElement("container");
+ const element = document.createElement("element");
+ container.appendChild(element);
+ range.selectNode(element);
+ return range;
+}
+function nestRangeInOuterContainer(range) {
+ range.startContainer.ownerDocument.createElement("outer").appendChild(range.startContainer);
+}
+function moveNodeToNewlyCreatedDocumentWithAppendChild(node) {
+ document.implementation.createDocument(null, null).appendChild(node);
+}
+
+//Tests removing only element
+test(()=>{
+ const range = createRangeWithUnparentedContainerOfSingleElement();
+ range.startContainer.removeChild(range.startContainer.firstChild);
+ assert_equals(range.endOffset, 0);
+}, "Range in document: Removing the only element in the range must collapse the range");
+
+
+//Tests removing only element after parented container moved to another document
+test(()=>{
+ const range = createRangeWithUnparentedContainerOfSingleElement();
+ nestRangeInOuterContainer(range);
+ moveNodeToNewlyCreatedDocumentWithAppendChild(range.startContainer);
+ assert_equals(range.endOffset, 0);
+}, "Parented range container moved to another document with appendChild: Moving the element to the other document must collapse the range");
+
+//Tests removing only element after parentless container moved oo another document
+test(()=>{
+ const range = createRangeWithUnparentedContainerOfSingleElement();
+ moveNodeToNewlyCreatedDocumentWithAppendChild(range.startContainer);
+ range.startContainer.removeChild(range.startContainer.firstChild);
+ assert_equals(range.endOffset, 0);
+}, "Parentless range container moved to another document with appendChild: Removing the only element in the range must collapse the range");
+
+//Tests removing only element after parentless container of container moved to another document
+test(()=>{
+ const range = createRangeWithUnparentedContainerOfSingleElement();
+ nestRangeInOuterContainer(range);
+ moveNodeToNewlyCreatedDocumentWithAppendChild(range.startContainer.parentNode);
+ range.startContainer.removeChild(range.startContainer.firstChild);
+ assert_equals(range.endOffset, 0);
+}, "Range container's parentless container moved to another document with appendChild: Removing the only element in the range must collapse the range");
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-attributes.html b/testing/web-platform/tests/dom/ranges/Range-attributes.html
new file mode 100644
index 0000000000..ced47edc50
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-attributes.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Range attributes</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<meta name=timeout content=long>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ var r = document.createRange();
+ assert_equals(r.startContainer, document)
+ assert_equals(r.endContainer, document)
+ assert_equals(r.startOffset, 0)
+ assert_equals(r.endOffset, 0)
+ assert_true(r.collapsed)
+ r.detach()
+ assert_equals(r.startContainer, document)
+ assert_equals(r.endContainer, document)
+ assert_equals(r.startOffset, 0)
+ assert_equals(r.endOffset, 0)
+ assert_true(r.collapsed)
+})
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-cloneContents.html b/testing/web-platform/tests/dom/ranges/Range-cloneContents.html
new file mode 100644
index 0000000000..6064151f62
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-cloneContents.html
@@ -0,0 +1,461 @@
+<!doctype html>
+<title>Range.cloneContents() tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<p>To debug test failures, add a query parameter "subtest" with the test id (like
+"?subtest=5"). Only that test will be run. Then you can look at the resulting
+iframe in the DOM.
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+testDiv.parentNode.removeChild(testDiv);
+
+var actualIframe = document.createElement("iframe");
+actualIframe.style.display = "none";
+document.body.appendChild(actualIframe);
+
+var expectedIframe = document.createElement("iframe");
+expectedIframe.style.display = "none";
+document.body.appendChild(expectedIframe);
+
+function myCloneContents(range) {
+ // "Let frag be a new DocumentFragment whose ownerDocument is the same as
+ // the ownerDocument of the context object's start node."
+ var ownerDoc = range.startContainer.nodeType == Node.DOCUMENT_NODE
+ ? range.startContainer
+ : range.startContainer.ownerDocument;
+ var frag = ownerDoc.createDocumentFragment();
+
+ // "If the context object's start and end are the same, abort this method,
+ // returning frag."
+ if (range.startContainer == range.endContainer
+ && range.startOffset == range.endOffset) {
+ return frag;
+ }
+
+ // "Let original start node, original start offset, original end node, and
+ // original end offset be the context object's start and end nodes and
+ // offsets, respectively."
+ var originalStartNode = range.startContainer;
+ var originalStartOffset = range.startOffset;
+ var originalEndNode = range.endContainer;
+ var originalEndOffset = range.endOffset;
+
+ // "If original start node and original end node are the same, and they are
+ // a Text, ProcessingInstruction, or Comment node:"
+ if (range.startContainer == range.endContainer
+ && (range.startContainer.nodeType == Node.TEXT_NODE
+ || range.startContainer.nodeType == Node.COMMENT_NODE
+ || range.startContainer.nodeType == Node.PROCESSING_INSTRUCTION_NODE)) {
+ // "Let clone be the result of calling cloneNode(false) on original
+ // start node."
+ var clone = originalStartNode.cloneNode(false);
+
+ // "Set the data of clone to the result of calling
+ // substringData(original start offset, original end offset − original
+ // start offset) on original start node."
+ clone.data = originalStartNode.substringData(originalStartOffset,
+ originalEndOffset - originalStartOffset);
+
+ // "Append clone as the last child of frag."
+ frag.appendChild(clone);
+
+ // "Abort this method, returning frag."
+ return frag;
+ }
+
+ // "Let common ancestor equal original start node."
+ var commonAncestor = originalStartNode;
+
+ // "While common ancestor is not an ancestor container of original end
+ // node, set common ancestor to its own parent."
+ while (!isAncestorContainer(commonAncestor, originalEndNode)) {
+ commonAncestor = commonAncestor.parentNode;
+ }
+
+ // "If original start node is an ancestor container of original end node,
+ // let first partially contained child be null."
+ var firstPartiallyContainedChild;
+ if (isAncestorContainer(originalStartNode, originalEndNode)) {
+ firstPartiallyContainedChild = null;
+ // "Otherwise, let first partially contained child be the first child of
+ // common ancestor that is partially contained in the context object."
+ } else {
+ for (var i = 0; i < commonAncestor.childNodes.length; i++) {
+ if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
+ firstPartiallyContainedChild = commonAncestor.childNodes[i];
+ break;
+ }
+ }
+ if (!firstPartiallyContainedChild) {
+ throw "Spec bug: no first partially contained child!";
+ }
+ }
+
+ // "If original end node is an ancestor container of original start node,
+ // let last partially contained child be null."
+ var lastPartiallyContainedChild;
+ if (isAncestorContainer(originalEndNode, originalStartNode)) {
+ lastPartiallyContainedChild = null;
+ // "Otherwise, let last partially contained child be the last child of
+ // common ancestor that is partially contained in the context object."
+ } else {
+ for (var i = commonAncestor.childNodes.length - 1; i >= 0; i--) {
+ if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
+ lastPartiallyContainedChild = commonAncestor.childNodes[i];
+ break;
+ }
+ }
+ if (!lastPartiallyContainedChild) {
+ throw "Spec bug: no last partially contained child!";
+ }
+ }
+
+ // "Let contained children be a list of all children of common ancestor
+ // that are contained in the context object, in tree order."
+ //
+ // "If any member of contained children is a DocumentType, raise a
+ // HIERARCHY_REQUEST_ERR exception and abort these steps."
+ var containedChildren = [];
+ for (var i = 0; i < commonAncestor.childNodes.length; i++) {
+ if (isContained(commonAncestor.childNodes[i], range)) {
+ if (commonAncestor.childNodes[i].nodeType
+ == Node.DOCUMENT_TYPE_NODE) {
+ return "HIERARCHY_REQUEST_ERR";
+ }
+ containedChildren.push(commonAncestor.childNodes[i]);
+ }
+ }
+
+ // "If first partially contained child is a Text, ProcessingInstruction, or Comment node:"
+ if (firstPartiallyContainedChild
+ && (firstPartiallyContainedChild.nodeType == Node.TEXT_NODE
+ || firstPartiallyContainedChild.nodeType == Node.COMMENT_NODE
+ || firstPartiallyContainedChild.nodeType == Node.PROCESSING_INSTRUCTION_NODE)) {
+ // "Let clone be the result of calling cloneNode(false) on original
+ // start node."
+ var clone = originalStartNode.cloneNode(false);
+
+ // "Set the data of clone to the result of calling substringData() on
+ // original start node, with original start offset as the first
+ // argument and (length of original start node − original start offset)
+ // as the second."
+ clone.data = originalStartNode.substringData(originalStartOffset,
+ nodeLength(originalStartNode) - originalStartOffset);
+
+ // "Append clone as the last child of frag."
+ frag.appendChild(clone);
+ // "Otherwise, if first partially contained child is not null:"
+ } else if (firstPartiallyContainedChild) {
+ // "Let clone be the result of calling cloneNode(false) on first
+ // partially contained child."
+ var clone = firstPartiallyContainedChild.cloneNode(false);
+
+ // "Append clone as the last child of frag."
+ frag.appendChild(clone);
+
+ // "Let subrange be a new Range whose start is (original start node,
+ // original start offset) and whose end is (first partially contained
+ // child, length of first partially contained child)."
+ var subrange = ownerDoc.createRange();
+ subrange.setStart(originalStartNode, originalStartOffset);
+ subrange.setEnd(firstPartiallyContainedChild,
+ nodeLength(firstPartiallyContainedChild));
+
+ // "Let subfrag be the result of calling cloneContents() on
+ // subrange."
+ var subfrag = myCloneContents(subrange);
+
+ // "For each child of subfrag, in order, append that child to clone as
+ // its last child."
+ for (var i = 0; i < subfrag.childNodes.length; i++) {
+ clone.appendChild(subfrag.childNodes[i]);
+ }
+ }
+
+ // "For each contained child in contained children:"
+ for (var i = 0; i < containedChildren.length; i++) {
+ // "Let clone be the result of calling cloneNode(true) of contained
+ // child."
+ var clone = containedChildren[i].cloneNode(true);
+
+ // "Append clone as the last child of frag."
+ frag.appendChild(clone);
+ }
+
+ // "If last partially contained child is a Text, ProcessingInstruction, or Comment node:"
+ if (lastPartiallyContainedChild
+ && (lastPartiallyContainedChild.nodeType == Node.TEXT_NODE
+ || lastPartiallyContainedChild.nodeType == Node.COMMENT_NODE
+ || lastPartiallyContainedChild.nodeType == Node.PROCESSING_INSTRUCTION_NODE)) {
+ // "Let clone be the result of calling cloneNode(false) on original
+ // end node."
+ var clone = originalEndNode.cloneNode(false);
+
+ // "Set the data of clone to the result of calling substringData(0,
+ // original end offset) on original end node."
+ clone.data = originalEndNode.substringData(0, originalEndOffset);
+
+ // "Append clone as the last child of frag."
+ frag.appendChild(clone);
+ // "Otherwise, if last partially contained child is not null:"
+ } else if (lastPartiallyContainedChild) {
+ // "Let clone be the result of calling cloneNode(false) on last
+ // partially contained child."
+ var clone = lastPartiallyContainedChild.cloneNode(false);
+
+ // "Append clone as the last child of frag."
+ frag.appendChild(clone);
+
+ // "Let subrange be a new Range whose start is (last partially
+ // contained child, 0) and whose end is (original end node, original
+ // end offset)."
+ var subrange = ownerDoc.createRange();
+ subrange.setStart(lastPartiallyContainedChild, 0);
+ subrange.setEnd(originalEndNode, originalEndOffset);
+
+ // "Let subfrag be the result of calling cloneContents() on
+ // subrange."
+ var subfrag = myCloneContents(subrange);
+
+ // "For each child of subfrag, in order, append that child to clone as
+ // its last child."
+ for (var i = 0; i < subfrag.childNodes.length; i++) {
+ clone.appendChild(subfrag.childNodes[i]);
+ }
+ }
+
+ // "Return frag."
+ return frag;
+}
+
+function restoreIframe(iframe, i) {
+ // Most of this function is designed to work around the fact that Opera
+ // doesn't let you add a doctype to a document that no longer has one, in
+ // any way I can figure out. I eventually compromised on something that
+ // will still let Opera pass most tests that don't actually involve
+ // doctypes.
+ while (iframe.contentDocument.firstChild
+ && iframe.contentDocument.firstChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
+ iframe.contentDocument.removeChild(iframe.contentDocument.firstChild);
+ }
+
+ while (iframe.contentDocument.lastChild
+ && iframe.contentDocument.lastChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
+ iframe.contentDocument.removeChild(iframe.contentDocument.lastChild);
+ }
+
+ if (!iframe.contentDocument.firstChild) {
+ // This will throw an exception in Opera if we reach here, which is why
+ // I try to avoid it. It will never happen in a browser that obeys the
+ // spec, so it's really just insurance. I don't think it actually gets
+ // hit by anything.
+ iframe.contentDocument.appendChild(iframe.contentDocument.implementation.createDocumentType("html", "", ""));
+ }
+ iframe.contentDocument.appendChild(referenceDoc.documentElement.cloneNode(true));
+ iframe.contentWindow.setupRangeTests();
+ iframe.contentWindow.testRangeInput = testRanges[i];
+ iframe.contentWindow.run();
+}
+
+function testCloneContents(i) {
+ restoreIframe(actualIframe, i);
+ restoreIframe(expectedIframe, i);
+
+ var actualRange = actualIframe.contentWindow.testRange;
+ var expectedRange = expectedIframe.contentWindow.testRange;
+ var actualFrag, expectedFrag;
+ var actualRoots, expectedRoots;
+
+ domTests[i].step(function() {
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual cloneContents()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated cloneContents()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+
+ // NOTE: We could just assume that cloneContents() doesn't change
+ // anything. That would simplify these tests, taken in isolation. But
+ // once we've already set up the whole apparatus for extractContents()
+ // and deleteContents(), we just reuse it here, on the theory of "why
+ // not test some more stuff if it's easy to do".
+ //
+ // Just to be pedantic, we'll test not only that the tree we're
+ // modifying is the same in expected vs. actual, but also that all the
+ // nodes originally in it were the same. Typically some nodes will
+ // become detached when the algorithm is run, but they still exist and
+ // references can still be kept to them, so they should also remain the
+ // same.
+ //
+ // We initialize the list to all nodes, and later on remove all the
+ // ones which still have parents, since the parents will presumably be
+ // tested for isEqualNode() and checking the children would be
+ // redundant.
+ var actualAllNodes = [];
+ var node = furthestAncestor(actualRange.startContainer);
+ do {
+ actualAllNodes.push(node);
+ } while (node = nextNode(node));
+
+ var expectedAllNodes = [];
+ var node = furthestAncestor(expectedRange.startContainer);
+ do {
+ expectedAllNodes.push(node);
+ } while (node = nextNode(node));
+
+ expectedFrag = myCloneContents(expectedRange);
+ if (typeof expectedFrag == "string") {
+ assert_throws_dom(
+ expectedFrag,
+ actualIframe.contentWindow.DOMException,
+ function() {
+ actualRange.cloneContents();
+ }
+ );
+ } else {
+ actualFrag = actualRange.cloneContents();
+ }
+
+ actualRoots = [];
+ for (var j = 0; j < actualAllNodes.length; j++) {
+ if (!actualAllNodes[j].parentNode) {
+ actualRoots.push(actualAllNodes[j]);
+ }
+ }
+
+ expectedRoots = [];
+ for (var j = 0; j < expectedAllNodes.length; j++) {
+ if (!expectedAllNodes[j].parentNode) {
+ expectedRoots.push(expectedAllNodes[j]);
+ }
+ }
+
+ for (var j = 0; j < actualRoots.length; j++) {
+ assertNodesEqual(actualRoots[j], expectedRoots[j], j ? "detached node #" + j : "tree root");
+
+ if (j == 0) {
+ // Clearly something is wrong if the node lists are different
+ // lengths. We want to report this only after we've already
+ // checked the main tree for equality, though, so it doesn't
+ // mask more interesting errors.
+ assert_equals(actualRoots.length, expectedRoots.length,
+ "Actual and expected DOMs were broken up into a different number of pieces by cloneContents() (this probably means you created or detached nodes when you weren't supposed to)");
+ }
+ }
+ });
+ domTests[i].done();
+
+ positionTests[i].step(function() {
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual cloneContents()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated cloneContents()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+
+ assert_true(actualRoots[0].isEqualNode(expectedRoots[0]),
+ "The resulting DOMs were not equal, so comparing positions makes no sense");
+
+ if (typeof expectedFrag == "string") {
+ // It's no longer true that, e.g., startContainer and endContainer
+ // must always be the same
+ return;
+ }
+
+ assert_equals(actualRange.startOffset, expectedRange.startOffset,
+ "Unexpected startOffset after cloneContents()");
+ // How do we decide that the two nodes are equal, since they're in
+ // different trees? Since the DOMs are the same, it's enough to check
+ // that the index in the parent is the same all the way up the tree.
+ // But we can first cheat by just checking they're actually equal.
+ assert_true(actualRange.startContainer.isEqualNode(expectedRange.startContainer),
+ "Unexpected startContainer after cloneContents(), expected " +
+ expectedRange.startContainer.nodeName.toLowerCase() + " but got " +
+ actualRange.startContainer.nodeName.toLowerCase());
+ var currentActual = actualRange.startContainer;
+ var currentExpected = expectedRange.startContainer;
+ var actual = "";
+ var expected = "";
+ while (currentActual && currentExpected) {
+ actual = indexOf(currentActual) + "-" + actual;
+ expected = indexOf(currentExpected) + "-" + expected;
+
+ currentActual = currentActual.parentNode;
+ currentExpected = currentExpected.parentNode;
+ }
+ actual = actual.substr(0, actual.length - 1);
+ expected = expected.substr(0, expected.length - 1);
+ assert_equals(actual, expected,
+ "startContainer superficially looks right but is actually the wrong node if you trace back its index in all its ancestors (I'm surprised this actually happened");
+ });
+ positionTests[i].done();
+
+ fragTests[i].step(function() {
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual cloneContents()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated cloneContents()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+
+ if (typeof expectedFrag == "string") {
+ // Comparing makes no sense
+ return;
+ }
+ assertNodesEqual(actualFrag, expectedFrag,
+ "returned fragment");
+ });
+ fragTests[i].done();
+}
+
+// First test a Range that has the no-op detach() called on it, synchronously
+test(function() {
+ var range = document.createRange();
+ range.detach();
+ assert_array_equals(range.cloneContents().childNodes, []);
+}, "Range.detach()");
+
+var iStart = 0;
+var iStop = testRanges.length;
+
+if (/subtest=[0-9]+/.test(location.search)) {
+ var matches = /subtest=([0-9]+)/.exec(location.search);
+ iStart = Number(matches[1]);
+ iStop = Number(matches[1]) + 1;
+}
+
+var domTests = [];
+var positionTests = [];
+var fragTests = [];
+
+for (var i = iStart; i < iStop; i++) {
+ domTests[i] = async_test("Resulting DOM for range " + i + " " + testRanges[i]);
+ positionTests[i] = async_test("Resulting cursor position for range " + i + " " + testRanges[i]);
+ fragTests[i] = async_test("Returned fragment for range " + i + " " + testRanges[i]);
+}
+
+var referenceDoc = document.implementation.createHTMLDocument("");
+referenceDoc.removeChild(referenceDoc.documentElement);
+
+actualIframe.onload = function() {
+ expectedIframe.onload = function() {
+ for (var i = iStart; i < iStop; i++) {
+ testCloneContents(i);
+ }
+ }
+ expectedIframe.src = "Range-test-iframe.html";
+ referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true));
+}
+actualIframe.src = "Range-test-iframe.html";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-cloneRange.html b/testing/web-platform/tests/dom/ranges/Range-cloneRange.html
new file mode 100644
index 0000000000..6c736df29f
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-cloneRange.html
@@ -0,0 +1,112 @@
+<!doctype html>
+<title>Range.cloneRange() and document.createRange() tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+function testCloneRange(rangeEndpoints) {
+ var range;
+ if (rangeEndpoints == "detached") {
+ range = document.createRange();
+ range.detach();
+ var clonedRange = range.cloneRange();
+ assert_equals(clonedRange.startContainer, range.startContainer,
+ "startContainers must be equal after cloneRange()");
+ assert_equals(clonedRange.startOffset, range.startOffset,
+ "startOffsets must be equal after cloneRange()");
+ assert_equals(clonedRange.endContainer, range.endContainer,
+ "endContainers must be equal after cloneRange()");
+ assert_equals(clonedRange.endOffset, range.endOffset,
+ "endOffsets must be equal after cloneRange()");
+ return;
+ }
+
+ // Have to account for Ranges involving Documents! We could just create
+ // the Range from the current document unconditionally, but some browsers
+ // (WebKit) don't implement setStart() and setEnd() per spec and will throw
+ // spurious exceptions at the time of this writing. No need to mask other
+ // bugs.
+ var ownerDoc = rangeEndpoints[0].nodeType == Node.DOCUMENT_NODE
+ ? rangeEndpoints[0]
+ : rangeEndpoints[0].ownerDocument;
+ range = ownerDoc.createRange();
+ // Here we throw in some createRange() tests, because why not. Have to
+ // test it someplace.
+ assert_equals(range.startContainer, ownerDoc,
+ "doc.createRange() must create Range whose startContainer is doc");
+ assert_equals(range.endContainer, ownerDoc,
+ "doc.createRange() must create Range whose endContainer is doc");
+ assert_equals(range.startOffset, 0,
+ "doc.createRange() must create Range whose startOffset is 0");
+ assert_equals(range.endOffset, 0,
+ "doc.createRange() must create Range whose endOffset is 0");
+
+ range.setStart(rangeEndpoints[0], rangeEndpoints[1]);
+ range.setEnd(rangeEndpoints[2], rangeEndpoints[3]);
+
+ // Make sure we bail out now if setStart or setEnd are buggy, so it doesn't
+ // create misleading failures later.
+ assert_equals(range.startContainer, rangeEndpoints[0],
+ "Sanity check on setStart()");
+ assert_equals(range.startOffset, rangeEndpoints[1],
+ "Sanity check on setStart()");
+ assert_equals(range.endContainer, rangeEndpoints[2],
+ "Sanity check on setEnd()");
+ assert_equals(range.endOffset, rangeEndpoints[3],
+ "Sanity check on setEnd()");
+
+ var clonedRange = range.cloneRange();
+
+ assert_equals(clonedRange.startContainer, range.startContainer,
+ "startContainers must be equal after cloneRange()");
+ assert_equals(clonedRange.startOffset, range.startOffset,
+ "startOffsets must be equal after cloneRange()");
+ assert_equals(clonedRange.endContainer, range.endContainer,
+ "endContainers must be equal after cloneRange()");
+ assert_equals(clonedRange.endOffset, range.endOffset,
+ "endOffsets must be equal after cloneRange()");
+
+ // Make sure that modifying one doesn't affect the other.
+ var testNode1 = ownerDoc.createTextNode("testing");
+ var testNode2 = ownerDoc.createTextNode("testing with different length");
+
+ range.setStart(testNode1, 1);
+ range.setEnd(testNode1, 2);
+ assert_equals(clonedRange.startContainer, rangeEndpoints[0],
+ "Modifying a Range must not modify its clone's startContainer");
+ assert_equals(clonedRange.startOffset, rangeEndpoints[1],
+ "Modifying a Range must not modify its clone's startOffset");
+ assert_equals(clonedRange.endContainer, rangeEndpoints[2],
+ "Modifying a Range must not modify its clone's endContainer");
+ assert_equals(clonedRange.endOffset, rangeEndpoints[3],
+ "Modifying a Range must not modify its clone's endOffset");
+
+ clonedRange.setStart(testNode2, 3);
+ clonedRange.setStart(testNode2, 4);
+
+ assert_equals(range.startContainer, testNode1,
+ "Modifying a clone must not modify the original Range's startContainer");
+ assert_equals(range.startOffset, 1,
+ "Modifying a clone must not modify the original Range's startOffset");
+ assert_equals(range.endContainer, testNode1,
+ "Modifying a clone must not modify the original Range's endContainer");
+ assert_equals(range.endOffset, 2,
+ "Modifying a clone must not modify the original Range's endOffset");
+}
+
+var tests = [];
+for (var i = 0; i < testRanges.length; i++) {
+ tests.push([
+ "Range " + i + " " + testRanges[i],
+ eval(testRanges[i])
+ ]);
+}
+generate_tests(testCloneRange, tests);
+
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-collapse.html b/testing/web-platform/tests/dom/ranges/Range-collapse.html
new file mode 100644
index 0000000000..141dbdf610
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-collapse.html
@@ -0,0 +1,67 @@
+<!doctype html>
+<title>Range.collapse() and .collapsed tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+function testCollapse(rangeEndpoints, toStart) {
+ // Have to account for Ranges involving Documents!
+ var ownerDoc = rangeEndpoints[0].nodeType == Node.DOCUMENT_NODE
+ ? rangeEndpoints[0]
+ : rangeEndpoints[0].ownerDocument;
+ var range = ownerDoc.createRange();
+ range.setStart(rangeEndpoints[0], rangeEndpoints[1]);
+ range.setEnd(rangeEndpoints[2], rangeEndpoints[3]);
+
+ var expectedContainer = toStart ? range.startContainer : range.endContainer;
+ var expectedOffset = toStart ? range.startOffset : range.endOffset;
+
+ assert_equals(range.collapsed, range.startContainer == range.endContainer
+ && range.startOffset == range.endOffset,
+ "collapsed must be true if and only if the start and end are equal");
+
+ if (toStart === undefined) {
+ range.collapse();
+ } else {
+ range.collapse(toStart);
+ }
+
+ assert_equals(range.startContainer, expectedContainer,
+ "Wrong startContainer");
+ assert_equals(range.endContainer, expectedContainer,
+ "Wrong endContainer");
+ assert_equals(range.startOffset, expectedOffset,
+ "Wrong startOffset");
+ assert_equals(range.endOffset, expectedOffset,
+ "Wrong endOffset");
+ assert_true(range.collapsed,
+ ".collapsed must be set after .collapsed()");
+}
+
+var tests = [];
+for (var i = 0; i < testRanges.length; i++) {
+ tests.push([
+ "Range " + i + " " + testRanges[i] + ", toStart true",
+ eval(testRanges[i]),
+ true
+ ]);
+ tests.push([
+ "Range " + i + " " + testRanges[i] + ", toStart false",
+ eval(testRanges[i]),
+ false
+ ]);
+ tests.push([
+ "Range " + i + " " + testRanges[i] + ", toStart omitted",
+ eval(testRanges[i]),
+ undefined
+ ]);
+}
+generate_tests(testCollapse, tests);
+
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-commonAncestorContainer-2.html b/testing/web-platform/tests/dom/ranges/Range-commonAncestorContainer-2.html
new file mode 100644
index 0000000000..f0a3e451cd
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-commonAncestorContainer-2.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>Range.commonAncestorContainer</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ var range = document.createRange();
+ range.detach();
+ assert_equals(range.commonAncestorContainer, document);
+}, "Detached Range")
+test(function() {
+ var df = document.createDocumentFragment();
+ var foo = df.appendChild(document.createElement("foo"));
+ foo.appendChild(document.createTextNode("Foo"));
+ var bar = df.appendChild(document.createElement("bar"));
+ bar.appendChild(document.createComment("Bar"));
+ [
+ // start node, start offset, end node, end offset, expected cAC
+ [foo, 0, bar, 0, df],
+ [foo, 0, foo.firstChild, 3, foo],
+ [foo.firstChild, 0, bar, 0, df],
+ [foo.firstChild, 3, bar.firstChild, 2, df]
+ ].forEach(function(t) {
+ test(function() {
+ var range = document.createRange();
+ range.setStart(t[0], t[1]);
+ range.setEnd(t[2], t[3]);
+ assert_equals(range.commonAncestorContainer, t[4]);
+ })
+ });
+}, "Normal Ranges")
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-commonAncestorContainer.html b/testing/web-platform/tests/dom/ranges/Range-commonAncestorContainer.html
new file mode 100644
index 0000000000..7882ccc31f
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-commonAncestorContainer.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<title>Range.commonAncestorContainer tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+testRanges.unshift("[detached]");
+
+for (var i = 0; i < testRanges.length; i++) {
+ test(function() {
+ var range;
+ if (i == 0) {
+ range = document.createRange();
+ range.detach();
+ } else {
+ range = rangeFromEndpoints(eval(testRanges[i]));
+ }
+
+ // "Let container be start node."
+ var container = range.startContainer;
+
+ // "While container is not an inclusive ancestor of end node, let
+ // container be container's parent."
+ while (container != range.endContainer
+ && !isAncestor(container, range.endContainer)) {
+ container = container.parentNode;
+ }
+
+ // "Return container."
+ assert_equals(range.commonAncestorContainer, container);
+ }, i + ": range " + testRanges[i]);
+}
+
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-compareBoundaryPoints.html b/testing/web-platform/tests/dom/ranges/Range-compareBoundaryPoints.html
new file mode 100644
index 0000000000..9d150ae0ab
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-compareBoundaryPoints.html
@@ -0,0 +1,182 @@
+<!doctype html>
+<title>Range.compareBoundaryPoints() tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+var testRangesCached = [];
+testRangesCached.push(document.createRange());
+testRangesCached[0].detach();
+for (var i = 0; i < testRangesShort.length; i++) {
+ try {
+ testRangesCached.push(rangeFromEndpoints(eval(testRangesShort[i])));
+ } catch(e) {
+ testRangesCached.push(null);
+ }
+}
+
+var testRangesCachedClones = [];
+testRangesCachedClones.push(document.createRange());
+testRangesCachedClones[0].detach();
+for (var i = 1; i < testRangesCached.length; i++) {
+ if (testRangesCached[i]) {
+ testRangesCachedClones.push(testRangesCached[i].cloneRange());
+ } else {
+ testRangesCachedClones.push(null);
+ }
+}
+
+// We want to run a whole bunch of extra tests with invalid "how" values (not
+// 0-3), but it's excessive to run them for every single pair of ranges --
+// there are too many of them. So just run them for a handful of the tests.
+var extraTests = [0, // detached
+ 1 + testRanges.indexOf("[paras[0].firstChild, 2, paras[0].firstChild, 8]"),
+ 1 + testRanges.indexOf("[paras[0].firstChild, 3, paras[3], 1]"),
+ 1 + testRanges.indexOf("[testDiv, 0, comment, 5]"),
+ 1 + testRanges.indexOf("[foreignDoc.documentElement, 0, foreignDoc.documentElement, 1]")];
+
+for (var i = 0; i < testRangesCached.length; i++) {
+ var range1 = testRangesCached[i];
+ var range1Desc = i + " " + (i == 0 ? "[detached]" : testRanges[i - 1]);
+ for (var j = 0; j <= testRangesCachedClones.length; j++) {
+ var range2;
+ var range2Desc;
+ if (j == testRangesCachedClones.length) {
+ range2 = range1;
+ range2Desc = "same as first range";
+ } else {
+ range2 = testRangesCachedClones[j];
+ range2Desc = j + " " + (j == 0 ? "[detached]" : testRanges[j - 1]);
+ }
+
+ var hows = [Range.START_TO_START, Range.START_TO_END, Range.END_TO_END,
+ Range.END_TO_START];
+ if (extraTests.indexOf(i) != -1 && extraTests.indexOf(j) != -1) {
+ // TODO: Make some type of reusable utility function to do this
+ // work.
+ hows.push(-1, 4, 5, NaN, -0, +Infinity, -Infinity);
+ [65536, -65536, 65536*65536, 0.5, -0.5, -72.5].forEach(function(addend) {
+ hows.push(-1 + addend, 0 + addend, 1 + addend,
+ 2 + addend, 3 + addend, 4 + addend);
+ });
+ hows.forEach(function(how) { hows.push(String(how)) });
+ hows.push("6.5536e4", null, undefined, true, false, "", "quasit");
+ }
+
+ for (var k = 0; k < hows.length; k++) {
+ var how = hows[k];
+ test(function() {
+ assert_not_equals(range1, null,
+ "Creating context range threw an exception");
+ assert_not_equals(range2, null,
+ "Creating argument range threw an exception");
+
+ // Convert how per WebIDL. TODO: Make some type of reusable
+ // utility function to do this work.
+ // "Let number be the result of calling ToNumber on the input
+ // argument."
+ var convertedHow = Number(how);
+
+ // "If number is NaN, +0, −0, +∞, or −∞, return +0."
+ if (isNaN(convertedHow)
+ || convertedHow == 0
+ || convertedHow == Infinity
+ || convertedHow == -Infinity) {
+ convertedHow = 0;
+ } else {
+ // "Let posInt be sign(number) * floor(abs(number))."
+ var posInt = (convertedHow < 0 ? -1 : 1) * Math.floor(Math.abs(convertedHow));
+
+ // "Let int16bit be posInt modulo 2^16; that is, a finite
+ // integer value k of Number type with positive sign and
+ // less than 2^16 in magnitude such that the mathematical
+ // difference of posInt and k is mathematically an integer
+ // multiple of 2^16."
+ //
+ // "Return int16bit."
+ convertedHow = posInt % 65536;
+ if (convertedHow < 0) {
+ convertedHow += 65536;
+ }
+ }
+
+ // Now to the actual algorithm.
+ // "If how is not one of
+ // START_TO_START,
+ // START_TO_END,
+ // END_TO_END, and
+ // END_TO_START,
+ // throw a "NotSupportedError" exception and terminate these
+ // steps."
+ if (convertedHow != Range.START_TO_START
+ && convertedHow != Range.START_TO_END
+ && convertedHow != Range.END_TO_END
+ && convertedHow != Range.END_TO_START) {
+ assert_throws_dom("NOT_SUPPORTED_ERR", function() {
+ range1.compareBoundaryPoints(how, range2);
+ }, "NotSupportedError required if first parameter doesn't convert to 0-3 per WebIDL");
+ return;
+ }
+
+ // "If context object's root is not the same as sourceRange's
+ // root, throw a "WrongDocumentError" exception and terminate
+ // these steps."
+ if (furthestAncestor(range1.startContainer) != furthestAncestor(range2.startContainer)) {
+ assert_throws_dom("WRONG_DOCUMENT_ERR", function() {
+ range1.compareBoundaryPoints(how, range2);
+ }, "WrongDocumentError required if the ranges don't share a root");
+ return;
+ }
+
+ // "If how is:
+ // START_TO_START:
+ // Let this point be the context object's start.
+ // Let other point be sourceRange's start.
+ // START_TO_END:
+ // Let this point be the context object's end.
+ // Let other point be sourceRange's start.
+ // END_TO_END:
+ // Let this point be the context object's end.
+ // Let other point be sourceRange's end.
+ // END_TO_START:
+ // Let this point be the context object's start.
+ // Let other point be sourceRange's end."
+ var thisPoint = convertedHow == Range.START_TO_START || convertedHow == Range.END_TO_START
+ ? [range1.startContainer, range1.startOffset]
+ : [range1.endContainer, range1.endOffset];
+ var otherPoint = convertedHow == Range.START_TO_START || convertedHow == Range.START_TO_END
+ ? [range2.startContainer, range2.startOffset]
+ : [range2.endContainer, range2.endOffset];
+
+ // "If the position of this point relative to other point is
+ // before
+ // Return −1.
+ // equal
+ // Return 0.
+ // after
+ // Return 1."
+ var position = getPosition(thisPoint[0], thisPoint[1], otherPoint[0], otherPoint[1]);
+ var expected;
+ if (position == "before") {
+ expected = -1;
+ } else if (position == "equal") {
+ expected = 0;
+ } else if (position == "after") {
+ expected = 1;
+ }
+
+ assert_equals(range1.compareBoundaryPoints(how, range2), expected,
+ "Wrong return value");
+ }, i + "," + j + "," + k + ": context range " + range1Desc + ", argument range " + range2Desc + ", how " + format_value(how));
+ }
+ }
+}
+
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-comparePoint-2.html b/testing/web-platform/tests/dom/ranges/Range-comparePoint-2.html
new file mode 100644
index 0000000000..30a6c57ad9
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-comparePoint-2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Range.comparePoint</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<meta name=timeout content=long>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ var r = document.createRange();
+ r.detach()
+ assert_equals(r.comparePoint(document.body, 0), 1)
+})
+test(function() {
+ var r = document.createRange();
+ assert_throws_js(TypeError, function() { r.comparePoint(null, 0) })
+})
+test(function() {
+ var doc = document.implementation.createHTMLDocument("tralala")
+ var r = document.createRange();
+ assert_throws_dom("WRONG_DOCUMENT_ERR", function() { r.comparePoint(doc.body, 0) })
+})
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-comparePoint.html b/testing/web-platform/tests/dom/ranges/Range-comparePoint.html
new file mode 100644
index 0000000000..e18ac95c4c
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-comparePoint.html
@@ -0,0 +1,92 @@
+<!doctype html>
+<title>Range.comparePoint() tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+// Will be filled in on the first run for that range
+var testRangesCached = [];
+
+for (var i = 0; i < testPoints.length; i++) {
+ var node = eval(testPoints[i])[0];
+ var offset = eval(testPoints[i])[1];
+
+ // comparePoint is an unsigned long, so per WebIDL, we need to treat it as
+ // though it wrapped to an unsigned 32-bit integer.
+ var normalizedOffset = offset % Math.pow(2, 32);
+ if (normalizedOffset < 0) {
+ normalizedOffset += Math.pow(2, 32);
+ }
+
+ for (var j = 0; j < testRanges.length; j++) {
+ test(function() {
+ if (testRangesCached[j] === undefined) {
+ try {
+ testRangesCached[j] = rangeFromEndpoints(eval(testRanges[i]));
+ } catch(e) {
+ testRangesCached[j] = null;
+ }
+ }
+ assert_not_equals(testRangesCached[j], null,
+ "Setting up the range failed");
+
+ var range = testRangesCached[j].cloneRange();
+
+ // "If node's root is different from the context object's root,
+ // throw a "WrongDocumentError" exception and terminate these
+ // steps."
+ if (furthestAncestor(node) !== furthestAncestor(range.startContainer)) {
+ assert_throws_dom("WRONG_DOCUMENT_ERR", function() {
+ range.comparePoint(node, offset);
+ }, "Must throw WrongDocumentError if node and range have different roots");
+ return;
+ }
+
+ // "If node is a doctype, throw an "InvalidNodeTypeError" exception
+ // and terminate these steps."
+ if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
+ assert_throws_dom("INVALID_NODE_TYPE_ERR", function() {
+ range.comparePoint(node, offset);
+ }, "Must throw InvalidNodeTypeError if node is a doctype");
+ return;
+ }
+
+ // "If offset is greater than node's length, throw an
+ // "IndexSizeError" exception and terminate these steps."
+ if (normalizedOffset > nodeLength(node)) {
+ assert_throws_dom("INDEX_SIZE_ERR", function() {
+ range.comparePoint(node, offset);
+ }, "Must throw IndexSizeError if offset is greater than length");
+ return;
+ }
+
+ // "If (node, offset) is before start, return −1 and terminate
+ // these steps."
+ if (getPosition(node, normalizedOffset, range.startContainer, range.startOffset) === "before") {
+ assert_equals(range.comparePoint(node, offset), -1,
+ "Must return -1 if point is before start");
+ return;
+ }
+
+ // "If (node, offset) is after end, return 1 and terminate these
+ // steps."
+ if (getPosition(node, normalizedOffset, range.endContainer, range.endOffset) === "after") {
+ assert_equals(range.comparePoint(node, offset), 1,
+ "Must return 1 if point is after end");
+ return;
+ }
+
+ // "Return 0."
+ assert_equals(range.comparePoint(node, offset), 0,
+ "Must return 0 if point is neither before start nor after end");
+ }, "Point " + i + " " + testPoints[i] + ", range " + j + " " + testRanges[j]);
+ }
+}
+
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-constructor.html b/testing/web-platform/tests/dom/ranges/Range-constructor.html
new file mode 100644
index 0000000000..e8cfbef753
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-constructor.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>Range constructor test</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+"use strict";
+
+test(function() {
+ var range = new Range();
+ assert_equals(range.startContainer, document, "startContainer");
+ assert_equals(range.endContainer, document, "endContainer");
+ assert_equals(range.startOffset, 0, "startOffset");
+ assert_equals(range.endOffset, 0, "endOffset");
+ assert_true(range.collapsed, "collapsed");
+ assert_equals(range.commonAncestorContainer, document,
+ "commonAncestorContainer");
+});
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-deleteContents.html b/testing/web-platform/tests/dom/ranges/Range-deleteContents.html
new file mode 100644
index 0000000000..40dc400125
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-deleteContents.html
@@ -0,0 +1,337 @@
+<!doctype html>
+<title>Range.deleteContents() tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<p>To debug test failures, add a query parameter "subtest" with the test id (like
+"?subtest=5"). Only that test will be run. Then you can look at the resulting
+iframe in the DOM.
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+testDiv.parentNode.removeChild(testDiv);
+
+var actualIframe = document.createElement("iframe");
+actualIframe.style.display = "none";
+document.body.appendChild(actualIframe);
+
+var expectedIframe = document.createElement("iframe");
+expectedIframe.style.display = "none";
+document.body.appendChild(expectedIframe);
+
+function restoreIframe(iframe, i) {
+ // Most of this function is designed to work around the fact that Opera
+ // doesn't let you add a doctype to a document that no longer has one, in
+ // any way I can figure out. I eventually compromised on something that
+ // will still let Opera pass most tests that don't actually involve
+ // doctypes.
+ while (iframe.contentDocument.firstChild
+ && iframe.contentDocument.firstChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
+ iframe.contentDocument.removeChild(iframe.contentDocument.firstChild);
+ }
+
+ while (iframe.contentDocument.lastChild
+ && iframe.contentDocument.lastChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
+ iframe.contentDocument.removeChild(iframe.contentDocument.lastChild);
+ }
+
+ if (!iframe.contentDocument.firstChild) {
+ // This will throw an exception in Opera if we reach here, which is why
+ // I try to avoid it. It will never happen in a browser that obeys the
+ // spec, so it's really just insurance. I don't think it actually gets
+ // hit by anything.
+ iframe.contentDocument.appendChild(iframe.contentDocument.implementation.createDocumentType("html", "", ""));
+ }
+ iframe.contentDocument.appendChild(referenceDoc.documentElement.cloneNode(true));
+ iframe.contentWindow.setupRangeTests();
+ iframe.contentWindow.testRangeInput = testRanges[i];
+ iframe.contentWindow.run();
+}
+
+function myDeleteContents(range) {
+ // "If the context object's start and end are the same, abort this method."
+ if (range.startContainer == range.endContainer
+ && range.startOffset == range.endOffset) {
+ return;
+ }
+
+ // "Let original start node, original start offset, original end node, and
+ // original end offset be the context object's start and end nodes and
+ // offsets, respectively."
+ var originalStartNode = range.startContainer;
+ var originalStartOffset = range.startOffset;
+ var originalEndNode = range.endContainer;
+ var originalEndOffset = range.endOffset;
+
+ // "If original start node and original end node are the same, and they are
+ // a Text, ProcessingInstruction, or Comment node, replace data with node
+ // original start node, offset original start offset, count original end
+ // offset minus original start offset, and data the empty string, and then
+ // terminate these steps"
+ if (originalStartNode == originalEndNode
+ && (range.startContainer.nodeType == Node.TEXT_NODE
+ || range.startContainer.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+ || range.startContainer.nodeType == Node.COMMENT_NODE)) {
+ originalStartNode.deleteData(originalStartOffset, originalEndOffset - originalStartOffset);
+ return;
+ }
+
+ // "Let nodes to remove be a list of all the Nodes that are contained in
+ // the context object, in tree order, omitting any Node whose parent is
+ // also contained in the context object."
+ //
+ // We rely on the fact that the contained nodes must lie in tree order
+ // between the start node, and the end node's last descendant (inclusive).
+ var nodesToRemove = [];
+ var stop = nextNodeDescendants(range.endContainer);
+ for (var node = range.startContainer; node != stop; node = nextNode(node)) {
+ if (isContained(node, range)
+ && !(node.parentNode && isContained(node.parentNode, range))) {
+ nodesToRemove.push(node);
+ }
+ }
+
+ // "If original start node is an ancestor container of original end node,
+ // set new node to original start node and new offset to original start
+ // offset."
+ var newNode;
+ var newOffset;
+ if (originalStartNode == originalEndNode
+ || originalEndNode.compareDocumentPosition(originalStartNode) & Node.DOCUMENT_POSITION_CONTAINS) {
+ newNode = originalStartNode;
+ newOffset = originalStartOffset;
+ // "Otherwise:"
+ } else {
+ // "Let reference node equal original start node."
+ var referenceNode = originalStartNode;
+
+ // "While reference node's parent is not null and is not an ancestor
+ // container of original end node, set reference node to its parent."
+ while (referenceNode.parentNode
+ && referenceNode.parentNode != originalEndNode
+ && !(originalEndNode.compareDocumentPosition(referenceNode.parentNode) & Node.DOCUMENT_POSITION_CONTAINS)) {
+ referenceNode = referenceNode.parentNode;
+ }
+
+ // "Set new node to the parent of reference node, and new offset to one
+ // plus the index of reference node."
+ newNode = referenceNode.parentNode;
+ newOffset = 1 + indexOf(referenceNode);
+ }
+
+ // "If original start node is a Text, ProcessingInstruction, or Comment node,
+ // replace data with node original start node, offset original start offset,
+ // count original start node's length minus original start offset, data the
+ // empty start"
+ if (originalStartNode.nodeType == Node.TEXT_NODE
+ || originalStartNode.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+ || originalStartNode.nodeType == Node.COMMENT_NODE) {
+ originalStartNode.deleteData(originalStartOffset, nodeLength(originalStartNode) - originalStartOffset);
+ }
+
+ // "For each node in nodes to remove, in order, remove node from its
+ // parent."
+ for (var i = 0; i < nodesToRemove.length; i++) {
+ nodesToRemove[i].parentNode.removeChild(nodesToRemove[i]);
+ }
+
+ // "If original end node is a Text, ProcessingInstruction, or Comment node,
+ // replace data with node original end node, offset 0, count original end
+ // offset, and data the empty string."
+ if (originalEndNode.nodeType == Node.TEXT_NODE
+ || originalEndNode.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+ || originalEndNode.nodeType == Node.COMMENT_NODE) {
+ originalEndNode.deleteData(0, originalEndOffset);
+ }
+
+ // "Set the context object's start and end to (new node, new offset)."
+ range.setStart(newNode, newOffset);
+ range.setEnd(newNode, newOffset);
+}
+
+function testDeleteContents(i) {
+ restoreIframe(actualIframe, i);
+ restoreIframe(expectedIframe, i);
+
+ var actualRange = actualIframe.contentWindow.testRange;
+ var expectedRange = expectedIframe.contentWindow.testRange;
+ var actualRoots, expectedRoots;
+
+ domTests[i].step(function() {
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual deleteContents()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated deleteContents()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+
+ // Just to be pedantic, we'll test not only that the tree we're
+ // modifying is the same in expected vs. actual, but also that all the
+ // nodes originally in it were the same. Typically some nodes will
+ // become detached when the algorithm is run, but they still exist and
+ // references can still be kept to them, so they should also remain the
+ // same.
+ //
+ // We initialize the list to all nodes, and later on remove all the
+ // ones which still have parents, since the parents will presumably be
+ // tested for isEqualNode() and checking the children would be
+ // redundant.
+ var actualAllNodes = [];
+ var node = furthestAncestor(actualRange.startContainer);
+ do {
+ actualAllNodes.push(node);
+ } while (node = nextNode(node));
+
+ var expectedAllNodes = [];
+ var node = furthestAncestor(expectedRange.startContainer);
+ do {
+ expectedAllNodes.push(node);
+ } while (node = nextNode(node));
+
+ actualRange.deleteContents();
+ myDeleteContents(expectedRange);
+
+ actualRoots = [];
+ for (var j = 0; j < actualAllNodes.length; j++) {
+ if (!actualAllNodes[j].parentNode) {
+ actualRoots.push(actualAllNodes[j]);
+ }
+ }
+
+ expectedRoots = [];
+ for (var j = 0; j < expectedAllNodes.length; j++) {
+ if (!expectedAllNodes[j].parentNode) {
+ expectedRoots.push(expectedAllNodes[j]);
+ }
+ }
+
+ for (var j = 0; j < actualRoots.length; j++) {
+ if (!actualRoots[j].isEqualNode(expectedRoots[j])) {
+ var msg = j ? "detached node #" + j : "tree root";
+ msg = "Actual and expected mismatch for " + msg + ". ";
+
+ // Find the specific error
+ var actual = actualRoots[j];
+ var expected = expectedRoots[j];
+
+ while (actual && expected) {
+ assert_equals(actual.nodeType, expected.nodeType,
+ msg + "First difference: differing nodeType");
+ assert_equals(actual.nodeName, expected.nodeName,
+ msg + "First difference: differing nodeName");
+ assert_equals(actual.nodeValue, expected.nodeValue,
+ msg + 'First difference: differing nodeValue (nodeName = "' + actual.nodeName + '")');
+ assert_equals(actual.childNodes.length, expected.childNodes.length,
+ msg + 'First difference: differing number of children (nodeName = "' + actual.nodeName + '")');
+ actual = nextNode(actual);
+ expected = nextNode(expected);
+ }
+
+ assert_unreached("DOMs were not equal but we couldn't figure out why");
+ }
+
+ if (j == 0) {
+ // Clearly something is wrong if the node lists are different
+ // lengths. We want to report this only after we've already
+ // checked the main tree for equality, though, so it doesn't
+ // mask more interesting errors.
+ assert_equals(actualRoots.length, expectedRoots.length,
+ "Actual and expected DOMs were broken up into a different number of pieces by deleteContents() (this probably means you created or detached nodes when you weren't supposed to)");
+ }
+ }
+ });
+ domTests[i].done();
+
+ positionTests[i].step(function() {
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual deleteContents()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated deleteContents()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+ assert_true(actualRoots[0].isEqualNode(expectedRoots[0]),
+ "The resulting DOMs were not equal, so comparing positions makes no sense");
+
+ assert_equals(actualRange.startContainer, actualRange.endContainer,
+ "startContainer and endContainer must always be the same after deleteContents()");
+ assert_equals(actualRange.startOffset, actualRange.endOffset,
+ "startOffset and endOffset must always be the same after deleteContents()");
+ assert_equals(expectedRange.startContainer, expectedRange.endContainer,
+ "Test bug! Expected startContainer and endContainer must always be the same after deleteContents()");
+ assert_equals(expectedRange.startOffset, expectedRange.endOffset,
+ "Test bug! Expected startOffset and endOffset must always be the same after deleteContents()");
+
+ assert_equals(actualRange.startOffset, expectedRange.startOffset,
+ "Unexpected startOffset after deleteContents()");
+ // How do we decide that the two nodes are equal, since they're in
+ // different trees? Since the DOMs are the same, it's enough to check
+ // that the index in the parent is the same all the way up the tree.
+ // But we can first cheat by just checking they're actually equal.
+ assert_true(actualRange.startContainer.isEqualNode(expectedRange.startContainer),
+ "Unexpected startContainer after deleteContents(), expected " +
+ expectedRange.startContainer.nodeName.toLowerCase() + " but got " +
+ actualRange.startContainer.nodeName.toLowerCase());
+ var currentActual = actualRange.startContainer;
+ var currentExpected = expectedRange.startContainer;
+ var actual = "";
+ var expected = "";
+ while (currentActual && currentExpected) {
+ actual = indexOf(currentActual) + "-" + actual;
+ expected = indexOf(currentExpected) + "-" + expected;
+
+ currentActual = currentActual.parentNode;
+ currentExpected = currentExpected.parentNode;
+ }
+ actual = actual.substr(0, actual.length - 1);
+ expected = expected.substr(0, expected.length - 1);
+ assert_equals(actual, expected,
+ "startContainer superficially looks right but is actually the wrong node if you trace back its index in all its ancestors (I'm surprised this actually happened");
+ });
+ positionTests[i].done();
+}
+
+// First test a detached Range, synchronously
+test(function() {
+ var range = document.createRange();
+ range.detach();
+ range.deleteContents();
+}, "Detached Range");
+
+var iStart = 0;
+var iStop = testRanges.length;
+
+if (/subtest=[0-9]+/.test(location.search)) {
+ var matches = /subtest=([0-9]+)/.exec(location.search);
+ iStart = Number(matches[1]);
+ iStop = Number(matches[1]) + 1;
+}
+
+var domTests = [];
+var positionTests = [];
+
+for (var i = iStart; i < iStop; i++) {
+ domTests[i] = async_test("Resulting DOM for range " + i + " " + testRanges[i]);
+ positionTests[i] = async_test("Resulting cursor position for range " + i + " " + testRanges[i]);
+}
+
+var referenceDoc = document.implementation.createHTMLDocument("");
+referenceDoc.removeChild(referenceDoc.documentElement);
+
+actualIframe.onload = function() {
+ expectedIframe.onload = function() {
+ for (var i = iStart; i < iStop; i++) {
+ testDeleteContents(i);
+ }
+ }
+ expectedIframe.src = "Range-test-iframe.html";
+ referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true));
+}
+actualIframe.src = "Range-test-iframe.html";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-detach.html b/testing/web-platform/tests/dom/ranges/Range-detach.html
new file mode 100644
index 0000000000..ac35d71369
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-detach.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Range.detach</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<meta name=timeout content=long>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ var r = document.createRange();
+ r.detach()
+ r.detach()
+})
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-extractContents.html b/testing/web-platform/tests/dom/ranges/Range-extractContents.html
new file mode 100644
index 0000000000..88f8fa55f8
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-extractContents.html
@@ -0,0 +1,252 @@
+<!doctype html>
+<title>Range.extractContents() tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<p>To debug test failures, add a query parameter "subtest" with the test id (like
+"?subtest=5"). Only that test will be run. Then you can look at the resulting
+iframe in the DOM.
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+testDiv.parentNode.removeChild(testDiv);
+
+var actualIframe = document.createElement("iframe");
+actualIframe.style.display = "none";
+document.body.appendChild(actualIframe);
+
+var expectedIframe = document.createElement("iframe");
+expectedIframe.style.display = "none";
+document.body.appendChild(expectedIframe);
+
+function restoreIframe(iframe, i) {
+ // Most of this function is designed to work around the fact that Opera
+ // doesn't let you add a doctype to a document that no longer has one, in
+ // any way I can figure out. I eventually compromised on something that
+ // will still let Opera pass most tests that don't actually involve
+ // doctypes.
+ while (iframe.contentDocument.firstChild
+ && iframe.contentDocument.firstChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
+ iframe.contentDocument.removeChild(iframe.contentDocument.firstChild);
+ }
+
+ while (iframe.contentDocument.lastChild
+ && iframe.contentDocument.lastChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
+ iframe.contentDocument.removeChild(iframe.contentDocument.lastChild);
+ }
+
+ if (!iframe.contentDocument.firstChild) {
+ // This will throw an exception in Opera if we reach here, which is why
+ // I try to avoid it. It will never happen in a browser that obeys the
+ // spec, so it's really just insurance. I don't think it actually gets
+ // hit by anything.
+ iframe.contentDocument.appendChild(iframe.contentDocument.implementation.createDocumentType("html", "", ""));
+ }
+ iframe.contentDocument.appendChild(referenceDoc.documentElement.cloneNode(true));
+ iframe.contentWindow.setupRangeTests();
+ iframe.contentWindow.testRangeInput = testRanges[i];
+ iframe.contentWindow.run();
+}
+
+function testExtractContents(i) {
+ restoreIframe(actualIframe, i);
+ restoreIframe(expectedIframe, i);
+
+ var actualRange = actualIframe.contentWindow.testRange;
+ var expectedRange = expectedIframe.contentWindow.testRange;
+ var actualFrag, expectedFrag;
+ var actualRoots, expectedRoots;
+
+ domTests[i].step(function() {
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual extractContents()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated extractContents()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+
+ // Just to be pedantic, we'll test not only that the tree we're
+ // modifying is the same in expected vs. actual, but also that all the
+ // nodes originally in it were the same. Typically some nodes will
+ // become detached when the algorithm is run, but they still exist and
+ // references can still be kept to them, so they should also remain the
+ // same.
+ //
+ // We initialize the list to all nodes, and later on remove all the
+ // ones which still have parents, since the parents will presumably be
+ // tested for isEqualNode() and checking the children would be
+ // redundant.
+ var actualAllNodes = [];
+ var node = furthestAncestor(actualRange.startContainer);
+ do {
+ actualAllNodes.push(node);
+ } while (node = nextNode(node));
+
+ var expectedAllNodes = [];
+ var node = furthestAncestor(expectedRange.startContainer);
+ do {
+ expectedAllNodes.push(node);
+ } while (node = nextNode(node));
+
+ expectedFrag = myExtractContents(expectedRange);
+ if (typeof expectedFrag == "string") {
+ assert_throws_dom(
+ expectedFrag,
+ actualIframe.contentWindow.DOMException,
+ function() {
+ actualRange.extractContents();
+ }
+ );
+ } else {
+ actualFrag = actualRange.extractContents();
+ }
+
+ actualRoots = [];
+ for (var j = 0; j < actualAllNodes.length; j++) {
+ if (!actualAllNodes[j].parentNode) {
+ actualRoots.push(actualAllNodes[j]);
+ }
+ }
+
+ expectedRoots = [];
+ for (var j = 0; j < expectedAllNodes.length; j++) {
+ if (!expectedAllNodes[j].parentNode) {
+ expectedRoots.push(expectedAllNodes[j]);
+ }
+ }
+
+ for (var j = 0; j < actualRoots.length; j++) {
+ assertNodesEqual(actualRoots[j], expectedRoots[j], j ? "detached node #" + j : "tree root");
+
+ if (j == 0) {
+ // Clearly something is wrong if the node lists are different
+ // lengths. We want to report this only after we've already
+ // checked the main tree for equality, though, so it doesn't
+ // mask more interesting errors.
+ assert_equals(actualRoots.length, expectedRoots.length,
+ "Actual and expected DOMs were broken up into a different number of pieces by extractContents() (this probably means you created or detached nodes when you weren't supposed to)");
+ }
+ }
+ });
+ domTests[i].done();
+
+ positionTests[i].step(function() {
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual extractContents()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated extractContents()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+
+ assert_true(actualRoots[0].isEqualNode(expectedRoots[0]),
+ "The resulting DOMs were not equal, so comparing positions makes no sense");
+
+ if (typeof expectedFrag == "string") {
+ // It's no longer true that, e.g., startContainer and endContainer
+ // must always be the same
+ return;
+ }
+ assert_equals(actualRange.startContainer, actualRange.endContainer,
+ "startContainer and endContainer must always be the same after extractContents()");
+ assert_equals(actualRange.startOffset, actualRange.endOffset,
+ "startOffset and endOffset must always be the same after extractContents()");
+ assert_equals(expectedRange.startContainer, expectedRange.endContainer,
+ "Test bug! Expected startContainer and endContainer must always be the same after extractContents()");
+ assert_equals(expectedRange.startOffset, expectedRange.endOffset,
+ "Test bug! Expected startOffset and endOffset must always be the same after extractContents()");
+
+ assert_equals(actualRange.startOffset, expectedRange.startOffset,
+ "Unexpected startOffset after extractContents()");
+ // How do we decide that the two nodes are equal, since they're in
+ // different trees? Since the DOMs are the same, it's enough to check
+ // that the index in the parent is the same all the way up the tree.
+ // But we can first cheat by just checking they're actually equal.
+ assert_true(actualRange.startContainer.isEqualNode(expectedRange.startContainer),
+ "Unexpected startContainer after extractContents(), expected " +
+ expectedRange.startContainer.nodeName.toLowerCase() + " but got " +
+ actualRange.startContainer.nodeName.toLowerCase());
+ var currentActual = actualRange.startContainer;
+ var currentExpected = expectedRange.startContainer;
+ var actual = "";
+ var expected = "";
+ while (currentActual && currentExpected) {
+ actual = indexOf(currentActual) + "-" + actual;
+ expected = indexOf(currentExpected) + "-" + expected;
+
+ currentActual = currentActual.parentNode;
+ currentExpected = currentExpected.parentNode;
+ }
+ actual = actual.substr(0, actual.length - 1);
+ expected = expected.substr(0, expected.length - 1);
+ assert_equals(actual, expected,
+ "startContainer superficially looks right but is actually the wrong node if you trace back its index in all its ancestors (I'm surprised this actually happened");
+ });
+ positionTests[i].done();
+
+ fragTests[i].step(function() {
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual extractContents()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated extractContents()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+
+ if (typeof expectedFrag == "string") {
+ // Comparing makes no sense
+ return;
+ }
+ assertNodesEqual(actualFrag, expectedFrag,
+ "returned fragment");
+ });
+ fragTests[i].done();
+}
+
+// First test a detached Range, synchronously
+test(function() {
+ var range = document.createRange();
+ range.detach();
+ assert_array_equals(range.extractContents().childNodes, []);
+}, "Detached Range");
+
+var iStart = 0;
+var iStop = testRanges.length;
+
+if (/subtest=[0-9]+/.test(location.search)) {
+ var matches = /subtest=([0-9]+)/.exec(location.search);
+ iStart = Number(matches[1]);
+ iStop = Number(matches[1]) + 1;
+}
+
+var domTests = [];
+var positionTests = [];
+var fragTests = [];
+
+for (var i = iStart; i < iStop; i++) {
+ domTests[i] = async_test("Resulting DOM for range " + i + " " + testRanges[i]);
+ positionTests[i] = async_test("Resulting cursor position for range " + i + " " + testRanges[i]);
+ fragTests[i] = async_test("Returned fragment for range " + i + " " + testRanges[i]);
+}
+
+var referenceDoc = document.implementation.createHTMLDocument("");
+referenceDoc.removeChild(referenceDoc.documentElement);
+
+actualIframe.onload = function() {
+ expectedIframe.onload = function() {
+ for (var i = iStart; i < iStop; i++) {
+ testExtractContents(i);
+ }
+ }
+ expectedIframe.src = "Range-test-iframe.html";
+ referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true));
+}
+actualIframe.src = "Range-test-iframe.html";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-insertNode.html b/testing/web-platform/tests/dom/ranges/Range-insertNode.html
new file mode 100644
index 0000000000..b0a9d43f98
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-insertNode.html
@@ -0,0 +1,286 @@
+<!doctype html>
+<title>Range.insertNode() tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<p>To debug test failures, add a query parameter "subtest" with the test id (like
+"?subtest=5,16"). Only that test will be run. Then you can look at the resulting
+iframes in the DOM.
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+testDiv.parentNode.removeChild(testDiv);
+
+function restoreIframe(iframe, i, j) {
+ // Most of this function is designed to work around the fact that Opera
+ // doesn't let you add a doctype to a document that no longer has one, in
+ // any way I can figure out. I eventually compromised on something that
+ // will still let Opera pass most tests that don't actually involve
+ // doctypes.
+ while (iframe.contentDocument.firstChild
+ && iframe.contentDocument.firstChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
+ iframe.contentDocument.removeChild(iframe.contentDocument.firstChild);
+ }
+
+ while (iframe.contentDocument.lastChild
+ && iframe.contentDocument.lastChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
+ iframe.contentDocument.removeChild(iframe.contentDocument.lastChild);
+ }
+
+ if (!iframe.contentDocument.firstChild) {
+ // This will throw an exception in Opera if we reach here, which is why
+ // I try to avoid it. It will never happen in a browser that obeys the
+ // spec, so it's really just insurance. I don't think it actually gets
+ // hit by anything.
+ iframe.contentDocument.appendChild(iframe.contentDocument.implementation.createDocumentType("html", "", ""));
+ }
+ iframe.contentDocument.appendChild(referenceDoc.documentElement.cloneNode(true));
+ iframe.contentWindow.setupRangeTests();
+ iframe.contentWindow.testRangeInput = testRangesShort[i];
+ iframe.contentWindow.testNodeInput = testNodesShort[j];
+ iframe.contentWindow.run();
+}
+
+function testInsertNode(i, j) {
+ var actualRange;
+ var expectedRange;
+ var actualNode;
+ var expectedNode;
+ var actualRoots = [];
+ var expectedRoots = [];
+
+ var detached = false;
+
+ domTests[i][j].step(function() {
+ restoreIframe(actualIframe, i, j);
+ restoreIframe(expectedIframe, i, j);
+
+ actualRange = actualIframe.contentWindow.testRange;
+ expectedRange = expectedIframe.contentWindow.testRange;
+ actualNode = actualIframe.contentWindow.testNode;
+ expectedNode = expectedIframe.contentWindow.testNode;
+
+ try {
+ actualRange.collapsed;
+ } catch (e) {
+ detached = true;
+ }
+
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual insertNode()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated insertNode()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_not_equals(actualRange, null,
+ "Range produced in actual iframe was null");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+ assert_not_equals(expectedRange, null,
+ "Range produced in expected iframe was null");
+ assert_equals(typeof actualNode, "object",
+ "typeof Node produced in actual iframe");
+ assert_not_equals(actualNode, null,
+ "Node produced in actual iframe was null");
+ assert_equals(typeof expectedNode, "object",
+ "typeof Node produced in expected iframe");
+ assert_not_equals(expectedNode, null,
+ "Node produced in expected iframe was null");
+
+ // We want to test that the trees containing the ranges are equal, and
+ // also the trees containing the moved nodes. These might not be the
+ // same, if we're inserting a node from a detached tree or a different
+ // document.
+ //
+ // Detached ranges are always in the contentDocument.
+ if (detached) {
+ actualRoots.push(actualIframe.contentDocument);
+ expectedRoots.push(expectedIframe.contentDocument);
+ } else {
+ actualRoots.push(furthestAncestor(actualRange.startContainer));
+ expectedRoots.push(furthestAncestor(expectedRange.startContainer));
+ }
+
+ if (furthestAncestor(actualNode) != actualRoots[0]) {
+ actualRoots.push(furthestAncestor(actualNode));
+ }
+ if (furthestAncestor(expectedNode) != expectedRoots[0]) {
+ expectedRoots.push(furthestAncestor(expectedNode));
+ }
+
+ assert_equals(actualRoots.length, expectedRoots.length,
+ "Either the actual node and actual range are in the same tree but the expected are in different trees, or vice versa");
+
+ // This doctype stuff is to work around the fact that Opera 11.00 will
+ // move around doctypes within a document, even to totally invalid
+ // positions, but it won't allow a new doctype to be added to a
+ // document in any way I can figure out. So if we try moving a doctype
+ // to some invalid place, in Opera it will actually succeed, and then
+ // restoreIframe() will remove the doctype along with the root element,
+ // and then nothing can re-add the doctype. So instead, we catch it
+ // during the test itself and move it back to the right place while we
+ // still can.
+ //
+ // I spent *way* too much time debugging and working around this bug.
+ var actualDoctype = actualIframe.contentDocument.doctype;
+ var expectedDoctype = expectedIframe.contentDocument.doctype;
+
+ var result;
+ try {
+ result = myInsertNode(expectedRange, expectedNode);
+ } catch (e) {
+ if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
+ expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
+ }
+ throw e;
+ }
+ if (typeof result == "string") {
+ assert_throws_dom(result, actualIframe.contentWindow.DOMException, function() {
+ try {
+ actualRange.insertNode(actualNode);
+ } catch (e) {
+ if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
+ expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
+ }
+ if (actualDoctype != actualIframe.contentDocument.firstChild) {
+ actualIframe.contentDocument.insertBefore(actualDoctype, actualIframe.contentDocument.firstChild);
+ }
+ throw e;
+ }
+ }, "A " + result + " DOMException must be thrown in this case");
+ // Don't return, we still need to test DOM equality
+ } else {
+ try {
+ actualRange.insertNode(actualNode);
+ } catch (e) {
+ if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
+ expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
+ }
+ if (actualDoctype != actualIframe.contentDocument.firstChild) {
+ actualIframe.contentDocument.insertBefore(actualDoctype, actualIframe.contentDocument.firstChild);
+ }
+ throw e;
+ }
+ }
+
+ for (var k = 0; k < actualRoots.length; k++) {
+ assertNodesEqual(actualRoots[k], expectedRoots[k], k ? "moved node's tree root" : "range's tree root");
+ }
+ });
+ domTests[i][j].done();
+
+ positionTests[i][j].step(function() {
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual insertNode()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated insertNode()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_not_equals(actualRange, null,
+ "Range produced in actual iframe was null");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+ assert_not_equals(expectedRange, null,
+ "Range produced in expected iframe was null");
+ assert_equals(typeof actualNode, "object",
+ "typeof Node produced in actual iframe");
+ assert_not_equals(actualNode, null,
+ "Node produced in actual iframe was null");
+ assert_equals(typeof expectedNode, "object",
+ "typeof Node produced in expected iframe");
+ assert_not_equals(expectedNode, null,
+ "Node produced in expected iframe was null");
+
+ for (var k = 0; k < actualRoots.length; k++) {
+ assertNodesEqual(actualRoots[k], expectedRoots[k], k ? "moved node's tree root" : "range's tree root");
+ }
+
+ if (detached) {
+ // No further tests we can do
+ return;
+ }
+
+ assert_equals(actualRange.startOffset, expectedRange.startOffset,
+ "Unexpected startOffset after insertNode()");
+ assert_equals(actualRange.endOffset, expectedRange.endOffset,
+ "Unexpected endOffset after insertNode()");
+ // How do we decide that the two nodes are equal, since they're in
+ // different trees? Since the DOMs are the same, it's enough to check
+ // that the index in the parent is the same all the way up the tree.
+ // But we can first cheat by just checking they're actually equal.
+ assert_true(actualRange.startContainer.isEqualNode(expectedRange.startContainer),
+ "Unexpected startContainer after insertNode(), expected " +
+ expectedRange.startContainer.nodeName.toLowerCase() + " but got " +
+ actualRange.startContainer.nodeName.toLowerCase());
+ var currentActual = actualRange.startContainer;
+ var currentExpected = expectedRange.startContainer;
+ var actual = "";
+ var expected = "";
+ while (currentActual && currentExpected) {
+ actual = indexOf(currentActual) + "-" + actual;
+ expected = indexOf(currentExpected) + "-" + expected;
+
+ currentActual = currentActual.parentNode;
+ currentExpected = currentExpected.parentNode;
+ }
+ actual = actual.substr(0, actual.length - 1);
+ expected = expected.substr(0, expected.length - 1);
+ assert_equals(actual, expected,
+ "startContainer superficially looks right but is actually the wrong node if you trace back its index in all its ancestors (I'm surprised this actually happened");
+ });
+ positionTests[i][j].done();
+}
+
+testRanges.unshift('"detached"');
+
+var iStart = 0;
+var iStop = testRangesShort.length;
+var jStart = 0;
+var jStop = testNodesShort.length;
+
+if (/subtest=[0-9]+,[0-9]+/.test(location.search)) {
+ var matches = /subtest=([0-9]+),([0-9]+)/.exec(location.search);
+ iStart = Number(matches[1]);
+ iStop = Number(matches[1]) + 1;
+ jStart = Number(matches[2]) + 0;
+ jStop = Number(matches[2]) + 1;
+}
+
+var domTests = [];
+var positionTests = [];
+for (var i = iStart; i < iStop; i++) {
+ domTests[i] = [];
+ positionTests[i] = [];
+ for (var j = jStart; j < jStop; j++) {
+ domTests[i][j] = async_test(i + "," + j + ": resulting DOM for range " + testRangesShort[i] + ", node " + testNodesShort[j]);
+ positionTests[i][j] = async_test(i + "," + j + ": resulting range position for range " + testRangesShort[i] + ", node " + testNodesShort[j]);
+ }
+}
+
+var actualIframe = document.createElement("iframe");
+actualIframe.style.display = "none";
+document.body.appendChild(actualIframe);
+
+var expectedIframe = document.createElement("iframe");
+expectedIframe.style.display = "none";
+document.body.appendChild(expectedIframe);
+
+var referenceDoc = document.implementation.createHTMLDocument("");
+referenceDoc.removeChild(referenceDoc.documentElement);
+
+actualIframe.onload = function() {
+ expectedIframe.onload = function() {
+ for (var i = iStart; i < iStop; i++) {
+ for (var j = jStart; j < jStop; j++) {
+ testInsertNode(i, j);
+ }
+ }
+ }
+ expectedIframe.src = "Range-test-iframe.html";
+ referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true));
+}
+actualIframe.src = "Range-test-iframe.html";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-intersectsNode-2.html b/testing/web-platform/tests/dom/ranges/Range-intersectsNode-2.html
new file mode 100644
index 0000000000..48072d98af
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-intersectsNode-2.html
@@ -0,0 +1,36 @@
+<!doctype htlml>
+<title>Range.intersectsNode</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="div"><span id="s0">s0</span><span id="s1">s1</span><span id="s2">s2</span></div>
+<script>
+// Taken from Chromium bug: http://crbug.com/822510
+test(() => {
+ const range = new Range();
+ const div = document.getElementById('div');
+ const s0 = document.getElementById('s0');
+ const s1 = document.getElementById('s1');
+ const s2 = document.getElementById('s2');
+
+ // Range encloses s0
+ range.setStart(div, 0);
+ range.setEnd(div, 1);
+ assert_true(range.intersectsNode(s0), '[s0] range.intersectsNode(s0)');
+ assert_false(range.intersectsNode(s1), '[s0] range.intersectsNode(s1)');
+ assert_false(range.intersectsNode(s2), '[s0] range.intersectsNode(s2)');
+
+ // Range encloses s1
+ range.setStart(div, 1);
+ range.setEnd(div, 2);
+ assert_false(range.intersectsNode(s0), '[s1] range.intersectsNode(s0)');
+ assert_true(range.intersectsNode(s1), '[s1] range.intersectsNode(s1)');
+ assert_false(range.intersectsNode(s2), '[s1] range.intersectsNode(s2)');
+
+ // Range encloses s2
+ range.setStart(div, 2);
+ range.setEnd(div, 3);
+ assert_false(range.intersectsNode(s0), '[s2] range.intersectsNode(s0)');
+ assert_false(range.intersectsNode(s1), '[s2] range.intersectsNode(s1)');
+ assert_true(range.intersectsNode(s2), '[s2] range.intersectsNode(s2)');
+}, 'Range.intersectsNode() simple cases');
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-intersectsNode-binding.html b/testing/web-platform/tests/dom/ranges/Range-intersectsNode-binding.html
new file mode 100644
index 0000000000..57d159b030
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-intersectsNode-binding.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>Range.intersectsNode</title>
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<meta name=timeout content=long>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ var r = document.createRange();
+ assert_throws_js(TypeError, function() { r.intersectsNode(); });
+ assert_throws_js(TypeError, function() { r.intersectsNode(null); });
+ assert_throws_js(TypeError, function() { r.intersectsNode(undefined); });
+ assert_throws_js(TypeError, function() { r.intersectsNode(42); });
+ assert_throws_js(TypeError, function() { r.intersectsNode("foo"); });
+ assert_throws_js(TypeError, function() { r.intersectsNode({}); });
+ r.detach();
+ assert_throws_js(TypeError, function() { r.intersectsNode(); });
+ assert_throws_js(TypeError, function() { r.intersectsNode(null); });
+ assert_throws_js(TypeError, function() { r.intersectsNode(undefined); });
+ assert_throws_js(TypeError, function() { r.intersectsNode(42); });
+ assert_throws_js(TypeError, function() { r.intersectsNode("foo"); });
+ assert_throws_js(TypeError, function() { r.intersectsNode({}); });
+}, "Calling intersectsNode without an argument or with an invalid argument should throw a TypeError.")
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-intersectsNode-shadow.html b/testing/web-platform/tests/dom/ranges/Range-intersectsNode-shadow.html
new file mode 100644
index 0000000000..8219ba8285
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-intersectsNode-shadow.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>Range.intersectsNode with Shadow DOM</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="host"></div>
+<script>
+test(() => {
+ const host = document.getElementById("host");
+ host.attachShadow({ mode: "open" }).innerHTML = `<span>ABC</span>`;
+
+ const range = document.createRange();
+ range.selectNode(document.body);
+
+ assert_true(range.intersectsNode(host), "Should intersect host");
+ assert_false(range.intersectsNode(host.shadowRoot), "Should not intersect shadow root");
+ assert_false(range.intersectsNode(host.shadowRoot.firstElementChild), "Should not intersect shadow span");
+}, "Range.intersectsNode() doesn't return true for shadow children in other trees");
+</script>
+
diff --git a/testing/web-platform/tests/dom/ranges/Range-intersectsNode.html b/testing/web-platform/tests/dom/ranges/Range-intersectsNode.html
new file mode 100644
index 0000000000..97e10f6f0d
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-intersectsNode.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<title>Range.intersectsNode() tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+// Will be filled in on the first run for that range
+var testRangesCached = [];
+
+for (var i = 0; i < testNodes.length; i++) {
+ var node = eval(testNodes[i]);
+
+ for (var j = 0; j < testRanges.length; j++) {
+ test(function() {
+ if (testRangesCached[j] === undefined) {
+ try {
+ testRangesCached[j] = rangeFromEndpoints(eval(testRanges[i]));
+ } catch(e) {
+ testRangesCached[j] = null;
+ }
+ }
+ assert_not_equals(testRangesCached[j], null,
+ "Setting up the range failed");
+
+ var range = testRangesCached[j].cloneRange();
+
+ // "If node's root is different from the context object's root,
+ // return false and terminate these steps."
+ if (furthestAncestor(node) !== furthestAncestor(range.startContainer)) {
+ assert_equals(range.intersectsNode(node), false,
+ "Must return false if node and range have different roots");
+ return;
+ }
+
+ // "Let parent be node's parent."
+ var parent_ = node.parentNode;
+
+ // "If parent is null, return true and terminate these steps."
+ if (!parent_) {
+ assert_equals(range.intersectsNode(node), true,
+ "Must return true if node's parent is null");
+ return;
+ }
+
+ // "Let offset be node's index."
+ var offset = indexOf(node);
+
+ // "If (parent, offset) is before end and (parent, offset + 1) is
+ // after start, return true and terminate these steps."
+ if (getPosition(parent_, offset, range.endContainer, range.endOffset) === "before"
+ && getPosition(parent_, offset + 1, range.startContainer, range.startOffset) === "after") {
+ assert_equals(range.intersectsNode(node), true,
+ "Must return true if (parent, offset) is before range end and (parent, offset + 1) is after range start");
+ return;
+ }
+
+ // "Return false."
+ assert_equals(range.intersectsNode(node), false,
+ "Must return false if (parent, offset) is not before range end or (parent, offset + 1) is not after range start");
+ }, "Node " + i + " " + testNodes[i] + ", range " + j + " " + testRanges[j]);
+ }
+}
+
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-isPointInRange.html b/testing/web-platform/tests/dom/ranges/Range-isPointInRange.html
new file mode 100644
index 0000000000..80db97e844
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-isPointInRange.html
@@ -0,0 +1,83 @@
+<!doctype html>
+<title>Range.isPointInRange() tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+var testRangesCached = [];
+test(function() {
+ for (var j = 0; j < testRanges.length; j++) {
+ test(function() {
+ testRangesCached[j] = rangeFromEndpoints(eval(testRanges[j]));
+ }, "Set up for range " + j + " " + testRanges[j]);
+ }
+ var detachedRange = document.createRange();
+ detachedRange.detach();
+ testRanges.push("detached");
+ testRangesCached.push(detachedRange);
+}, "Setup");
+
+for (var i = 0; i < testPoints.length; i++) {
+ var node = eval(testPoints[i])[0];
+ var offset = eval(testPoints[i])[1];
+
+ // isPointInRange is an unsigned long, so per WebIDL, we need to treat it
+ // as though it wrapped to an unsigned 32-bit integer.
+ var normalizedOffset = offset % Math.pow(2, 32);
+ if (normalizedOffset < 0) {
+ normalizedOffset += Math.pow(2, 32);
+ }
+
+ for (var j = 0; j < testRanges.length; j++) {
+ test(function() {
+ var range = testRangesCached[j].cloneRange();
+
+ // "If node's root is different from the context object's root,
+ // return false and terminate these steps."
+ if (furthestAncestor(node) !== furthestAncestor(range.startContainer)) {
+ assert_false(range.isPointInRange(node, offset),
+ "Must return false if node has a different root from the context object");
+ return;
+ }
+
+ // "If node is a doctype, throw an "InvalidNodeTypeError" exception
+ // and terminate these steps."
+ if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
+ assert_throws_dom("INVALID_NODE_TYPE_ERR", function() {
+ range.isPointInRange(node, offset);
+ }, "Must throw InvalidNodeTypeError if node is a doctype");
+ return;
+ }
+
+ // "If offset is greater than node's length, throw an
+ // "IndexSizeError" exception and terminate these steps."
+ if (normalizedOffset > nodeLength(node)) {
+ assert_throws_dom("INDEX_SIZE_ERR", function() {
+ range.isPointInRange(node, offset);
+ }, "Must throw IndexSizeError if offset is greater than length");
+ return;
+ }
+
+ // "If (node, offset) is before start or after end, return false
+ // and terminate these steps."
+ if (getPosition(node, normalizedOffset, range.startContainer, range.startOffset) === "before"
+ || getPosition(node, normalizedOffset, range.endContainer, range.endOffset) === "after") {
+ assert_false(range.isPointInRange(node, offset),
+ "Must return false if point is before start or after end");
+ return;
+ }
+
+ // "Return true."
+ assert_true(range.isPointInRange(node, offset),
+ "Must return true if point is not before start, after end, or in different tree");
+ }, "Point " + i + " " + testPoints[i] + ", range " + j + " " + testRanges[j]);
+ }
+}
+
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-mutations-appendChild.html b/testing/web-platform/tests/dom/ranges/Range-mutations-appendChild.html
new file mode 100644
index 0000000000..5b5b5a55df
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-mutations-appendChild.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Range mutation tests - appendChild</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+
+<div id=log></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../common.js"></script>
+<script src="Range-mutations.js"></script>
+<script>
+doTests(appendChildTests, function(params) { return params[0] + ".appendChild(" + params[1] + ")" }, testAppendChild);
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-mutations-appendData.html b/testing/web-platform/tests/dom/ranges/Range-mutations-appendData.html
new file mode 100644
index 0000000000..1d4879d57c
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-mutations-appendData.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Range mutation tests - appendData</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+
+<div id=log></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../common.js"></script>
+<script src="Range-mutations.js"></script>
+<script>
+doTests(appendDataTests, function(params) { return params[0] + ".appendData(" + params[1] + ")" }, testAppendData);
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-mutations-dataChange.html b/testing/web-platform/tests/dom/ranges/Range-mutations-dataChange.html
new file mode 100644
index 0000000000..3683e8bb9b
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-mutations-dataChange.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Range mutation tests - update data by IDL attributes</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+
+<div id=log></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../common.js"></script>
+<script src="Range-mutations.js"></script>
+<script>
+doTests(dataChangeTests, function(params) { return params[0] + "." + eval(params[1]) + " " + eval(params[2]) + ' ' + params[3] }, testDataChange);
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-mutations-deleteData.html b/testing/web-platform/tests/dom/ranges/Range-mutations-deleteData.html
new file mode 100644
index 0000000000..5f2b852f5b
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-mutations-deleteData.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Range mutation tests - deleteData</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+
+<div id=log></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../common.js"></script>
+<script src="Range-mutations.js"></script>
+<script>
+doTests(deleteDataTests, function(params) { return params[0] + ".deleteData(" + params[1] + ", " + params[2] + ")" }, testDeleteData);
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-mutations-insertBefore.html b/testing/web-platform/tests/dom/ranges/Range-mutations-insertBefore.html
new file mode 100644
index 0000000000..c71b239547
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-mutations-insertBefore.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Range mutation tests - insertBefore</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+
+<div id=log></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../common.js"></script>
+<script src="Range-mutations.js"></script>
+<script>
+doTests(insertBeforeTests, function(params) { return params[0] + ".insertBefore(" + params[1] + ", " + params[2] + ")" }, testInsertBefore);
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-mutations-insertData.html b/testing/web-platform/tests/dom/ranges/Range-mutations-insertData.html
new file mode 100644
index 0000000000..fca533d503
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-mutations-insertData.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Range mutation tests - insertData</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+
+<div id=log></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../common.js"></script>
+<script src="Range-mutations.js"></script>
+<script>
+doTests(insertDataTests, function(params) { return params[0] + ".insertData(" + params[1] + ", " + params[2] + ")" }, testInsertData);
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-mutations-removeChild.html b/testing/web-platform/tests/dom/ranges/Range-mutations-removeChild.html
new file mode 100644
index 0000000000..a8d2381253
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-mutations-removeChild.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Range mutation tests - removeChild</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+
+<div id=log></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../common.js"></script>
+<script src="Range-mutations.js"></script>
+<script>
+doTests(removeChildTests, function(params) { return params[0] + ".parentNode.removeChild(" + params[0] + ")" }, testRemoveChild);
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-mutations-replaceChild.html b/testing/web-platform/tests/dom/ranges/Range-mutations-replaceChild.html
new file mode 100644
index 0000000000..a4ef0c365e
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-mutations-replaceChild.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Range mutation tests - replaceChild</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+
+<div id=log></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../common.js"></script>
+<script src="Range-mutations.js"></script>
+<script>
+doTests(replaceChildTests, function(params) { return params[0] + ".replaceChild(" + params[1] + ", " + params[2] + ")" }, testReplaceChild);
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-mutations-replaceData.html b/testing/web-platform/tests/dom/ranges/Range-mutations-replaceData.html
new file mode 100644
index 0000000000..55ddb146ea
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-mutations-replaceData.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Range mutation tests - replaceData</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+
+<div id=log></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../common.js"></script>
+<script src="Range-mutations.js"></script>
+<script>
+doTests(replaceDataTests, function(params) { return params[0] + ".replaceData(" + params[1] + ", " + params[2] + ", " + params[3] + ")" }, testReplaceData);
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-mutations-splitText.html b/testing/web-platform/tests/dom/ranges/Range-mutations-splitText.html
new file mode 100644
index 0000000000..fbb4c8d9b6
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-mutations-splitText.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Range mutation tests - splitText</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+
+<div id=log></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../common.js"></script>
+<script src="Range-mutations.js"></script>
+<script>
+doTests(splitTextTests, function(params) { return params[0] + ".splitText(" + params[1] + ")" }, testSplitText);
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-mutations.js b/testing/web-platform/tests/dom/ranges/Range-mutations.js
new file mode 100644
index 0000000000..23c013ac6a
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-mutations.js
@@ -0,0 +1,921 @@
+"use strict";
+
+// These tests probably use too much abstraction and too little copy-paste.
+// Reader beware.
+//
+// TODO:
+//
+// * Lots and lots and lots more different types of ranges
+// * insertBefore() with DocumentFragments
+// * Fill out other insert/remove tests
+// * normalize() (https://www.w3.org/Bugs/Public/show_bug.cgi?id=13843)
+
+// Give a textual description of the range we're testing, for the test names.
+function describeRange(startContainer, startOffset, endContainer, endOffset) {
+ if (startContainer == endContainer && startOffset == endOffset) {
+ return "range collapsed at (" + startContainer + ", " + startOffset + ")";
+ } else if (startContainer == endContainer) {
+ return "range on " + startContainer + " from " + startOffset + " to " + endOffset;
+ } else {
+ return "range from (" + startContainer + ", " + startOffset + ") to (" + endContainer + ", " + endOffset + ")";
+ }
+}
+
+// Lists of the various types of nodes we'll want to use. We use strings that
+// we can later eval(), so that we can produce legible test names.
+var textNodes = [
+ "paras[0].firstChild",
+ "paras[1].firstChild",
+ "foreignTextNode",
+ "xmlTextNode",
+ "detachedTextNode",
+ "detachedForeignTextNode",
+ "detachedXmlTextNode",
+];
+var commentNodes = [
+ "comment",
+ "foreignComment",
+ "xmlComment",
+ "detachedComment",
+ "detachedForeignComment",
+ "detachedXmlComment",
+];
+var characterDataNodes = textNodes.concat(commentNodes);
+
+// This function is slightly scary, but it works well enough, so . . .
+// sourceTests is an array of test data that will be altered in mysterious ways
+// before being passed off to doTest, descFn is something that takes an element
+// of sourceTests and produces the first part of a human-readable description
+// of the test, testFn is the function that doTest will call to do the actual
+// work and tell it what results to expect.
+function doTests(sourceTests, descFn, testFn) {
+ var tests = [];
+ for (var i = 0; i < sourceTests.length; i++) {
+ var params = sourceTests[i];
+ var len = params.length;
+ tests.push([
+ descFn(params) + ", with unselected " + describeRange(params[len - 4], params[len - 3], params[len - 2], params[len - 1]),
+ // The closure here ensures that the params that testFn get are the
+ // current version of params, not the version from the last
+ // iteration of this loop. We test that none of the parameters
+ // evaluate to undefined to catch bugs in our eval'ing, like
+ // mistyping a property name.
+ function(params) { return function() {
+ var evaledParams = params.map(eval);
+ for (var i = 0; i < evaledParams.length; i++) {
+ assert_not_equals(typeof evaledParams[i], "undefined",
+ "Test bug: " + params[i] + " is undefined");
+ }
+ return testFn.apply(null, evaledParams);
+ } }(params),
+ false,
+ params[len - 4],
+ params[len - 3],
+ params[len - 2],
+ params[len - 1]
+ ]);
+ tests.push([
+ descFn(params) + ", with selected " + describeRange(params[len - 4], params[len - 3], params[len - 2], params[len - 1]),
+ function(params) { return function(selectedRange) {
+ var evaledParams = params.slice(0, len - 4).map(eval);
+ for (var i = 0; i < evaledParams.length; i++) {
+ assert_not_equals(typeof evaledParams[i], "undefined",
+ "Test bug: " + params[i] + " is undefined");
+ }
+ // Override input range with the one that was actually selected when computing the expected result.
+ evaledParams = evaledParams.concat([selectedRange.startContainer, selectedRange.startOffset, selectedRange.endContainer, selectedRange.endOffset]);
+ return testFn.apply(null, evaledParams);
+ } }(params),
+ true,
+ params[len - 4],
+ params[len - 3],
+ params[len - 2],
+ params[len - 1]
+ ]);
+ }
+ generate_tests(doTest, tests);
+}
+
+// Set up the range, call the callback function to do the DOM modification and
+// tell us what to expect. The callback function needs to return a
+// four-element array with the expected start/end containers/offsets, and
+// receives no arguments. useSelection tells us whether the Range should be
+// added to a Selection and the Selection tested to ensure that the mutation
+// affects user selections as well as other ranges; every test is run with this
+// both false and true, because when it's set to true WebKit and Opera fail all
+// tests' sanity checks, which is unhelpful. The last four parameters just
+// tell us what range to build.
+function doTest(callback, useSelection, startContainer, startOffset, endContainer, endOffset) {
+ // Recreate all the test nodes in case they were altered by the last test
+ // run.
+ setupRangeTests();
+ startContainer = eval(startContainer);
+ startOffset = eval(startOffset);
+ endContainer = eval(endContainer);
+ endOffset = eval(endOffset);
+
+ var ownerDoc = startContainer.nodeType == Node.DOCUMENT_NODE
+ ? startContainer
+ : startContainer.ownerDocument;
+ var range = ownerDoc.createRange();
+ range.setStart(startContainer, startOffset);
+ range.setEnd(endContainer, endOffset);
+
+ if (useSelection) {
+ getSelection().removeAllRanges();
+ getSelection().addRange(range);
+
+ // Some browsers refuse to add a range unless it results in an actual visible selection.
+ if (!getSelection().rangeCount)
+ return;
+
+ // Override range with the one that was actually selected as it differs in some browsers.
+ range = getSelection().getRangeAt(0);
+ }
+
+ var expected = callback(range);
+
+ assert_equals(range.startContainer, expected[0],
+ "Wrong start container");
+ assert_equals(range.startOffset, expected[1],
+ "Wrong start offset");
+ assert_equals(range.endContainer, expected[2],
+ "Wrong end container");
+ assert_equals(range.endOffset, expected[3],
+ "Wrong end offset");
+}
+
+
+// Now we get to the specific tests.
+
+function testSplitText(oldNode, offset, startContainer, startOffset, endContainer, endOffset) {
+ // Save these for later
+ var originalStartOffset = startOffset;
+ var originalEndOffset = endOffset;
+ var originalLength = oldNode.length;
+
+ var newNode;
+ try {
+ newNode = oldNode.splitText(offset);
+ } catch (e) {
+ // Should only happen if offset is negative
+ return [startContainer, startOffset, endContainer, endOffset];
+ }
+
+ // First we adjust for replacing data:
+ //
+ // "Replace data with offset offset, count count, and data the empty
+ // string."
+ //
+ // That translates to offset = offset, count = originalLength - offset,
+ // data = "". node is oldNode.
+ //
+ // "For every boundary point whose node is node, and whose offset is
+ // greater than offset but less than or equal to offset plus count, set its
+ // offset to offset."
+ if (startContainer == oldNode
+ && startOffset > offset
+ && startOffset <= originalLength) {
+ startOffset = offset;
+ }
+
+ if (endContainer == oldNode
+ && endOffset > offset
+ && endOffset <= originalLength) {
+ endOffset = offset;
+ }
+
+ // "For every boundary point whose node is node, and whose offset is
+ // greater than offset plus count, add the length of data to its offset,
+ // then subtract count from it."
+ //
+ // Can't happen: offset plus count is originalLength.
+
+ // Now we insert a node, if oldNode's parent isn't null: "For each boundary
+ // point whose node is the new parent of the affected node and whose offset
+ // is greater than the new index of the affected node, add one to the
+ // boundary point's offset."
+ if (startContainer == oldNode.parentNode
+ && startOffset > 1 + indexOf(oldNode)) {
+ startOffset++;
+ }
+
+ if (endContainer == oldNode.parentNode
+ && endOffset > 1 + indexOf(oldNode)) {
+ endOffset++;
+ }
+
+ // Finally, the splitText stuff itself:
+ //
+ // "If parent is not null, run these substeps:
+ //
+ // * "For each range whose start node is node and start offset is greater
+ // than offset, set its start node to new node and decrease its start
+ // offset by offset.
+ //
+ // * "For each range whose end node is node and end offset is greater
+ // than offset, set its end node to new node and decrease its end offset
+ // by offset.
+ //
+ // * "For each range whose start node is parent and start offset is equal
+ // to the index of node + 1, increase its start offset by one.
+ //
+ // * "For each range whose end node is parent and end offset is equal to
+ // the index of node + 1, increase its end offset by one."
+ if (oldNode.parentNode) {
+ if (startContainer == oldNode && originalStartOffset > offset) {
+ startContainer = newNode;
+ startOffset = originalStartOffset - offset;
+ }
+
+ if (endContainer == oldNode && originalEndOffset > offset) {
+ endContainer = newNode;
+ endOffset = originalEndOffset - offset;
+ }
+
+ if (startContainer == oldNode.parentNode
+ && startOffset == 1 + indexOf(oldNode)) {
+ startOffset++;
+ }
+
+ if (endContainer == oldNode.parentNode
+ && endOffset == 1 + indexOf(oldNode)) {
+ endOffset++;
+ }
+ }
+
+ return [startContainer, startOffset, endContainer, endOffset];
+}
+
+// The offset argument is unsigned, so per WebIDL -1 should wrap to 4294967295,
+// which is probably longer than the length, so it should throw an exception.
+// This is no different from the other cases where the offset is longer than
+// the length, and the wrapping complicates my testing slightly, so I won't
+// bother testing negative values here or in other cases.
+var splitTextTests = [];
+for (var i = 0; i < textNodes.length; i++) {
+ var node = textNodes[i];
+ splitTextTests.push([node, 376, node, 0, node, 1]);
+ splitTextTests.push([node, 0, node, 0, node, 0]);
+ splitTextTests.push([node, 1, node, 1, node, 1]);
+ splitTextTests.push([node, node + ".length", node, node + ".length", node, node + ".length"]);
+ splitTextTests.push([node, 1, node, 1, node, 3]);
+ splitTextTests.push([node, 2, node, 1, node, 3]);
+ splitTextTests.push([node, 3, node, 1, node, 3]);
+}
+
+splitTextTests.push(
+ ["paras[0].firstChild", 1, "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", 1, "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", 1, "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 2, "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 3, "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 2, "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 3, "paras[0]", 0, "paras[0].firstChild", 3]
+);
+
+
+function testReplaceDataAlgorithm(node, offset, count, data, callback, startContainer, startOffset, endContainer, endOffset) {
+ // Mutation works the same any time DOM Core's "replace data" algorithm is
+ // invoked. node, offset, count, data are as in that algorithm. The
+ // callback is what does the actual setting. Not to be confused with
+ // testReplaceData, which tests the replaceData() method.
+
+ // Barring any provision to the contrary, the containers and offsets must
+ // not change.
+ var expectedStartContainer = startContainer;
+ var expectedStartOffset = startOffset;
+ var expectedEndContainer = endContainer;
+ var expectedEndOffset = endOffset;
+
+ var originalParent = node.parentNode;
+ var originalData = node.data;
+
+ var exceptionThrown = false;
+ try {
+ callback();
+ } catch (e) {
+ // Should only happen if offset is greater than length
+ exceptionThrown = true;
+ }
+
+ assert_equals(node.parentNode, originalParent,
+ "Sanity check failed: changing data changed the parent");
+
+ // "User agents must run the following steps whenever they replace data of
+ // a CharacterData node, as though they were written in the specification
+ // for that algorithm after all other steps. In particular, the steps must
+ // not be executed if the algorithm threw an exception."
+ if (exceptionThrown) {
+ assert_equals(node.data, originalData,
+ "Sanity check failed: exception thrown but data changed");
+ } else {
+ assert_equals(node.data,
+ originalData.substr(0, offset) + data + originalData.substr(offset + count),
+ "Sanity check failed: data not changed as expected");
+ }
+
+ // "For every boundary point whose node is node, and whose offset is
+ // greater than offset but less than or equal to offset plus count, set
+ // its offset to offset."
+ if (!exceptionThrown
+ && startContainer == node
+ && startOffset > offset
+ && startOffset <= offset + count) {
+ expectedStartOffset = offset;
+ }
+
+ if (!exceptionThrown
+ && endContainer == node
+ && endOffset > offset
+ && endOffset <= offset + count) {
+ expectedEndOffset = offset;
+ }
+
+ // "For every boundary point whose node is node, and whose offset is
+ // greater than offset plus count, add the length of data to its offset,
+ // then subtract count from it."
+ if (!exceptionThrown
+ && startContainer == node
+ && startOffset > offset + count) {
+ expectedStartOffset += data.length - count;
+ }
+
+ if (!exceptionThrown
+ && endContainer == node
+ && endOffset > offset + count) {
+ expectedEndOffset += data.length - count;
+ }
+
+ return [expectedStartContainer, expectedStartOffset, expectedEndContainer, expectedEndOffset];
+}
+
+function testInsertData(node, offset, data, startContainer, startOffset, endContainer, endOffset) {
+ return testReplaceDataAlgorithm(node, offset, 0, data,
+ function() { node.insertData(offset, data) },
+ startContainer, startOffset, endContainer, endOffset);
+}
+
+var insertDataTests = [];
+for (var i = 0; i < characterDataNodes.length; i++) {
+ var node = characterDataNodes[i];
+ insertDataTests.push([node, 376, '"foo"', node, 0, node, 1]);
+ insertDataTests.push([node, 0, '"foo"', node, 0, node, 0]);
+ insertDataTests.push([node, 1, '"foo"', node, 1, node, 1]);
+ insertDataTests.push([node, node + ".length", '"foo"', node, node + ".length", node, node + ".length"]);
+ insertDataTests.push([node, 1, '"foo"', node, 1, node, 3]);
+ insertDataTests.push([node, 2, '"foo"', node, 1, node, 3]);
+ insertDataTests.push([node, 3, '"foo"', node, 1, node, 3]);
+
+ insertDataTests.push([node, 376, '""', node, 0, node, 1]);
+ insertDataTests.push([node, 0, '""', node, 0, node, 0]);
+ insertDataTests.push([node, 1, '""', node, 1, node, 1]);
+ insertDataTests.push([node, node + ".length", '""', node, node + ".length", node, node + ".length"]);
+ insertDataTests.push([node, 1, '""', node, 1, node, 3]);
+ insertDataTests.push([node, 2, '""', node, 1, node, 3]);
+ insertDataTests.push([node, 3, '""', node, 1, node, 3]);
+}
+
+insertDataTests.push(
+ ["paras[0].firstChild", 1, '"foo"', "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", 1, '"foo"', "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", 1, '"foo"', "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 2, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 3, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 2, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 3, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3]
+);
+
+
+function testAppendData(node, data, startContainer, startOffset, endContainer, endOffset) {
+ return testReplaceDataAlgorithm(node, node.length, 0, data,
+ function() { node.appendData(data) },
+ startContainer, startOffset, endContainer, endOffset);
+}
+
+var appendDataTests = [];
+for (var i = 0; i < characterDataNodes.length; i++) {
+ var node = characterDataNodes[i];
+ appendDataTests.push([node, '"foo"', node, 0, node, 1]);
+ appendDataTests.push([node, '"foo"', node, 0, node, 0]);
+ appendDataTests.push([node, '"foo"', node, 1, node, 1]);
+ appendDataTests.push([node, '"foo"', node, 0, node, node + ".length"]);
+ appendDataTests.push([node, '"foo"', node, 1, node, node + ".length"]);
+ appendDataTests.push([node, '"foo"', node, node + ".length", node, node + ".length"]);
+ appendDataTests.push([node, '"foo"', node, 1, node, 3]);
+
+ appendDataTests.push([node, '""', node, 0, node, 1]);
+ appendDataTests.push([node, '""', node, 0, node, 0]);
+ appendDataTests.push([node, '""', node, 1, node, 1]);
+ appendDataTests.push([node, '""', node, 0, node, node + ".length"]);
+ appendDataTests.push([node, '""', node, 1, node, node + ".length"]);
+ appendDataTests.push([node, '""', node, node + ".length", node, node + ".length"]);
+ appendDataTests.push([node, '""', node, 1, node, 3]);
+}
+
+appendDataTests.push(
+ ["paras[0].firstChild", '""', "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", '""', "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", '""', "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", '""', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", '""', "paras[0]", 0, "paras[0].firstChild", 3],
+
+ ["paras[0].firstChild", '"foo"', "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", '"foo"', "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", '"foo"', "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", '"foo"', "paras[0]", 0, "paras[0].firstChild", 3]
+);
+
+
+function testDeleteData(node, offset, count, startContainer, startOffset, endContainer, endOffset) {
+ return testReplaceDataAlgorithm(node, offset, count, "",
+ function() { node.deleteData(offset, count) },
+ startContainer, startOffset, endContainer, endOffset);
+}
+
+var deleteDataTests = [];
+for (var i = 0; i < characterDataNodes.length; i++) {
+ var node = characterDataNodes[i];
+ deleteDataTests.push([node, 376, 2, node, 0, node, 1]);
+ deleteDataTests.push([node, 0, 2, node, 0, node, 0]);
+ deleteDataTests.push([node, 1, 2, node, 1, node, 1]);
+ deleteDataTests.push([node, node + ".length", 2, node, node + ".length", node, node + ".length"]);
+ deleteDataTests.push([node, 1, 2, node, 1, node, 3]);
+ deleteDataTests.push([node, 2, 2, node, 1, node, 3]);
+ deleteDataTests.push([node, 3, 2, node, 1, node, 3]);
+
+ deleteDataTests.push([node, 376, 0, node, 0, node, 1]);
+ deleteDataTests.push([node, 0, 0, node, 0, node, 0]);
+ deleteDataTests.push([node, 1, 0, node, 1, node, 1]);
+ deleteDataTests.push([node, node + ".length", 0, node, node + ".length", node, node + ".length"]);
+ deleteDataTests.push([node, 1, 0, node, 1, node, 3]);
+ deleteDataTests.push([node, 2, 0, node, 1, node, 3]);
+ deleteDataTests.push([node, 3, 0, node, 1, node, 3]);
+
+ deleteDataTests.push([node, 376, 631, node, 0, node, 1]);
+ deleteDataTests.push([node, 0, 631, node, 0, node, 0]);
+ deleteDataTests.push([node, 1, 631, node, 1, node, 1]);
+ deleteDataTests.push([node, node + ".length", 631, node, node + ".length", node, node + ".length"]);
+ deleteDataTests.push([node, 1, 631, node, 1, node, 3]);
+ deleteDataTests.push([node, 2, 631, node, 1, node, 3]);
+ deleteDataTests.push([node, 3, 631, node, 1, node, 3]);
+}
+
+deleteDataTests.push(
+ ["paras[0].firstChild", 1, 2, "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", 1, 2, "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 2, "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 2, "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 2, 2, "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 3, 2, "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 2, "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 2, 2, "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 3, 2, "paras[0]", 0, "paras[0].firstChild", 3]
+);
+
+
+function testReplaceData(node, offset, count, data, startContainer, startOffset, endContainer, endOffset) {
+ return testReplaceDataAlgorithm(node, offset, count, data,
+ function() { node.replaceData(offset, count, data) },
+ startContainer, startOffset, endContainer, endOffset);
+}
+
+var replaceDataTests = [];
+for (var i = 0; i < characterDataNodes.length; i++) {
+ var node = characterDataNodes[i];
+ replaceDataTests.push([node, 376, 0, '"foo"', node, 0, node, 1]);
+ replaceDataTests.push([node, 0, 0, '"foo"', node, 0, node, 0]);
+ replaceDataTests.push([node, 1, 0, '"foo"', node, 1, node, 1]);
+ replaceDataTests.push([node, node + ".length", 0, '"foo"', node, node + ".length", node, node + ".length"]);
+ replaceDataTests.push([node, 1, 0, '"foo"', node, 1, node, 3]);
+ replaceDataTests.push([node, 2, 0, '"foo"', node, 1, node, 3]);
+ replaceDataTests.push([node, 3, 0, '"foo"', node, 1, node, 3]);
+
+ replaceDataTests.push([node, 376, 0, '""', node, 0, node, 1]);
+ replaceDataTests.push([node, 0, 0, '""', node, 0, node, 0]);
+ replaceDataTests.push([node, 1, 0, '""', node, 1, node, 1]);
+ replaceDataTests.push([node, node + ".length", 0, '""', node, node + ".length", node, node + ".length"]);
+ replaceDataTests.push([node, 1, 0, '""', node, 1, node, 3]);
+ replaceDataTests.push([node, 2, 0, '""', node, 1, node, 3]);
+ replaceDataTests.push([node, 3, 0, '""', node, 1, node, 3]);
+
+ replaceDataTests.push([node, 376, 1, '"foo"', node, 0, node, 1]);
+ replaceDataTests.push([node, 0, 1, '"foo"', node, 0, node, 0]);
+ replaceDataTests.push([node, 1, 1, '"foo"', node, 1, node, 1]);
+ replaceDataTests.push([node, node + ".length", 1, '"foo"', node, node + ".length", node, node + ".length"]);
+ replaceDataTests.push([node, 1, 1, '"foo"', node, 1, node, 3]);
+ replaceDataTests.push([node, 2, 1, '"foo"', node, 1, node, 3]);
+ replaceDataTests.push([node, 3, 1, '"foo"', node, 1, node, 3]);
+
+ replaceDataTests.push([node, 376, 1, '""', node, 0, node, 1]);
+ replaceDataTests.push([node, 0, 1, '""', node, 0, node, 0]);
+ replaceDataTests.push([node, 1, 1, '""', node, 1, node, 1]);
+ replaceDataTests.push([node, node + ".length", 1, '""', node, node + ".length", node, node + ".length"]);
+ replaceDataTests.push([node, 1, 1, '""', node, 1, node, 3]);
+ replaceDataTests.push([node, 2, 1, '""', node, 1, node, 3]);
+ replaceDataTests.push([node, 3, 1, '""', node, 1, node, 3]);
+
+ replaceDataTests.push([node, 376, 47, '"foo"', node, 0, node, 1]);
+ replaceDataTests.push([node, 0, 47, '"foo"', node, 0, node, 0]);
+ replaceDataTests.push([node, 1, 47, '"foo"', node, 1, node, 1]);
+ replaceDataTests.push([node, node + ".length", 47, '"foo"', node, node + ".length", node, node + ".length"]);
+ replaceDataTests.push([node, 1, 47, '"foo"', node, 1, node, 3]);
+ replaceDataTests.push([node, 2, 47, '"foo"', node, 1, node, 3]);
+ replaceDataTests.push([node, 3, 47, '"foo"', node, 1, node, 3]);
+
+ replaceDataTests.push([node, 376, 47, '""', node, 0, node, 1]);
+ replaceDataTests.push([node, 0, 47, '""', node, 0, node, 0]);
+ replaceDataTests.push([node, 1, 47, '""', node, 1, node, 1]);
+ replaceDataTests.push([node, node + ".length", 47, '""', node, node + ".length", node, node + ".length"]);
+ replaceDataTests.push([node, 1, 47, '""', node, 1, node, 3]);
+ replaceDataTests.push([node, 2, 47, '""', node, 1, node, 3]);
+ replaceDataTests.push([node, 3, 47, '""', node, 1, node, 3]);
+}
+
+replaceDataTests.push(
+ ["paras[0].firstChild", 1, 0, '"foo"', "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", 1, 0, '"foo"', "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 0, '"foo"', "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 0, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 2, 0, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 3, 0, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 0, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 2, 0, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 3, 0, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+
+ ["paras[0].firstChild", 1, 1, '"foo"', "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", 1, 1, '"foo"', "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 1, '"foo"', "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 1, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 2, 1, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 3, 1, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 1, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 2, 1, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 3, 1, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+
+ ["paras[0].firstChild", 1, 47, '"foo"', "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", 1, 47, '"foo"', "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 47, '"foo"', "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 47, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 2, 47, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 3, 47, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 47, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 2, 47, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 3, 47, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3]
+);
+
+
+// There are lots of ways to set data, so we pass a callback that does the
+// actual setting.
+function testDataChange(node, attr, op, rval, startContainer, startOffset, endContainer, endOffset) {
+ return testReplaceDataAlgorithm(node, 0, node.length, op == "=" ? rval : node[attr] + rval,
+ function() {
+ if (op == "=") {
+ node[attr] = rval;
+ } else if (op == "+=") {
+ node[attr] += rval;
+ } else {
+ throw "Unknown op " + op;
+ }
+ },
+ startContainer, startOffset, endContainer, endOffset);
+}
+
+var dataChangeTests = [];
+var dataChangeTestAttrs = ["data", "textContent", "nodeValue"];
+for (var i = 0; i < characterDataNodes.length; i++) {
+ var node = characterDataNodes[i];
+ var dataChangeTestRanges = [
+ [node, 0, node, 0],
+ [node, 0, node, 1],
+ [node, 1, node, 1],
+ [node, 0, node, node + ".length"],
+ [node, 1, node, node + ".length"],
+ [node, node + ".length", node, node + ".length"],
+ ];
+
+ for (var j = 0; j < dataChangeTestRanges.length; j++) {
+ for (var k = 0; k < dataChangeTestAttrs.length; k++) {
+ dataChangeTests.push([
+ node,
+ '"' + dataChangeTestAttrs[k] + '"',
+ '"="',
+ '""',
+ ].concat(dataChangeTestRanges[j]));
+
+ dataChangeTests.push([
+ node,
+ '"' + dataChangeTestAttrs[k] + '"',
+ '"="',
+ '"foo"',
+ ].concat(dataChangeTestRanges[j]));
+
+ dataChangeTests.push([
+ node,
+ '"' + dataChangeTestAttrs[k] + '"',
+ '"="',
+ node + "." + dataChangeTestAttrs[k],
+ ].concat(dataChangeTestRanges[j]));
+
+ dataChangeTests.push([
+ node,
+ '"' + dataChangeTestAttrs[k] + '"',
+ '"+="',
+ '""',
+ ].concat(dataChangeTestRanges[j]));
+
+ dataChangeTests.push([
+ node,
+ '"' + dataChangeTestAttrs[k] + '"',
+ '"+="',
+ '"foo"',
+ ].concat(dataChangeTestRanges[j]));
+
+ dataChangeTests.push([
+ node,
+ '"' + dataChangeTestAttrs[k] + '"',
+ '"+="',
+ node + "." + dataChangeTestAttrs[k]
+ ].concat(dataChangeTestRanges[j]));
+ }
+ }
+}
+
+
+// Now we test node insertions and deletions, as opposed to just data changes.
+// To avoid loads of repetition, we define modifyForRemove() and
+// modifyForInsert().
+
+// If we were to remove removedNode from its parent, what would the boundary
+// point [node, offset] become? Returns [new node, new offset]. Must be
+// called BEFORE the node is actually removed, so its parent is not null. (If
+// the parent is null, it will do nothing.)
+function modifyForRemove(removedNode, point) {
+ var oldParent = removedNode.parentNode;
+ var oldIndex = indexOf(removedNode);
+ if (!oldParent) {
+ return point;
+ }
+
+ // "For each boundary point whose node is removed node or a descendant of
+ // it, set the boundary point to (old parent, old index)."
+ if (point[0] == removedNode || isDescendant(point[0], removedNode)) {
+ return [oldParent, oldIndex];
+ }
+
+ // "For each boundary point whose node is old parent and whose offset is
+ // greater than old index, subtract one from its offset."
+ if (point[0] == oldParent && point[1] > oldIndex) {
+ return [point[0], point[1] - 1];
+ }
+
+ return point;
+}
+
+// Update the given boundary point [node, offset] to account for the fact that
+// insertedNode was just inserted into its current position. This must be
+// called AFTER insertedNode was already inserted.
+function modifyForInsert(insertedNode, point) {
+ // "For each boundary point whose node is the new parent of the affected
+ // node and whose offset is greater than the new index of the affected
+ // node, add one to the boundary point's offset."
+ if (point[0] == insertedNode.parentNode && point[1] > indexOf(insertedNode)) {
+ return [point[0], point[1] + 1];
+ }
+
+ return point;
+}
+
+
+function testInsertBefore(newParent, affectedNode, refNode, startContainer, startOffset, endContainer, endOffset) {
+ var expectedStart = [startContainer, startOffset];
+ var expectedEnd = [endContainer, endOffset];
+
+ expectedStart = modifyForRemove(affectedNode, expectedStart);
+ expectedEnd = modifyForRemove(affectedNode, expectedEnd);
+
+ try {
+ newParent.insertBefore(affectedNode, refNode);
+ } catch (e) {
+ // For our purposes, assume that DOM Core is true -- i.e., ignore
+ // mutation events and similar.
+ return [startContainer, startOffset, endContainer, endOffset];
+ }
+
+ expectedStart = modifyForInsert(affectedNode, expectedStart);
+ expectedEnd = modifyForInsert(affectedNode, expectedEnd);
+
+ return expectedStart.concat(expectedEnd);
+}
+
+var insertBeforeTests = [
+ // Moving a node to its current position
+ ["testDiv", "paras[0]", "paras[1]", "paras[0]", 0, "paras[0]", 0],
+ ["testDiv", "paras[0]", "paras[1]", "paras[0]", 0, "paras[0]", 1],
+ ["testDiv", "paras[0]", "paras[1]", "paras[0]", 1, "paras[0]", 1],
+ ["testDiv", "paras[0]", "paras[1]", "testDiv", 0, "testDiv", 2],
+ ["testDiv", "paras[0]", "paras[1]", "testDiv", 1, "testDiv", 1],
+ ["testDiv", "paras[0]", "paras[1]", "testDiv", 1, "testDiv", 2],
+ ["testDiv", "paras[0]", "paras[1]", "testDiv", 2, "testDiv", 2],
+
+ // Stuff that actually moves something. Note that paras[0] and paras[1]
+ // are both children of testDiv.
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 1, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 0, "testDiv", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 0, "testDiv", 2],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 1, "testDiv", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 1, "testDiv", 2],
+ ["paras[0]", "paras[1]", "null", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "paras[1]", "null", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "null", "paras[0]", 1, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "null", "testDiv", 0, "testDiv", 1],
+ ["paras[0]", "paras[1]", "null", "testDiv", 0, "testDiv", 2],
+ ["paras[0]", "paras[1]", "null", "testDiv", 1, "testDiv", 1],
+ ["paras[0]", "paras[1]", "null", "testDiv", 1, "testDiv", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", 0],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 1, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "foreignDoc", 0],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "foreignDoc", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 1, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "null", "foreignDoc", 0, "foreignDoc", 1],
+ ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 1, "paras[0]", 1],
+
+ // Stuff that throws exceptions
+ ["paras[0]", "paras[0]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "testDiv", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "document", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "foreignDoc", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "document.doctype", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+];
+
+
+function testReplaceChild(newParent, newChild, oldChild, startContainer, startOffset, endContainer, endOffset) {
+ var expectedStart = [startContainer, startOffset];
+ var expectedEnd = [endContainer, endOffset];
+
+ expectedStart = modifyForRemove(oldChild, expectedStart);
+ expectedEnd = modifyForRemove(oldChild, expectedEnd);
+
+ if (newChild != oldChild) {
+ // Don't do this twice, if they're the same!
+ expectedStart = modifyForRemove(newChild, expectedStart);
+ expectedEnd = modifyForRemove(newChild, expectedEnd);
+ }
+
+ try {
+ newParent.replaceChild(newChild, oldChild);
+ } catch (e) {
+ return [startContainer, startOffset, endContainer, endOffset];
+ }
+
+ expectedStart = modifyForInsert(newChild, expectedStart);
+ expectedEnd = modifyForInsert(newChild, expectedEnd);
+
+ return expectedStart.concat(expectedEnd);
+}
+
+var replaceChildTests = [
+ // Moving a node to its current position. Doesn't match most browsers'
+ // behavior, but we probably want to keep the spec the same anyway:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=647603
+ ["testDiv", "paras[0]", "paras[0]", "paras[0]", 0, "paras[0]", 0],
+ ["testDiv", "paras[0]", "paras[0]", "paras[0]", 0, "paras[0]", 1],
+ ["testDiv", "paras[0]", "paras[0]", "paras[0]", 1, "paras[0]", 1],
+ ["testDiv", "paras[0]", "paras[0]", "testDiv", 0, "testDiv", 2],
+ ["testDiv", "paras[0]", "paras[0]", "testDiv", 1, "testDiv", 1],
+ ["testDiv", "paras[0]", "paras[0]", "testDiv", 1, "testDiv", 2],
+ ["testDiv", "paras[0]", "paras[0]", "testDiv", 2, "testDiv", 2],
+
+ // Stuff that actually moves something.
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 1, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 0, "testDiv", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 0, "testDiv", 2],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 1, "testDiv", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 1, "testDiv", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", 0],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 1, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "foreignDoc", 0],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "foreignDoc", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 1, "foreignDoc", 1],
+ ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 1, "paras[0]", 1],
+
+ // Stuff that throws exceptions
+ ["paras[0]", "paras[0]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "testDiv", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "document", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "foreignDoc", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "document.doctype", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+];
+
+
+function testAppendChild(newParent, affectedNode, startContainer, startOffset, endContainer, endOffset) {
+ var expectedStart = [startContainer, startOffset];
+ var expectedEnd = [endContainer, endOffset];
+
+ expectedStart = modifyForRemove(affectedNode, expectedStart);
+ expectedEnd = modifyForRemove(affectedNode, expectedEnd);
+
+ try {
+ newParent.appendChild(affectedNode);
+ } catch (e) {
+ return [startContainer, startOffset, endContainer, endOffset];
+ }
+
+ // These two lines will actually never do anything, if you think about it,
+ // but let's leave them in so correctness is more obvious.
+ expectedStart = modifyForInsert(affectedNode, expectedStart);
+ expectedEnd = modifyForInsert(affectedNode, expectedEnd);
+
+ return expectedStart.concat(expectedEnd);
+}
+
+var appendChildTests = [
+ // Moving a node to its current position
+ ["testDiv", "testDiv.lastChild", "testDiv.lastChild", 0, "testDiv.lastChild", 0],
+ ["testDiv", "testDiv.lastChild", "testDiv.lastChild", 0, "testDiv.lastChild", 1],
+ ["testDiv", "testDiv.lastChild", "testDiv.lastChild", 1, "testDiv.lastChild", 1],
+ ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length - 2", "testDiv", "testDiv.childNodes.length"],
+ ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length - 2", "testDiv", "testDiv.childNodes.length - 1"],
+ ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length - 1", "testDiv", "testDiv.childNodes.length"],
+ ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length - 1", "testDiv", "testDiv.childNodes.length - 1"],
+ ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length", "testDiv", "testDiv.childNodes.length"],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv.lastChild", 0, "detachedDiv.lastChild", 0],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv.lastChild", 0, "detachedDiv.lastChild", 1],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv.lastChild", 1, "detachedDiv.lastChild", 1],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNodes.length - 2", "detachedDiv", "detachedDiv.childNodes.length"],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNodes.length - 2", "detachedDiv", "detachedDiv.childNodes.length - 1"],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNodes.length - 1", "detachedDiv", "detachedDiv.childNodes.length"],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNodes.length - 1", "detachedDiv", "detachedDiv.childNodes.length - 1"],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNodes.length", "detachedDiv", "detachedDiv.childNodes.length"],
+
+ // Stuff that actually moves something
+ ["paras[0]", "paras[1]", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "paras[1]", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "paras[0]", 1, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "testDiv", 0, "testDiv", 1],
+ ["paras[0]", "paras[1]", "testDiv", 0, "testDiv", 2],
+ ["paras[0]", "paras[1]", "testDiv", 1, "testDiv", 1],
+ ["paras[0]", "paras[1]", "testDiv", 1, "testDiv", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc", "foreignDoc.childNodes.length - 1", "foreignDoc", "foreignDoc.childNodes.length"],
+ ["foreignDoc", "detachedComment", "foreignDoc", "foreignDoc.childNodes.length - 1", "foreignDoc", "foreignDoc.childNodes.length - 1"],
+ ["foreignDoc", "detachedComment", "foreignDoc", "foreignDoc.childNodes.length", "foreignDoc", "foreignDoc.childNodes.length"],
+ ["foreignDoc", "detachedComment", "detachedComment", 0, "detachedComment", 5],
+ ["paras[0]", "xmlTextNode", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "xmlTextNode", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "xmlTextNode", "paras[0]", 1, "paras[0]", 1],
+
+ // Stuff that throws exceptions
+ ["paras[0]", "paras[0]", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "testDiv", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "document", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "foreignDoc", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "document.doctype", "paras[0]", 0, "paras[0]", 1],
+];
+
+
+function testRemoveChild(affectedNode, startContainer, startOffset, endContainer, endOffset) {
+ var expectedStart = [startContainer, startOffset];
+ var expectedEnd = [endContainer, endOffset];
+
+ expectedStart = modifyForRemove(affectedNode, expectedStart);
+ expectedEnd = modifyForRemove(affectedNode, expectedEnd);
+
+ // We don't test cases where the parent is wrong, so this should never
+ // throw an exception.
+ affectedNode.parentNode.removeChild(affectedNode);
+
+ return expectedStart.concat(expectedEnd);
+}
+
+var removeChildTests = [
+ ["paras[0]", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "paras[0]", 1, "paras[0]", 1],
+ ["paras[0]", "testDiv", 0, "testDiv", 0],
+ ["paras[0]", "testDiv", 0, "testDiv", 1],
+ ["paras[0]", "testDiv", 1, "testDiv", 1],
+ ["paras[0]", "testDiv", 0, "testDiv", 2],
+ ["paras[0]", "testDiv", 1, "testDiv", 2],
+ ["paras[0]", "testDiv", 2, "testDiv", 2],
+
+ ["foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", "foreignDoc.childNodes.length"],
+];
diff --git a/testing/web-platform/tests/dom/ranges/Range-selectNode.html b/testing/web-platform/tests/dom/ranges/Range-selectNode.html
new file mode 100644
index 0000000000..fe9b1f7860
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-selectNode.html
@@ -0,0 +1,99 @@
+<!doctype html>
+<title>Range.selectNode() and .selectNodeContents() tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+function testSelectNode(range, node) {
+ try {
+ range.collapsed;
+ } catch (e) {
+ // Range is detached
+ assert_throws_dom("INVALID_STATE_ERR", function () {
+ range.selectNode(node);
+ }, "selectNode() on a detached node must throw INVALID_STATE_ERR");
+ assert_throws_dom("INVALID_STATE_ERR", function () {
+ range.selectNodeContents(node);
+ }, "selectNodeContents() on a detached node must throw INVALID_STATE_ERR");
+ return;
+ }
+
+ if (!node.parentNode) {
+ assert_throws_dom("INVALID_NODE_TYPE_ERR", function() {
+ range.selectNode(node);
+ }, "selectNode() on a node with no parent must throw INVALID_NODE_TYPE_ERR");
+ } else {
+ var index = 0;
+ while (node.parentNode.childNodes[index] != node) {
+ index++;
+ }
+
+ range.selectNode(node);
+ assert_equals(range.startContainer, node.parentNode,
+ "After selectNode(), startContainer must equal parent node");
+ assert_equals(range.endContainer, node.parentNode,
+ "After selectNode(), endContainer must equal parent node");
+ assert_equals(range.startOffset, index,
+ "After selectNode(), startOffset must be index of node in parent (" + index + ")");
+ assert_equals(range.endOffset, index + 1,
+ "After selectNode(), endOffset must be one plus index of node in parent (" + (index + 1) + ")");
+ }
+
+ if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
+ assert_throws_dom("INVALID_NODE_TYPE_ERR", function () {
+ range.selectNodeContents(node);
+ }, "selectNodeContents() on a doctype must throw INVALID_NODE_TYPE_ERR");
+ } else {
+ range.selectNodeContents(node);
+ assert_equals(range.startContainer, node,
+ "After selectNodeContents(), startContainer must equal node");
+ assert_equals(range.endContainer, node,
+ "After selectNodeContents(), endContainer must equal node");
+ assert_equals(range.startOffset, 0,
+ "After selectNodeContents(), startOffset must equal 0");
+ var len = nodeLength(node);
+ assert_equals(range.endOffset, len,
+ "After selectNodeContents(), endOffset must equal node length (" + len + ")");
+ }
+}
+
+var range = document.createRange();
+var foreignRange = foreignDoc.createRange();
+var xmlRange = xmlDoc.createRange();
+var detachedRange = document.createRange();
+detachedRange.detach();
+var tests = [];
+function testTree(root, marker) {
+ if (root.nodeType == Node.ELEMENT_NODE && root.id == "log") {
+ // This is being modified during the tests, so let's not test it.
+ return;
+ }
+ tests.push([marker + ": " + root.nodeName.toLowerCase() + " node, current doc's range, type " + root.nodeType, range, root]);
+ tests.push([marker + ": " + root.nodeName.toLowerCase() + " node, foreign doc's range, type " + root.nodeType, foreignRange, root]);
+ tests.push([marker + ": " + root.nodeName.toLowerCase() + " node, XML doc's range, type " + root.nodeType, xmlRange, root]);
+ tests.push([marker + ": " + root.nodeName.toLowerCase() + " node, detached range, type " + root.nodeType, detachedRange, root]);
+ for (var i = 0; i < root.childNodes.length; i++) {
+ testTree(root.childNodes[i], marker + "[" + i + "]");
+ }
+}
+testTree(document, "current doc");
+testTree(foreignDoc, "foreign doc");
+testTree(detachedDiv, "detached div in current doc");
+
+var otherTests = ["xmlDoc", "xmlElement", "detachedTextNode",
+"foreignTextNode", "xmlTextNode", "processingInstruction", "comment",
+"foreignComment", "xmlComment", "docfrag", "foreignDocfrag", "xmlDocfrag"];
+
+for (var i = 0; i < otherTests.length; i++) {
+ testTree(window[otherTests[i]], otherTests[i]);
+}
+
+generate_tests(testSelectNode, tests);
+
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-set.html b/testing/web-platform/tests/dom/ranges/Range-set.html
new file mode 100644
index 0000000000..694fc60749
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-set.html
@@ -0,0 +1,221 @@
+<!doctype html>
+<title>Range setting tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+function testSetStart(range, node, offset) {
+ if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
+ assert_throws_dom("INVALID_NODE_TYPE_ERR", function() {
+ range.setStart(node, offset);
+ }, "setStart() to a doctype must throw INVALID_NODE_TYPE_ERR");
+ return;
+ }
+
+ if (offset < 0 || offset > nodeLength(node)) {
+ assert_throws_dom("INDEX_SIZE_ERR", function() {
+ range.setStart(node, offset);
+ }, "setStart() to a too-large offset must throw INDEX_SIZE_ERR");
+ return;
+ }
+
+ var newRange = range.cloneRange();
+ newRange.setStart(node, offset);
+
+ assert_equals(newRange.startContainer, node,
+ "setStart() must change startContainer to the new node");
+ assert_equals(newRange.startOffset, offset,
+ "setStart() must change startOffset to the new offset");
+
+ // FIXME: I'm assuming comparePoint() is correct, but the tests for that
+ // will depend on setStart()/setEnd().
+ if (furthestAncestor(node) != furthestAncestor(range.startContainer)
+ || range.comparePoint(node, offset) > 0) {
+ assert_equals(newRange.endContainer, node,
+ "setStart(node, offset) where node is after current end or in different document must set the end node to node too");
+ assert_equals(newRange.endOffset, offset,
+ "setStart(node, offset) where node is after current end or in different document must set the end offset to offset too");
+ } else {
+ assert_equals(newRange.endContainer, range.endContainer,
+ "setStart() must not change the end node if the new start is before the old end");
+ assert_equals(newRange.endOffset, range.endOffset,
+ "setStart() must not change the end offset if the new start is before the old end");
+ }
+}
+
+function testSetEnd(range, node, offset) {
+ if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
+ assert_throws_dom("INVALID_NODE_TYPE_ERR", function() {
+ range.setEnd(node, offset);
+ }, "setEnd() to a doctype must throw INVALID_NODE_TYPE_ERR");
+ return;
+ }
+
+ if (offset < 0 || offset > nodeLength(node)) {
+ assert_throws_dom("INDEX_SIZE_ERR", function() {
+ range.setEnd(node, offset);
+ }, "setEnd() to a too-large offset must throw INDEX_SIZE_ERR");
+ return;
+ }
+
+ var newRange = range.cloneRange();
+ newRange.setEnd(node, offset);
+
+ // FIXME: I'm assuming comparePoint() is correct, but the tests for that
+ // will depend on setStart()/setEnd().
+ if (furthestAncestor(node) != furthestAncestor(range.startContainer)
+ || range.comparePoint(node, offset) < 0) {
+ assert_equals(newRange.startContainer, node,
+ "setEnd(node, offset) where node is before current start or in different document must set the end node to node too");
+ assert_equals(newRange.startOffset, offset,
+ "setEnd(node, offset) where node is before current start or in different document must set the end offset to offset too");
+ } else {
+ assert_equals(newRange.startContainer, range.startContainer,
+ "setEnd() must not change the start node if the new end is after the old start");
+ assert_equals(newRange.startOffset, range.startOffset,
+ "setEnd() must not change the start offset if the new end is after the old start");
+ }
+
+ assert_equals(newRange.endContainer, node,
+ "setEnd() must change endContainer to the new node");
+ assert_equals(newRange.endOffset, offset,
+ "setEnd() must change endOffset to the new offset");
+}
+
+function testSetStartBefore(range, node) {
+ var parent = node.parentNode;
+ if (parent === null) {
+ assert_throws_dom("INVALID_NODE_TYPE_ERR", function () {
+ range.setStartBefore(node);
+ }, "setStartBefore() to a node with null parent must throw INVALID_NODE_TYPE_ERR");
+ return;
+ }
+
+ var idx = 0;
+ while (node.parentNode.childNodes[idx] != node) {
+ idx++;
+ }
+
+ testSetStart(range, node.parentNode, idx);
+}
+
+function testSetStartAfter(range, node) {
+ var parent = node.parentNode;
+ if (parent === null) {
+ assert_throws_dom("INVALID_NODE_TYPE_ERR", function () {
+ range.setStartAfter(node);
+ }, "setStartAfter() to a node with null parent must throw INVALID_NODE_TYPE_ERR");
+ return;
+ }
+
+ var idx = 0;
+ while (node.parentNode.childNodes[idx] != node) {
+ idx++;
+ }
+
+ testSetStart(range, node.parentNode, idx + 1);
+}
+
+function testSetEndBefore(range, node) {
+ var parent = node.parentNode;
+ if (parent === null) {
+ assert_throws_dom("INVALID_NODE_TYPE_ERR", function () {
+ range.setEndBefore(node);
+ }, "setEndBefore() to a node with null parent must throw INVALID_NODE_TYPE_ERR");
+ return;
+ }
+
+ var idx = 0;
+ while (node.parentNode.childNodes[idx] != node) {
+ idx++;
+ }
+
+ testSetEnd(range, node.parentNode, idx);
+}
+
+function testSetEndAfter(range, node) {
+ var parent = node.parentNode;
+ if (parent === null) {
+ assert_throws_dom("INVALID_NODE_TYPE_ERR", function () {
+ range.setEndAfter(node);
+ }, "setEndAfter() to a node with null parent must throw INVALID_NODE_TYPE_ERR");
+ return;
+ }
+
+ var idx = 0;
+ while (node.parentNode.childNodes[idx] != node) {
+ idx++;
+ }
+
+ testSetEnd(range, node.parentNode, idx + 1);
+}
+
+
+var startTests = [];
+var endTests = [];
+var startBeforeTests = [];
+var startAfterTests = [];
+var endBeforeTests = [];
+var endAfterTests = [];
+
+// Don't want to eval() each point a bazillion times
+var testPointsCached = testPoints.map(eval);
+var testNodesCached = testNodesShort.map(eval);
+
+for (var i = 0; i < testRangesShort.length; i++) {
+ var endpoints = eval(testRangesShort[i]);
+ var range;
+ test(function() {
+ range = ownerDocument(endpoints[0]).createRange();
+ range.setStart(endpoints[0], endpoints[1]);
+ range.setEnd(endpoints[2], endpoints[3]);
+ }, "Set up range " + i + " " + testRangesShort[i]);
+
+ for (var j = 0; j < testPoints.length; j++) {
+ startTests.push(["setStart() with range " + i + " " + testRangesShort[i] + ", point " + j + " " + testPoints[j],
+ range,
+ testPointsCached[j][0],
+ testPointsCached[j][1]
+ ]);
+ endTests.push(["setEnd() with range " + i + " " + testRangesShort[i] + ", point " + j + " " + testPoints[j],
+ range,
+ testPointsCached[j][0],
+ testPointsCached[j][1]
+ ]);
+ }
+
+ for (var j = 0; j < testNodesShort.length; j++) {
+ startBeforeTests.push(["setStartBefore() with range " + i + " " + testRangesShort[i] + ", node " + j + " " + testNodesShort[j],
+ range,
+ testNodesCached[j]
+ ]);
+ startAfterTests.push(["setStartAfter() with range " + i + " " + testRangesShort[i] + ", node " + j + " " + testNodesShort[j],
+ range,
+ testNodesCached[j]
+ ]);
+ endBeforeTests.push(["setEndBefore() with range " + i + " " + testRangesShort[i] + ", node " + j + " " + testNodesShort[j],
+ range,
+ testNodesCached[j]
+ ]);
+ endAfterTests.push(["setEndAfter() with range " + i + " " + testRangesShort[i] + ", node " + j + " " + testNodesShort[j],
+ range,
+ testNodesCached[j]
+ ]);
+ }
+}
+
+generate_tests(testSetStart, startTests);
+generate_tests(testSetEnd, endTests);
+generate_tests(testSetStartBefore, startBeforeTests);
+generate_tests(testSetStartAfter, startAfterTests);
+generate_tests(testSetEndBefore, endBeforeTests);
+generate_tests(testSetEndAfter, endAfterTests);
+
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-stringifier.html b/testing/web-platform/tests/dom/ranges/Range-stringifier.html
new file mode 100644
index 0000000000..330c7421ea
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-stringifier.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Range stringifier</title>
+<link rel="author" title="KiChjang" href="mailto:kungfukeith11@gmail.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=test>Test div</div>
+<div id=another>Another div</div>
+<div id=last>Last div</div>
+<div id=log></div>
+<script>
+test(function() {
+ var r = new Range();
+ var testDiv = document.getElementById("test");
+ test(function() {
+ r.selectNodeContents(testDiv);
+ assert_equals(r.collapsed, false);
+ assert_equals(r.toString(), testDiv.textContent);
+ }, "Node contents of a single div");
+
+ var textNode = testDiv.childNodes[0];
+ test(function() {
+ r.setStart(textNode, 5);
+ r.setEnd(textNode, 7);
+ assert_equals(r.collapsed, false);
+ assert_equals(r.toString(), "di");
+ }, "Text node with offsets");
+
+ var anotherDiv = document.getElementById("another");
+ test(function() {
+ r.setStart(testDiv, 0);
+ r.setEnd(anotherDiv, 0);
+ assert_equals(r.toString(), "Test div\n");
+ }, "Two nodes, each with a text node");
+
+ var lastDiv = document.getElementById("last");
+ var lastText = lastDiv.childNodes[0];
+ test(function() {
+ r.setStart(textNode, 5);
+ r.setEnd(lastText, 4);
+ assert_equals(r.toString(), "div\nAnother div\nLast");
+ }, "Three nodes with start offset and end offset on text nodes");
+});
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-surroundContents.html b/testing/web-platform/tests/dom/ranges/Range-surroundContents.html
new file mode 100644
index 0000000000..c9d47fcbad
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-surroundContents.html
@@ -0,0 +1,324 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Range.surroundContents() tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<p>To debug test failures, add a query parameter "subtest" with the test id (like
+"?subtest=5,16"). Only that test will be run. Then you can look at the resulting
+iframes in the DOM.
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+testDiv.parentNode.removeChild(testDiv);
+
+function mySurroundContents(range, newParent) {
+ try {
+ // "If a non-Text node is partially contained in the context object,
+ // throw a "InvalidStateError" exception and terminate these steps."
+ var node = range.commonAncestorContainer;
+ var stop = nextNodeDescendants(node);
+ for (; node != stop; node = nextNode(node)) {
+ if (node.nodeType != Node.TEXT_NODE
+ && isPartiallyContained(node, range)) {
+ return "INVALID_STATE_ERR";
+ }
+ }
+
+ // "If newParent is a Document, DocumentType, or DocumentFragment node,
+ // throw an "InvalidNodeTypeError" exception and terminate these
+ // steps."
+ if (newParent.nodeType == Node.DOCUMENT_NODE
+ || newParent.nodeType == Node.DOCUMENT_TYPE_NODE
+ || newParent.nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
+ return "INVALID_NODE_TYPE_ERR";
+ }
+
+ // "Call extractContents() on the context object, and let fragment be
+ // the result."
+ var fragment = myExtractContents(range);
+ if (typeof fragment == "string") {
+ return fragment;
+ }
+
+ // "While newParent has children, remove its first child."
+ while (newParent.childNodes.length) {
+ newParent.removeChild(newParent.firstChild);
+ }
+
+ // "Call insertNode(newParent) on the context object."
+ var ret = myInsertNode(range, newParent);
+ if (typeof ret == "string") {
+ return ret;
+ }
+
+ // "Call appendChild(fragment) on newParent."
+ newParent.appendChild(fragment);
+
+ // "Call selectNode(newParent) on the context object."
+ //
+ // We just reimplement this in-place.
+ if (!newParent.parentNode) {
+ return "INVALID_NODE_TYPE_ERR";
+ }
+ var index = indexOf(newParent);
+ range.setStart(newParent.parentNode, index);
+ range.setEnd(newParent.parentNode, index + 1);
+ } catch (e) {
+ return getDomExceptionName(e);
+ }
+}
+
+function restoreIframe(iframe, i, j) {
+ // Most of this function is designed to work around the fact that Opera
+ // doesn't let you add a doctype to a document that no longer has one, in
+ // any way I can figure out. I eventually compromised on something that
+ // will still let Opera pass most tests that don't actually involve
+ // doctypes.
+ while (iframe.contentDocument.firstChild
+ && iframe.contentDocument.firstChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
+ iframe.contentDocument.removeChild(iframe.contentDocument.firstChild);
+ }
+
+ while (iframe.contentDocument.lastChild
+ && iframe.contentDocument.lastChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
+ iframe.contentDocument.removeChild(iframe.contentDocument.lastChild);
+ }
+
+ if (!iframe.contentDocument.firstChild) {
+ // This will throw an exception in Opera if we reach here, which is why
+ // I try to avoid it. It will never happen in a browser that obeys the
+ // spec, so it's really just insurance. I don't think it actually gets
+ // hit by anything.
+ iframe.contentDocument.appendChild(iframe.contentDocument.implementation.createDocumentType("html", "", ""));
+ }
+ iframe.contentDocument.appendChild(referenceDoc.documentElement.cloneNode(true));
+ iframe.contentWindow.setupRangeTests();
+ iframe.contentWindow.testRangeInput = testRangesShort[i];
+ iframe.contentWindow.testNodeInput = testNodesShort[j];
+ iframe.contentWindow.run();
+}
+
+function testSurroundContents(i, j) {
+ var actualRange;
+ var expectedRange;
+ var actualNode;
+ var expectedNode;
+ var actualRoots = [];
+ var expectedRoots = [];
+
+ domTests[i][j].step(function() {
+ restoreIframe(actualIframe, i, j);
+ restoreIframe(expectedIframe, i, j);
+
+ actualRange = actualIframe.contentWindow.testRange;
+ expectedRange = expectedIframe.contentWindow.testRange;
+ actualNode = actualIframe.contentWindow.testNode;
+ expectedNode = expectedIframe.contentWindow.testNode;
+
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual surroundContents()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated surroundContents()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_not_equals(actualRange, null,
+ "Range produced in actual iframe was null");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+ assert_not_equals(expectedRange, null,
+ "Range produced in expected iframe was null");
+ assert_equals(typeof actualNode, "object",
+ "typeof Node produced in actual iframe");
+ assert_not_equals(actualNode, null,
+ "Node produced in actual iframe was null");
+ assert_equals(typeof expectedNode, "object",
+ "typeof Node produced in expected iframe");
+ assert_not_equals(expectedNode, null,
+ "Node produced in expected iframe was null");
+
+ // We want to test that the trees containing the ranges are equal, and
+ // also the trees containing the moved nodes. These might not be the
+ // same, if we're inserting a node from a detached tree or a different
+ // document.
+ actualRoots.push(furthestAncestor(actualRange.startContainer));
+ expectedRoots.push(furthestAncestor(expectedRange.startContainer));
+
+ if (furthestAncestor(actualNode) != actualRoots[0]) {
+ actualRoots.push(furthestAncestor(actualNode));
+ }
+ if (furthestAncestor(expectedNode) != expectedRoots[0]) {
+ expectedRoots.push(furthestAncestor(expectedNode));
+ }
+
+ assert_equals(actualRoots.length, expectedRoots.length,
+ "Either the actual node and actual range are in the same tree but the expected are in different trees, or vice versa");
+
+ // This doctype stuff is to work around the fact that Opera 11.00 will
+ // move around doctypes within a document, even to totally invalid
+ // positions, but it won't allow a new doctype to be added to a
+ // document in any way I can figure out. So if we try moving a doctype
+ // to some invalid place, in Opera it will actually succeed, and then
+ // restoreIframe() will remove the doctype along with the root element,
+ // and then nothing can re-add the doctype. So instead, we catch it
+ // during the test itself and move it back to the right place while we
+ // still can.
+ //
+ // I spent *way* too much time debugging and working around this bug.
+ var actualDoctype = actualIframe.contentDocument.doctype;
+ var expectedDoctype = expectedIframe.contentDocument.doctype;
+
+ var result;
+ try {
+ result = mySurroundContents(expectedRange, expectedNode);
+ } catch (e) {
+ if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
+ expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
+ }
+ throw e;
+ }
+ if (typeof result == "string") {
+ assert_throws_dom(result, actualIframe.contentWindow.DOMException, function() {
+ try {
+ actualRange.surroundContents(actualNode);
+ } catch (e) {
+ if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
+ expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
+ }
+ if (actualDoctype != actualIframe.contentDocument.firstChild) {
+ actualIframe.contentDocument.insertBefore(actualDoctype, actualIframe.contentDocument.firstChild);
+ }
+ throw e;
+ }
+ }, "A " + result + " must be thrown in this case");
+ // Don't return, we still need to test DOM equality
+ } else {
+ try {
+ actualRange.surroundContents(actualNode);
+ } catch (e) {
+ if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
+ expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
+ }
+ if (actualDoctype != actualIframe.contentDocument.firstChild) {
+ actualIframe.contentDocument.insertBefore(actualDoctype, actualIframe.contentDocument.firstChild);
+ }
+ throw e;
+ }
+ }
+
+ for (var k = 0; k < actualRoots.length; k++) {
+ assertNodesEqual(actualRoots[k], expectedRoots[k], k ? "moved node's tree root" : "range's tree root");
+ }
+ });
+ domTests[i][j].done();
+
+ positionTests[i][j].step(function() {
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual surroundContents()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated surroundContents()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_not_equals(actualRange, null,
+ "Range produced in actual iframe was null");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+ assert_not_equals(expectedRange, null,
+ "Range produced in expected iframe was null");
+ assert_equals(typeof actualNode, "object",
+ "typeof Node produced in actual iframe");
+ assert_not_equals(actualNode, null,
+ "Node produced in actual iframe was null");
+ assert_equals(typeof expectedNode, "object",
+ "typeof Node produced in expected iframe");
+ assert_not_equals(expectedNode, null,
+ "Node produced in expected iframe was null");
+
+ for (var k = 0; k < actualRoots.length; k++) {
+ assertNodesEqual(actualRoots[k], expectedRoots[k], k ? "moved node's tree root" : "range's tree root");
+ }
+
+ assert_equals(actualRange.startOffset, expectedRange.startOffset,
+ "Unexpected startOffset after surroundContents()");
+ assert_equals(actualRange.endOffset, expectedRange.endOffset,
+ "Unexpected endOffset after surroundContents()");
+ // How do we decide that the two nodes are equal, since they're in
+ // different trees? Since the DOMs are the same, it's enough to check
+ // that the index in the parent is the same all the way up the tree.
+ // But we can first cheat by just checking they're actually equal.
+ assert_true(actualRange.startContainer.isEqualNode(expectedRange.startContainer),
+ "Unexpected startContainer after surroundContents(), expected " +
+ expectedRange.startContainer.nodeName.toLowerCase() + " but got " +
+ actualRange.startContainer.nodeName.toLowerCase());
+ var currentActual = actualRange.startContainer;
+ var currentExpected = expectedRange.startContainer;
+ var actual = "";
+ var expected = "";
+ while (currentActual && currentExpected) {
+ actual = indexOf(currentActual) + "-" + actual;
+ expected = indexOf(currentExpected) + "-" + expected;
+
+ currentActual = currentActual.parentNode;
+ currentExpected = currentExpected.parentNode;
+ }
+ actual = actual.substr(0, actual.length - 1);
+ expected = expected.substr(0, expected.length - 1);
+ assert_equals(actual, expected,
+ "startContainer superficially looks right but is actually the wrong node if you trace back its index in all its ancestors (I'm surprised this actually happened");
+ });
+ positionTests[i][j].done();
+}
+
+var iStart = 0;
+var iStop = testRangesShort.length;
+var jStart = 0;
+var jStop = testNodesShort.length;
+
+if (/subtest=[0-9]+,[0-9]+/.test(location.search)) {
+ var matches = /subtest=([0-9]+),([0-9]+)/.exec(location.search);
+ iStart = Number(matches[1]);
+ iStop = Number(matches[1]) + 1;
+ jStart = Number(matches[2]) + 0;
+ jStop = Number(matches[2]) + 1;
+}
+
+var domTests = [];
+var positionTests = [];
+for (var i = iStart; i < iStop; i++) {
+ domTests[i] = [];
+ positionTests[i] = [];
+ for (var j = jStart; j < jStop; j++) {
+ domTests[i][j] = async_test(i + "," + j + ": resulting DOM for range " + testRangesShort[i] + ", node " + testNodesShort[j]);
+ positionTests[i][j] = async_test(i + "," + j + ": resulting range position for range " + testRangesShort[i] + ", node " + testNodesShort[j]);
+ }
+}
+
+var actualIframe = document.createElement("iframe");
+actualIframe.style.display = "none";
+actualIframe.id = "actual";
+document.body.appendChild(actualIframe);
+
+var expectedIframe = document.createElement("iframe");
+expectedIframe.style.display = "none";
+expectedIframe.id = "expected";
+document.body.appendChild(expectedIframe);
+
+var referenceDoc = document.implementation.createHTMLDocument("");
+referenceDoc.removeChild(referenceDoc.documentElement);
+
+actualIframe.onload = function() {
+ expectedIframe.onload = function() {
+ for (var i = iStart; i < iStop; i++) {
+ for (var j = jStart; j < jStop; j++) {
+ testSurroundContents(i, j);
+ }
+ }
+ }
+ expectedIframe.src = "Range-test-iframe.html";
+ referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true));
+}
+actualIframe.src = "Range-test-iframe.html";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/Range-test-iframe.html b/testing/web-platform/tests/dom/ranges/Range-test-iframe.html
new file mode 100644
index 0000000000..f354ff758f
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-test-iframe.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<title>Range test iframe</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<body onload=run()>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+// This script only exists because we want to evaluate the range endpoints
+// in each iframe using that iframe's local variables set up by common.js. It
+// just creates the range and does nothing else. The data is returned via
+// window.testRange, and if an exception is thrown, it's put in
+// window.unexpectedException.
+window.unexpectedException = null;
+
+function run() {
+ try {
+ window.unexpectedException = null;
+
+ if (typeof window.testNodeInput != "undefined") {
+ window.testNode = eval(window.testNodeInput);
+ }
+
+ var rangeEndpoints;
+ if (typeof window.testRangeInput == "undefined") {
+ // Use the hash (old way of doing things, bad because it requires
+ // navigation)
+ if (location.hash == "") {
+ return;
+ }
+ rangeEndpoints = eval(location.hash.substr(1));
+ } else {
+ // Get the variable directly off the window, faster and can be done
+ // synchronously
+ rangeEndpoints = eval(window.testRangeInput);
+ }
+
+ var range;
+ if (rangeEndpoints == "detached") {
+ range = document.createRange();
+ range.detach();
+ } else {
+ range = ownerDocument(rangeEndpoints[0]).createRange();
+ range.setStart(rangeEndpoints[0], rangeEndpoints[1]);
+ range.setEnd(rangeEndpoints[2], rangeEndpoints[3]);
+ }
+
+ window.testRange = range;
+ } catch(e) {
+ window.unexpectedException = e;
+ }
+}
+
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/ranges/StaticRange-constructor.html b/testing/web-platform/tests/dom/ranges/StaticRange-constructor.html
new file mode 100644
index 0000000000..6aae93f49b
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/StaticRange-constructor.html
@@ -0,0 +1,200 @@
+<!doctype html>
+<title>StaticRange constructor test</title>
+<link rel='author' title='Sanket Joshi' href='mailto:sajos@microsoft.com'>
+<div id='log'></div>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<div id='testDiv'>abc<span>def</span>ghi</div>
+<script>
+'use strict';
+
+const testDiv = document.getElementById('testDiv');
+const testTextNode = testDiv.firstChild;
+const testPINode = document.createProcessingInstruction('foo', 'abc');
+const testCommentNode = document.createComment('abc');
+document.body.append(testPINode, testCommentNode);
+
+test(function() {
+ const staticRange = new StaticRange({startContainer: testDiv, startOffset: 1, endContainer: testDiv, endOffset: 2});
+ assert_equals(staticRange.startContainer, testDiv, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 1, 'valid startOffset');
+ assert_equals(staticRange.endContainer, testDiv, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 2, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct static range with Element container');
+
+test(function() {
+ const staticRange = new StaticRange({startContainer: testTextNode, startOffset: 1, endContainer: testTextNode, endOffset: 2});
+ assert_equals(staticRange.startContainer, testTextNode, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 1, 'valid startOffset');
+ assert_equals(staticRange.endContainer, testTextNode, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 2, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct static range with Text container');
+
+test(function() {
+ const staticRange = new StaticRange({startContainer: testDiv, startOffset: 0, endContainer: testTextNode, endOffset: 1});
+ assert_equals(staticRange.startContainer, testDiv, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 0, 'valid startOffset');
+ assert_equals(staticRange.endContainer, testTextNode, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 1, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct static range with Element startContainer and Text endContainer');
+
+test(function() {
+ const staticRange = new StaticRange({startContainer: testTextNode, startOffset: 0, endContainer: testDiv, endOffset: 3});
+ assert_equals(staticRange.startContainer, testTextNode, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 0, 'valid startOffset');
+ assert_equals(staticRange.endContainer, testDiv, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 3, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct static range with Text startContainer and Element endContainer');
+
+test(function() {
+ const staticRange = new StaticRange({startContainer: testPINode, startOffset: 1, endContainer: testPINode, endOffset: 2});
+ assert_equals(staticRange.startContainer, testPINode, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 1, 'valid startOffset');
+ assert_equals(staticRange.endContainer, testPINode, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 2, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct static range with ProcessingInstruction container');
+
+test(function() {
+ const staticRange = new StaticRange({startContainer: testCommentNode, startOffset: 1, endContainer: testCommentNode, endOffset: 2});
+ assert_equals(staticRange.startContainer, testCommentNode, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 1, 'valid startOffset');
+ assert_equals(staticRange.endContainer, testCommentNode, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 2, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct static range with Comment container');
+
+test(function() {
+ const xmlDoc = new DOMParser().parseFromString('<xml></xml>', 'application/xml');
+ const testCDATASection = xmlDoc.createCDATASection('abc');
+ const staticRange = new StaticRange({startContainer: testCDATASection, startOffset: 1, endContainer: testCDATASection, endOffset: 2});
+ assert_equals(staticRange.startContainer, testCDATASection, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 1, 'valid startOffset');
+ assert_equals(staticRange.endContainer, testCDATASection, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 2, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct static range with CDATASection container');
+
+test(function() {
+ const staticRange = new StaticRange({startContainer: document, startOffset: 0, endContainer: document, endOffset: 1});
+ assert_equals(staticRange.startContainer, document, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 0, 'valid startOffset');
+ assert_equals(staticRange.endContainer, document, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 1, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct static range with Document container');
+
+test(function() {
+ const testDocFrag = document.createDocumentFragment();
+ testDocFrag.append('a','b','c');
+ const staticRange = new StaticRange({startContainer: testDocFrag, startOffset: 0, endContainer: testDocFrag, endOffset: 1});
+ assert_equals(staticRange.startContainer, testDocFrag, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 0, 'valid startOffset');
+ assert_equals(staticRange.endContainer, testDocFrag, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 1, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct static range with DocumentFragment container');
+
+test(function() {
+ const staticRange = new StaticRange({startContainer: testDiv, startOffset: 0, endContainer: testDiv, endOffset: 0});
+ assert_equals(staticRange.startContainer, testDiv, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 0, 'valid startOffset');
+ assert_equals(staticRange.endContainer, testDiv, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 0, 'valid endOffset');
+ assert_true(staticRange.collapsed, 'collapsed');
+}, 'Construct collapsed static range');
+
+test(function() {
+ const staticRange = new StaticRange({startContainer: testDiv, startOffset: 1, endContainer: document.body, endOffset: 0});
+ assert_equals(staticRange.startContainer, testDiv, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 1, 'valid startOffset');
+ assert_equals(staticRange.endContainer, document.body, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 0, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct inverted static range');
+
+test(function() {
+ const staticRange = new StaticRange({startContainer: testDiv, startOffset: 0, endContainer: testDiv, endOffset: 15});
+ assert_equals(staticRange.startContainer, testDiv, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 0, 'valid startOffset');
+ assert_equals(staticRange.endContainer, testDiv, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 15, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct static range with offset greater than length');
+
+test(function() {
+ const testNode = document.createTextNode('abc');
+ const staticRange = new StaticRange({startContainer: testNode, startOffset: 1, endContainer: testNode, endOffset: 2});
+ assert_equals(staticRange.startContainer, testNode, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 1, 'valid startOffset');
+ assert_equals(staticRange.endContainer, testNode, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 2, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct static range with standalone Node container');
+
+test(function() {
+ const testRoot = document.createElement('div');
+ testRoot.append('a','b');
+ const staticRange = new StaticRange({startContainer: testDiv, startOffset: 1, endContainer: testRoot, endOffset: 2});
+ assert_equals(staticRange.startContainer, testDiv, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 1, 'valid startOffset');
+ assert_equals(staticRange.endContainer, testRoot, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 2, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct static range with endpoints in disconnected trees');
+
+test(function() {
+ const testDocNode = document.implementation.createDocument('about:blank', 'html', null);
+ const staticRange = new StaticRange({startContainer: document, startOffset: 0, endContainer: testDocNode.documentElement, endOffset: 0});
+ assert_equals(staticRange.startContainer, document, 'valid startContainer');
+ assert_equals(staticRange.startOffset, 0, 'valid startOffset');
+ assert_equals(staticRange.endContainer, testDocNode.documentElement, 'valid endContainer');
+ assert_equals(staticRange.endOffset, 0, 'valid endOffset');
+ assert_false(staticRange.collapsed, 'not collapsed');
+}, 'Construct static range with endpoints in disconnected documents');
+
+test(function() {
+ assert_throws_dom('INVALID_NODE_TYPE_ERR', function() {
+ const staticRange = new StaticRange({startContainer: document.doctype, startOffset: 0, endContainer: document.doctype, endOffset: 0});
+ }, 'throw a InvalidNodeTypeError when a DocumentType is passed as a startContainer or endContainer');
+
+ assert_throws_dom('INVALID_NODE_TYPE_ERR', function() {
+ const testAttrNode = testDiv.getAttributeNode('id');
+ const staticRange = new StaticRange({startContainer: testAttrNode, startOffset: 0, endContainer: testAttrNode, endOffset: 0});
+ }, 'throw a InvalidNodeTypeError when a Attr is passed as a startContainer or endContainer');
+}, 'Throw on DocumentType or Attr container');
+
+test(function () {
+ assert_throws_js(TypeError, function () {
+ const staticRange = new StaticRange();
+ }, 'throw a TypeError when no argument is passed');
+
+ assert_throws_js(TypeError, function () {
+ const staticRange = new StaticRange({startOffset: 0, endContainer: testDiv, endOffset: 0});
+ }, 'throw a TypeError when a startContainer is not passed');
+
+ assert_throws_js(TypeError, function () {
+ const staticRange = new StaticRange({startContainer: testDiv, endContainer: testDiv, endOffset: 0});
+ }, 'throw a TypeError when a startOffset is not passed');
+
+ assert_throws_js(TypeError, function () {
+ const staticRange = new StaticRange({startContainer: testDiv, startOffset: 0, endOffset: 0});
+ }, 'throw a TypeError when an endContainer is not passed');
+
+ assert_throws_js(TypeError, function () {
+ const staticRange = new StaticRange({startContainer: testDiv, startOffset: 0, endContainer: testDiv});
+ }, 'throw a TypeError when an endOffset is not passed');
+
+ assert_throws_js(TypeError, function () {
+ const staticRange = new StaticRange({startContainer: null, startOffset: 0, endContainer: testDiv, endOffset: 0});
+ }, 'throw a TypeError when a null startContainer is passed');
+
+ assert_throws_js(TypeError, function () {
+ const staticRange = new StaticRange({startContainer: testDiv, startOffset: 0, endContainer: null, endOffset: 0});
+ }, 'throw a TypeError when a null endContainer is passed');
+}, 'Throw on missing or invalid arguments');
+</script>
diff --git a/testing/web-platform/tests/dom/slot-recalc-ref.html b/testing/web-platform/tests/dom/slot-recalc-ref.html
new file mode 100644
index 0000000000..521eedb42a
--- /dev/null
+++ b/testing/web-platform/tests/dom/slot-recalc-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<div>
+ <p>there should be more text below this</p>
+ <p>PASS if this text is visible</p>
+</div>
diff --git a/testing/web-platform/tests/dom/slot-recalc.html b/testing/web-platform/tests/dom/slot-recalc.html
new file mode 100644
index 0000000000..5124336ad2
--- /dev/null
+++ b/testing/web-platform/tests/dom/slot-recalc.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1184357">
+
+<link rel="match" href="/dom/slot-recalc-ref.html">
+
+<body>
+<script>
+const host = document.createElement('div');
+document.body.appendChild(host);
+const root = host.attachShadow({mode: 'open'});
+
+const slot = document.createElement('slot');
+slot.innerHTML = `<p>there should be more text below this</p>`;
+root.appendChild(slot);
+
+onload = () => {
+ const shouldBeVisible = document.createElement('p');
+ shouldBeVisible.textContent = 'PASS if this text is visible';
+ slot.appendChild(shouldBeVisible);
+};
+</script>
diff --git a/testing/web-platform/tests/dom/svg-insert-crash.html b/testing/web-platform/tests/dom/svg-insert-crash.html
new file mode 100644
index 0000000000..80eec1fba7
--- /dev/null
+++ b/testing/web-platform/tests/dom/svg-insert-crash.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://crbug.com/1029262">
+<meta name="assert" content="The renderer should not crash.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<svg>
+ <!-- Note that the SVG in the data URL below is intentionally malformed: -->
+ <feImage xlink:href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect"/>
+</svg>
+
+<script>
+async_test(t => {
+ window.onload = t.step_func_done();
+}, 'The renderer should not crash.');
+</script>
diff --git a/testing/web-platform/tests/dom/traversal/NodeFilter-constants.html b/testing/web-platform/tests/dom/traversal/NodeFilter-constants.html
new file mode 100644
index 0000000000..1ce4736cc6
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/NodeFilter-constants.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<title>NodeFilter constants</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../constants.js"></script>
+<div id="log"></div>
+<script>
+var objects;
+setup(function() {
+ objects = [
+ [NodeFilter, "NodeFilter interface object"],
+ ]
+})
+testConstants(objects, [
+ ["FILTER_ACCEPT", 1],
+ ["FILTER_REJECT", 2],
+ ["FILTER_SKIP", 3]
+], "acceptNode")
+testConstants(objects, [
+ ["SHOW_ALL", 0xFFFFFFFF],
+ ["SHOW_ELEMENT", 0x1],
+ ["SHOW_ATTRIBUTE", 0x2],
+ ["SHOW_TEXT", 0x4],
+ ["SHOW_CDATA_SECTION", 0x8],
+ ["SHOW_ENTITY_REFERENCE", 0x10],
+ ["SHOW_ENTITY", 0x20],
+ ["SHOW_PROCESSING_INSTRUCTION", 0x40],
+ ["SHOW_COMMENT", 0x80],
+ ["SHOW_DOCUMENT", 0x100],
+ ["SHOW_DOCUMENT_TYPE", 0x200],
+ ["SHOW_DOCUMENT_FRAGMENT", 0x400],
+ ["SHOW_NOTATION", 0x800]
+], "whatToShow")
+</script>
diff --git a/testing/web-platform/tests/dom/traversal/NodeIterator-removal.html b/testing/web-platform/tests/dom/traversal/NodeIterator-removal.html
new file mode 100644
index 0000000000..b5fc69541a
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/NodeIterator-removal.html
@@ -0,0 +1,100 @@
+<!doctype html>
+<title>NodeIterator removal tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+for (var i = 0; i < testNodes.length; i++) {
+ var node = eval(testNodes[i]);
+ if (!node.parentNode) {
+ // Nothing to test
+ continue;
+ }
+ test(function() {
+ var iters = [];
+ var descs = [];
+ var expectedReferenceNodes = [];
+ var expectedPointers = [];
+
+ for (var j = 0; j < testNodes.length; j++) {
+ var root = eval(testNodes[j]);
+ // Add all distinct iterators with this root, calling nextNode()
+ // repeatedly until it winds up with the same iterator.
+ for (var k = 0; ; k++) {
+ var iter = document.createNodeIterator(root);
+ for (var l = 0; l < k; l++) {
+ iter.nextNode();
+ }
+ if (k && iter.referenceNode == iters[iters.length - 1].referenceNode
+ && iter.pointerBeforeReferenceNode
+ == iters[iters.length - 1].pointerBeforeReferenceNode) {
+ break;
+ } else {
+ iters.push(iter);
+ descs.push("document.createNodeIterator(" + testNodes[j]
+ + ") advanced " + k + " times");
+ expectedReferenceNodes.push(iter.referenceNode);
+ expectedPointers.push(iter.pointerBeforeReferenceNode);
+
+ var idx = iters.length - 1;
+
+ // "If the node is root or is not an inclusive ancestor of the
+ // referenceNode attribute value, terminate these steps."
+ //
+ // We also have to rule out the case where node is an ancestor of
+ // root, which is implicitly handled by the spec since such a node
+ // was not part of the iterator collection to start with.
+ if (isInclusiveAncestor(node, root)
+ || !isInclusiveAncestor(node, iter.referenceNode)) {
+ continue;
+ }
+
+ // "If the pointerBeforeReferenceNode attribute value is false, set
+ // the referenceNode attribute to the first node preceding the node
+ // that is being removed, and terminate these steps."
+ if (!iter.pointerBeforeReferenceNode) {
+ expectedReferenceNodes[idx] = previousNode(node);
+ continue;
+ }
+
+ // "If there is a node following the last inclusive descendant of the
+ // node that is being removed, set the referenceNode attribute to the
+ // first such node, and terminate these steps."
+ var next = nextNodeDescendants(node);
+ if (next) {
+ expectedReferenceNodes[idx] = next;
+ continue;
+ }
+
+ // "Set the referenceNode attribute to the first node preceding the
+ // node that is being removed and set the pointerBeforeReferenceNode
+ // attribute to false."
+ expectedReferenceNodes[idx] = previousNode(node);
+ expectedPointers[idx] = false;
+ }
+ }
+ }
+
+ var oldParent = node.parentNode;
+ var oldSibling = node.nextSibling;
+ oldParent.removeChild(node);
+
+ for (var j = 0; j < iters.length; j++) {
+ var iter = iters[j];
+ assert_equals(iter.referenceNode, expectedReferenceNodes[j],
+ ".referenceNode of " + descs[j]);
+ assert_equals(iter.pointerBeforeReferenceNode, expectedPointers[j],
+ ".pointerBeforeReferenceNode of " + descs[j]);
+ }
+
+ oldParent.insertBefore(node, oldSibling);
+ }, "Test removing node " + testNodes[i]);
+}
+
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/traversal/NodeIterator.html b/testing/web-platform/tests/dom/traversal/NodeIterator.html
new file mode 100644
index 0000000000..fb81676cc5
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/NodeIterator.html
@@ -0,0 +1,215 @@
+<!doctype html>
+<title>NodeIterator tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+function check_iter(iter, root, whatToShowValue) {
+ whatToShowValue = whatToShowValue === undefined ? 0xFFFFFFFF : whatToShowValue;
+
+ assert_equals(iter.toString(), '[object NodeIterator]', 'toString');
+ assert_equals(iter.root, root, 'root');
+ assert_equals(iter.whatToShow, whatToShowValue, 'whatToShow');
+ assert_equals(iter.filter, null, 'filter');
+ assert_equals(iter.referenceNode, root, 'referenceNode');
+ assert_equals(iter.pointerBeforeReferenceNode, true, 'pointerBeforeReferenceNode');
+ assert_readonly(iter, 'root');
+ assert_readonly(iter, 'whatToShow');
+ assert_readonly(iter, 'filter');
+ assert_readonly(iter, 'referenceNode');
+ assert_readonly(iter, 'pointerBeforeReferenceNode');
+}
+
+test(function() {
+ var iter = document.createNodeIterator(document);
+ iter.detach();
+ iter.detach();
+}, "detach() should be a no-op");
+
+test(function() {
+ var iter = document.createNodeIterator(document);
+ check_iter(iter, document);
+}, "createNodeIterator() parameter defaults");
+
+test(function() {
+ var iter = document.createNodeIterator(document, null, null);
+ check_iter(iter, document, 0);
+}, "createNodeIterator() with null as arguments");
+
+test(function() {
+ var iter = document.createNodeIterator(document, undefined, undefined);
+ check_iter(iter, document);
+}, "createNodeIterator() with undefined as arguments");
+
+test(function() {
+ var err = {name: "failed"};
+ var iter = document.createNodeIterator(document, NodeFilter.SHOW_ALL,
+ function() { throw err; });
+ assert_throws_exactly(err, function() { iter.nextNode() });
+}, "Propagate exception from filter function");
+
+test(function() {
+ var depth = 0;
+ var iter = document.createNodeIterator(document, NodeFilter.SHOW_ALL,
+ function() {
+ if (iter.referenceNode != document && depth == 0) {
+ depth++;
+ iter.nextNode();
+ }
+ return NodeFilter.FILTER_ACCEPT;
+ });
+ iter.nextNode();
+ iter.nextNode();
+ assert_throws_dom("InvalidStateError", function() { iter.nextNode() });
+ depth--;
+ assert_throws_dom("InvalidStateError", function() { iter.previousNode() });
+}, "Recursive filters need to throw");
+
+function testIterator(root, whatToShow, filter) {
+ var iter = document.createNodeIterator(root, whatToShow, filter);
+
+ assert_equals(iter.root, root, ".root");
+ assert_equals(iter.referenceNode, root, "Initial .referenceNode");
+ assert_equals(iter.pointerBeforeReferenceNode, true,
+ ".pointerBeforeReferenceNode");
+ assert_equals(iter.whatToShow, whatToShow, ".whatToShow");
+ assert_equals(iter.filter, filter, ".filter");
+
+ var expectedReferenceNode = root;
+ var expectedBeforeNode = true;
+ // "Let node be the value of the referenceNode attribute."
+ var node = root;
+ // "Let before node be the value of the pointerBeforeReferenceNode
+ // attribute."
+ var beforeNode = true;
+ var i = 1;
+ // Each loop iteration runs nextNode() once.
+ while (node) {
+ do {
+ if (!beforeNode) {
+ // "If before node is false, let node be the first node following node
+ // in the iterator collection. If there is no such node return null."
+ node = nextNode(node);
+ if (!isInclusiveDescendant(node, root)) {
+ node = null;
+ break;
+ }
+ } else {
+ // "If before node is true, set it to false."
+ beforeNode = false;
+ }
+ // "Filter node and let result be the return value.
+ //
+ // "If result is FILTER_ACCEPT, go to the next step in the overall set of
+ // steps.
+ //
+ // "Otherwise, run these substeps again."
+ if (!((1 << (node.nodeType - 1)) & whatToShow)
+ || (filter && filter(node) != NodeFilter.FILTER_ACCEPT)) {
+ continue;
+ }
+
+ // "Set the referenceNode attribute to node, set the
+ // pointerBeforeReferenceNode attribute to before node, and return node."
+ expectedReferenceNode = node;
+ expectedBeforeNode = beforeNode;
+
+ break;
+ } while (true);
+
+ assert_equals(iter.nextNode(), node, ".nextNode() " + i + " time(s)");
+ assert_equals(iter.referenceNode, expectedReferenceNode,
+ ".referenceNode after nextNode() " + i + " time(s)");
+ assert_equals(iter.pointerBeforeReferenceNode, expectedBeforeNode,
+ ".pointerBeforeReferenceNode after nextNode() " + i + " time(s)");
+
+ i++;
+ }
+
+ // Same but for previousNode() (mostly copy-pasted, oh well)
+ var iter = document.createNodeIterator(root, whatToShow, filter);
+
+ var expectedReferenceNode = root;
+ var expectedBeforeNode = true;
+ // "Let node be the value of the referenceNode attribute."
+ var node = root;
+ // "Let before node be the value of the pointerBeforeReferenceNode
+ // attribute."
+ var beforeNode = true;
+ var i = 1;
+ // Each loop iteration runs previousNode() once.
+ while (node) {
+ do {
+ if (beforeNode) {
+ // "If before node is true, let node be the first node preceding node
+ // in the iterator collection. If there is no such node return null."
+ node = previousNode(node);
+ if (!isInclusiveDescendant(node, root)) {
+ node = null;
+ break;
+ }
+ } else {
+ // "If before node is false, set it to true."
+ beforeNode = true;
+ }
+ // "Filter node and let result be the return value.
+ //
+ // "If result is FILTER_ACCEPT, go to the next step in the overall set of
+ // steps.
+ //
+ // "Otherwise, run these substeps again."
+ if (!((1 << (node.nodeType - 1)) & whatToShow)
+ || (filter && filter(node) != NodeFilter.FILTER_ACCEPT)) {
+ continue;
+ }
+
+ // "Set the referenceNode attribute to node, set the
+ // pointerBeforeReferenceNode attribute to before node, and return node."
+ expectedReferenceNode = node;
+ expectedBeforeNode = beforeNode;
+
+ break;
+ } while (true);
+
+ assert_equals(iter.previousNode(), node, ".previousNode() " + i + " time(s)");
+ assert_equals(iter.referenceNode, expectedReferenceNode,
+ ".referenceNode after previousNode() " + i + " time(s)");
+ assert_equals(iter.pointerBeforeReferenceNode, expectedBeforeNode,
+ ".pointerBeforeReferenceNode after previousNode() " + i + " time(s)");
+
+ i++;
+ }
+}
+
+var whatToShows = [
+ "0",
+ "0xFFFFFFFF",
+ "NodeFilter.SHOW_ELEMENT",
+ "NodeFilter.SHOW_ATTRIBUTE",
+ "NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_DOCUMENT",
+];
+
+var callbacks = [
+ "null",
+ "(function(node) { return true })",
+ "(function(node) { return false })",
+ "(function(node) { return node.nodeName[0] == '#' })",
+];
+
+for (var i = 0; i < testNodes.length; i++) {
+ for (var j = 0; j < whatToShows.length; j++) {
+ for (var k = 0; k < callbacks.length; k++) {
+ test(() => {
+ testIterator(eval(testNodes[i]), eval(whatToShows[j]), eval(callbacks[k]));
+ }, "document.createNodeIterator(" + testNodes[i] + ", " + whatToShows[j] + ", " + callbacks[k] + ")");
+ }
+ }
+}
+
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/traversal/TreeWalker-acceptNode-filter-cross-realm-null-browsing-context.html b/testing/web-platform/tests/dom/traversal/TreeWalker-acceptNode-filter-cross-realm-null-browsing-context.html
new file mode 100644
index 0000000000..f8e71bcdf5
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/TreeWalker-acceptNode-filter-cross-realm-null-browsing-context.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>TreeWalker: NodeFilter from detached iframe doesn't get called</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<div></div>
+
+<script>
+const t = async_test();
+
+const iframe = document.createElement("iframe");
+iframe.src = "support/TreeWalker-acceptNode-filter-cross-realm-null-browsing-context-subframe.html";
+iframe.onload = t.step_func_done(() => {
+ const nodeIterator = iframe.contentWindow.createNodeIterator();
+ iframe.remove();
+
+ assert_equals(iframe.contentWindow, null);
+
+ let errorWasThrown = false;
+ try { nodeIterator.nextNode(); }
+ catch { errorWasThrown = true; }
+
+ assert_true(errorWasThrown);
+ assert_false(nodeIterator.dummyFilterCalled);
+});
+
+document.body.append(iframe);
+</script>
diff --git a/testing/web-platform/tests/dom/traversal/TreeWalker-acceptNode-filter-cross-realm.html b/testing/web-platform/tests/dom/traversal/TreeWalker-acceptNode-filter-cross-realm.html
new file mode 100644
index 0000000000..da91cf6cb2
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/TreeWalker-acceptNode-filter-cross-realm.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>TreeWalker: cross-realm NodeFilter throws TypeError of its associated Realm</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#ref-for-prepare-to-run-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe name="nodeFilterGlobalObject" src="support/empty-document.html"></iframe>
+
+<div id="treeWalkerRoot">
+ <div class="firstChild"></div>
+</div>
+
+<script>
+test_onload(() => {
+ const nodeFilter = new nodeFilterGlobalObject.Object;
+
+ const walker = document.createTreeWalker(treeWalkerRoot, NodeFilter.SHOW_ELEMENT, nodeFilter);
+ assert_throws_js(nodeFilterGlobalObject.TypeError, () => { walker.firstChild(); });
+}, "NodeFilter is cross-realm plain object without 'acceptNode' property");
+
+test_onload(() => {
+ const nodeFilter = new nodeFilterGlobalObject.Object;
+ nodeFilter.acceptNode = {};
+
+ const walker = document.createTreeWalker(treeWalkerRoot, NodeFilter.SHOW_ELEMENT, nodeFilter);
+ assert_throws_js(nodeFilterGlobalObject.TypeError, () => { walker.firstChild(); });
+}, "NodeFilter is cross-realm plain object with non-callable 'acceptNode' property");
+
+test_onload(() => {
+ const { proxy, revoke } = Proxy.revocable(() => {}, {});
+ revoke();
+
+ const nodeFilter = new nodeFilterGlobalObject.Object;
+ nodeFilter.acceptNode = proxy;
+
+ const walker = document.createTreeWalker(treeWalkerRoot, NodeFilter.SHOW_ELEMENT, nodeFilter);
+ assert_throws_js(nodeFilterGlobalObject.TypeError, () => { walker.firstChild(); });
+}, "NodeFilter is cross-realm plain object with revoked Proxy as 'acceptNode' property");
+
+test_onload(() => {
+ const { proxy, revoke } = nodeFilterGlobalObject.Proxy.revocable({}, {});
+ revoke();
+
+ const walker = document.createTreeWalker(treeWalkerRoot, NodeFilter.SHOW_ELEMENT, proxy);
+ assert_throws_js(nodeFilterGlobalObject.TypeError, () => { walker.firstChild(); });
+}, "NodeFilter is cross-realm non-callable revoked Proxy");
+
+test_onload(() => {
+ const { proxy, revoke } = nodeFilterGlobalObject.Proxy.revocable(() => {}, {});
+ revoke();
+
+ const walker = document.createTreeWalker(treeWalkerRoot, NodeFilter.SHOW_ELEMENT, proxy);
+ assert_throws_js(nodeFilterGlobalObject.TypeError, () => { walker.firstChild(); });
+}, "NodeFilter is cross-realm callable revoked Proxy");
+
+function test_onload(fn, desc) {
+ async_test(t => { window.addEventListener("load", t.step_func_done(fn)); }, desc);
+}
+</script>
diff --git a/testing/web-platform/tests/dom/traversal/TreeWalker-acceptNode-filter.html b/testing/web-platform/tests/dom/traversal/TreeWalker-acceptNode-filter.html
new file mode 100644
index 0000000000..282dc9d142
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/TreeWalker-acceptNode-filter.html
@@ -0,0 +1,195 @@
+<!DOCTYPE html>
+<html>
+<!--
+Test adapted from chromium/source/src/third_party/WebKit/LayoutTests/fast/dom/TreeWalker/script-tests/acceptNode-filter.js
+-->
+<head>
+<title>TreeWalker: acceptNode-filter</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/assert-node.js"></script>
+<link rel="help" href="https://dom.spec.whatwg.org/#callbackdef-nodefilter">
+<div id=log></div>
+</head>
+<body>
+<p>Test JS objects as NodeFilters</p>
+<script>
+var testElement;
+setup(function() {
+ testElement = document.createElement("div");
+ testElement.id = 'root';
+ //testElement.innerHTML='<div id="A1"><div id="B1"></div><div id="B2"></div></div>';
+
+ // XXX for Servo, build the tree without using innerHTML
+ var a1 = document.createElement("div");
+ a1.id = "A1";
+ var b1 = document.createElement("div");
+ b1.id = "B1";
+ var b2 = document.createElement("div");
+ b2.id = "B2";
+ testElement.appendChild(a1);
+ a1.appendChild(b1);
+ a1.appendChild(b2);
+});
+
+test(function()
+{
+ function filter(node)
+ {
+ if (node.id == "B1")
+ return NodeFilter.FILTER_SKIP;
+ return NodeFilter.FILTER_ACCEPT;
+ }
+
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, filter);
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+ assert_node(walker.firstChild(), { type: Element, id: 'A1' });
+ assert_node(walker.currentNode, { type: Element, id: 'A1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'B2' });
+ assert_node(walker.currentNode, { type: Element, id: 'B2' });
+}, 'Testing with raw function filter');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, {
+ acceptNode : function(node) {
+ if (node.id == "B1")
+ return NodeFilter.FILTER_SKIP;
+ return NodeFilter.FILTER_ACCEPT;
+ }
+ });
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+ assert_node(walker.firstChild(), { type: Element, id: 'A1' });
+ assert_node(walker.currentNode, { type: Element, id: 'A1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'B2' });
+ assert_node(walker.currentNode, { type: Element, id: 'B2' });
+}, 'Testing with object filter');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, null);
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+ assert_node(walker.firstChild(), { type: Element, id: 'A1' });
+ assert_node(walker.currentNode, { type: Element, id: 'A1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'B1' });
+ assert_node(walker.currentNode, { type: Element, id: 'B1' });
+}, 'Testing with null filter');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, undefined);
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+ assert_node(walker.firstChild(), { type: Element, id: 'A1' });
+ assert_node(walker.currentNode, { type: Element, id: 'A1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'B1' });
+ assert_node(walker.currentNode, { type: Element, id: 'B1' });
+}, 'Testing with undefined filter');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, {});
+ assert_throws_js(TypeError, function () { walker.firstChild(); });
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+ assert_throws_js(TypeError, function () { walker.nextNode(); });
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+}, 'Testing with object lacking acceptNode property');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, { acceptNode: "foo" });
+ assert_throws_js(TypeError, function () { walker.firstChild(); });
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+ assert_throws_js(TypeError, function () { walker.nextNode(); });
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+}, 'Testing with object with non-function acceptNode property');
+
+test(function(t)
+{
+ var filter = function() { return NodeFilter.FILTER_ACCEPT; };
+ filter.acceptNode = t.unreached_func("`acceptNode` method should not be called on functions");
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, filter);
+ assert_node(walker.firstChild(), { type: Element, id: 'A1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'B1' });
+}, 'Testing with function having acceptNode function');
+
+test(function()
+{
+ var test_error = { name: "test" };
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT,
+ function(node) {
+ throw test_error;
+ });
+ assert_throws_exactly(test_error, function () { walker.firstChild(); });
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+ assert_throws_exactly(test_error, function () { walker.nextNode(); });
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+}, 'Testing with filter function that throws');
+
+test(function() {
+ var testError = { name: "test" };
+ var filter = {
+ get acceptNode() {
+ throw testError;
+ },
+ };
+
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, filter);
+ assert_throws_exactly(testError, function() { walker.firstChild(); });
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+ assert_throws_exactly(testError, function() { walker.nextNode(); });
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+}, "rethrows errors when getting `acceptNode`");
+
+test(function() {
+ var calls = 0;
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, {
+ get acceptNode() {
+ calls++;
+ return function() {
+ return NodeFilter.FILTER_ACCEPT;
+ };
+ },
+ });
+
+ assert_equals(calls, 0);
+ walker.nextNode();
+ walker.nextNode();
+ assert_equals(calls, 2);
+}, "performs `Get` on every traverse");
+
+test(function()
+{
+ var test_error = { name: "test" };
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT,
+ {
+ acceptNode : function(node) {
+ throw test_error;
+ }
+ });
+ assert_throws_exactly(test_error, function () { walker.firstChild(); });
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+ assert_throws_exactly(test_error, function () { walker.nextNode(); });
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+}, 'Testing with filter object that throws');
+
+test(() =>
+{
+ let thisValue, nodeArgID;
+ const filter = {
+ acceptNode(node) {
+ thisValue = this;
+ nodeArgID = node.id;
+ return NodeFilter.FILTER_ACCEPT;
+ },
+ };
+
+ const walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, filter);
+ walker.nextNode();
+
+ assert_equals(thisValue, filter);
+ assert_equals(nodeArgID, 'A1');
+}, 'Testing with filter object: this value and `node` argument');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/traversal/TreeWalker-basic.html b/testing/web-platform/tests/dom/traversal/TreeWalker-basic.html
new file mode 100644
index 0000000000..bd8b112840
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/TreeWalker-basic.html
@@ -0,0 +1,154 @@
+<!DOCTYPE html>
+<html>
+<!--
+Test adapted from chromium/source/src/third_party/WebKit/LayoutTests/fast/dom/TreeWalker/TreeWalker-basic.html
+-->
+<head>
+<title>TreeWalker: Basic test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/assert-node.js"></script>
+<div id=log></div>
+</head>
+<body>
+<p>This test checks the basic functionality of TreeWalker.</p>
+<script>
+function createSampleDOM()
+{
+ // Tree structure:
+ // #a
+ // |
+ // +----+----+
+ // | |
+ // "b" #c
+ // |
+ // +----+----+
+ // | |
+ // #d <!--j-->
+ // |
+ // +----+----+
+ // | | |
+ // "e" #f "i"
+ // |
+ // +--+--+
+ // | |
+ // "g" <!--h-->
+ var div = document.createElement('div');
+ div.id = 'a';
+ // div.innerHTML = 'b<div id="c"><div id="d">e<span id="f">g<!--h--></span>i</div><!--j--></div>';
+
+ div.appendChild(document.createTextNode("b"));
+
+ var c = document.createElement("div");
+ c.id = 'c';
+ div.appendChild(c);
+
+ var d = document.createElement("div");
+ d.id = 'd';
+ c.appendChild(d);
+
+ var e = document.createTextNode("e");
+ d.appendChild(e);
+
+ var f = document.createElement("span");
+ f.id = 'f';
+ d.appendChild(f);
+
+ var g = document.createTextNode("g");
+ f.appendChild(g);
+
+ var h = document.createComment("h");
+ f.appendChild(h);
+
+ var i = document.createTextNode("i");
+ d.appendChild(i);
+
+ var j = document.createComment("j");
+ c.appendChild(j);
+
+ return div;
+}
+
+function check_walker(walker, root, whatToShowValue)
+{
+ whatToShowValue = whatToShowValue === undefined ? 0xFFFFFFFF : whatToShowValue;
+
+ assert_equals(walker.toString(), '[object TreeWalker]', 'toString');
+ assert_equals(walker.root, root, 'root');
+ assert_equals(walker.whatToShow, whatToShowValue, 'whatToShow');
+ assert_equals(walker.filter, null, 'filter');
+ assert_equals(walker.currentNode, root, 'currentNode');
+ assert_readonly(walker, 'root');
+ assert_readonly(walker, 'whatToShow');
+ assert_readonly(walker, 'filter');
+}
+
+test(function ()
+{
+ var root = createSampleDOM();
+ var walker = document.createTreeWalker(root);
+ check_walker(walker, root);
+}, 'Construct a TreeWalker by document.createTreeWalker(root).');
+
+test(function ()
+{
+ var root = createSampleDOM();
+ var walker = document.createTreeWalker(root, null, null);
+ check_walker(walker, root, 0);
+}, 'Construct a TreeWalker by document.createTreeWalker(root, null, null).');
+
+test(function ()
+{
+ var root = createSampleDOM();
+ var walker = document.createTreeWalker(root, undefined, undefined);
+ check_walker(walker, root);
+}, 'Construct a TreeWalker by document.createTreeWalker(root, undefined, undefined).');
+
+test(function ()
+{
+ assert_throws_js(TypeError, function () { document.createTreeWalker(); });
+ assert_throws_js(TypeError, function () { document.createTreeWalker(null); });
+ assert_throws_js(TypeError, function () { document.createTreeWalker(undefined); });
+ assert_throws_js(TypeError, function () { document.createTreeWalker(new Object()); });
+ assert_throws_js(TypeError, function () { document.createTreeWalker(1); });
+}, 'Give an invalid root node to document.createTreeWalker().');
+
+test(function ()
+{
+ var root = createSampleDOM();
+ var walker = document.createTreeWalker(root);
+ var f = root.lastChild.firstChild.childNodes[1]; // An element node: div#f.
+
+ assert_node(walker.currentNode, { type: Element, id: 'a' });
+ assert_equals(walker.parentNode(), null);
+ assert_node(walker.currentNode, { type: Element, id: 'a' });
+ assert_node(walker.firstChild(), { type: Text, nodeValue: 'b' });
+ assert_node(walker.currentNode, { type: Text, nodeValue: 'b' });
+ assert_node(walker.nextSibling(), { type: Element, id: 'c' });
+ assert_node(walker.currentNode, { type: Element, id: 'c' });
+ assert_node(walker.lastChild(), { type: Comment, nodeValue: 'j' });
+ assert_node(walker.currentNode, { type: Comment, nodeValue: 'j' });
+ assert_node(walker.previousSibling(), { type: Element, id: 'd' });
+ assert_node(walker.currentNode, { type: Element, id: 'd' });
+ assert_node(walker.nextNode(), { type: Text, nodeValue: 'e' });
+ assert_node(walker.currentNode, { type: Text, nodeValue: 'e' });
+ assert_node(walker.parentNode(), { type: Element, id: 'd' });
+ assert_node(walker.currentNode, { type: Element, id: 'd' });
+ assert_node(walker.previousNode(), { type: Element, id: 'c' });
+ assert_node(walker.currentNode, { type: Element, id: 'c' });
+ assert_equals(walker.nextSibling(), null);
+ assert_node(walker.currentNode, { type: Element, id: 'c' });
+ walker.currentNode = f;
+ assert_equals(walker.currentNode, f);
+}, 'Walk over nodes.');
+
+test(function() {
+ var treeWalker = document.createTreeWalker(document.body, 42, null);
+ assert_equals(treeWalker.root, document.body);
+ assert_equals(treeWalker.currentNode, document.body);
+ assert_equals(treeWalker.whatToShow, 42);
+ assert_equals(treeWalker.filter, null);
+}, "Optional arguments to createTreeWalker should be optional (3 passed, null).");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/traversal/TreeWalker-currentNode.html b/testing/web-platform/tests/dom/traversal/TreeWalker-currentNode.html
new file mode 100644
index 0000000000..f795abe0d2
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/TreeWalker-currentNode.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+<!--
+Test adapted from chromium/source/src/third_party/WebKit/LayoutTests/fast/dom/TreeWalker/resources/TreeWalker-currentNode.js
+-->
+<head>
+<title>TreeWalker: currentNode</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/assert-node.js"></script>
+<div id=log></div>
+</head>
+<body>
+<div id='parent'>
+<div id='subTree'><p>Lorem ipsum <span>dolor <b>sit</b> amet</span>, consectetur <i>adipisicing</i> elit, sed do eiusmod <tt>tempor <b><i>incididunt ut</i> labore</b> et dolore magna</tt> aliqua.</p></div>
+</div>
+<p>Test TreeWalker currentNode functionality</p>
+<script>
+// var subTree = document.createElement('div');
+// subTree.innerHTML = "<p>Lorem ipsum <span>dolor <b>sit</b> amet</span>, consectetur <i>adipisicing</i> elit, sed do eiusmod <tt>tempor <b><i>incididunt ut</i> labore</b> et dolore magna</tt> aliqua.</p>"
+// document.body.appendChild(subTree);
+var subTree = document.getElementById("subTree");
+
+var all = function(node) { return true; }
+
+test(function()
+{
+ var w = document.createTreeWalker(subTree, NodeFilter.SHOW_ELEMENT, all);
+ assert_node(w.currentNode, { type: Element, id: 'subTree' });
+ assert_equals(w.parentNode(), null);
+ assert_node(w.currentNode, { type: Element, id: 'subTree' });
+}, "Test that TreeWalker.parent() doesn't set the currentNode to a node not under the root.");
+
+test(function()
+{
+ var w = document.createTreeWalker(subTree,
+ NodeFilter.SHOW_ELEMENT
+ | NodeFilter.SHOW_COMMENT,
+ all);
+ w.currentNode = document.documentElement;
+ assert_equals(w.parentNode(), null);
+ assert_equals(w.currentNode, document.documentElement);
+ w.currentNode = document.documentElement;
+ assert_equals(w.nextNode(), document.documentElement.firstChild);
+ assert_equals(w.currentNode, document.documentElement.firstChild);
+ w.currentNode = document.documentElement;
+ assert_equals(w.previousNode(), null);
+ assert_equals(w.currentNode, document.documentElement);
+ w.currentNode = document.documentElement;
+ assert_equals(w.firstChild(), document.documentElement.firstChild);
+ assert_equals(w.currentNode, document.documentElement.firstChild);
+ w.currentNode = document.documentElement;
+ assert_equals(w.lastChild(), document.documentElement.lastChild);
+ assert_equals(w.currentNode, document.documentElement.lastChild);
+ w.currentNode = document.documentElement;
+ assert_equals(w.nextSibling(), null);
+ assert_equals(w.currentNode, document.documentElement);
+ w.currentNode = document.documentElement;
+ assert_equals(w.previousSibling(), null);
+ assert_equals(w.currentNode, document.documentElement);
+}, "Test that we handle setting the currentNode to arbitrary nodes not under the root element.");
+
+test(function()
+{
+ var w = document.createTreeWalker(subTree, NodeFilter.SHOW_ELEMENT, all);
+ w.currentNode = subTree.previousSibling;
+ assert_equals(w.nextNode(), subTree);
+ w.currentNode = document.getElementById("parent");
+ assert_equals(w.firstChild(), subTree);
+}, "Test how we handle the case when the traversed to node is within the root, but the currentElement is not.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/traversal/TreeWalker-previousNodeLastChildReject.html b/testing/web-platform/tests/dom/traversal/TreeWalker-previousNodeLastChildReject.html
new file mode 100644
index 0000000000..e24ca06e20
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/TreeWalker-previousNodeLastChildReject.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+<!--
+Test adapted from chromium/source/src/third_party/WebKit/LayoutTests/fast/dom/TreeWalker/script-tests/previousNodeLastChildReject.js
+-->
+<head>
+<title>TreeWalker: previousNodeLastChildReject</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/assert-node.js"></script>
+<div id=log></div>
+</head>
+<body>
+<p>Test that previousNode properly respects the filter.</p>
+<script>
+var testElement;
+setup(function() {
+ testElement = document.createElement("div");
+ testElement.id = 'root';
+ // testElement.innerHTML='<div id="A1"><div id="B1"><div id="C1"></div><div id="C2"><div id="D1"></div><div id="D2"></div></div></div><div id="B2"><div id="C3"></div><div id="C4"></div></div></div>';
+ // testElement.innerHTML='
+ // <div id="A1">
+ // <div id="B1">
+ // <div id="C1">
+ // </div>
+ // <div id="C2">
+ // <div id="D1">
+ // </div>
+ // <div id="D2">
+ // </div>
+ // </div>
+ // </div>
+ // <div id="B2">
+ // <div id="C3">
+ // </div>
+ // <div id="C4">
+ // </div>
+ // </div>
+ // </div>';
+
+ // XXX for Servo, build the tree without using innerHTML
+ var a1 = document.createElement("div"); a1.id = "A1";
+ var b1 = document.createElement("div"); b1.id = "B1";
+ var b2 = document.createElement("div"); b2.id = "B2";
+ var c1 = document.createElement("div"); c1.id = "C1";
+ var c2 = document.createElement("div"); c2.id = "C2";
+ var c3 = document.createElement("div"); c3.id = "C3";
+ var c4 = document.createElement("div"); c4.id = "C4";
+ var d1 = document.createElement("div"); d1.id = "D1";
+ var d2 = document.createElement("div"); d2.id = "D2";
+
+ testElement.appendChild(a1);
+ a1.appendChild(b1);
+ a1.appendChild(b2);
+ b1.appendChild(c1);
+ b1.appendChild(c2);
+ b2.appendChild(c3);
+ b2.appendChild(c4);
+ c2.appendChild(d1);
+ c2.appendChild(d2);
+});
+
+test(function()
+{
+ function filter(node)
+ {
+ if (node.id == "C2")
+ return NodeFilter.FILTER_REJECT;
+ return NodeFilter.FILTER_ACCEPT;
+ }
+
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, filter);
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+ assert_node(walker.firstChild(), { type: Element, id: 'A1' });
+ assert_node(walker.currentNode, { type: Element, id: 'A1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'B1' });
+ assert_node(walker.currentNode, { type: Element, id: 'B1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'C1' });
+ assert_node(walker.currentNode, { type: Element, id: 'C1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'B2' });
+ assert_node(walker.currentNode, { type: Element, id: 'B2' });
+ assert_node(walker.previousNode(), { type: Element, id: 'C1' });
+ assert_node(walker.currentNode, { type: Element, id: 'C1' });
+}, 'Test that previousNode properly respects the filter.');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/traversal/TreeWalker-previousSiblingLastChildSkip.html b/testing/web-platform/tests/dom/traversal/TreeWalker-previousSiblingLastChildSkip.html
new file mode 100644
index 0000000000..5e28aa5142
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/TreeWalker-previousSiblingLastChildSkip.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html>
+<!--
+Test adapted from chromium/source/src/third_party/WebKit/LayoutTests/fast/dom/TreeWalker/script-tests/previousSiblingLastChildSkip.js
+-->
+<head>
+<title>TreeWalker: previousSiblingLastChildSkip</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/assert-node.js"></script>
+<div id=log></div>
+</head>
+<body>
+<p>Test that previousSibling properly respects the filter.</p>
+<script>
+var testElement;
+setup(function() {
+ testElement = document.createElement("div");
+ testElement.id = 'root';
+ // testElement.innerHTML='<div id="A1"><div id="B1"><div id="C1"></div><div id="C2"><div id="D1"></div><div id="D2"></div></div></div><div id="B2"><div id="C3"></div><div id="C4"></div></div></div>';
+ // testElement.innerHTML='
+ // <div id="A1">
+ // <div id="B1">
+ // <div id="C1">
+ // </div>
+ // <div id="C2">
+ // <div id="D1">
+ // </div>
+ // <div id="D2">
+ // </div>
+ // </div>
+ // </div>
+ // <div id="B2">
+ // <div id="C3">
+ // </div>
+ // <div id="C4">
+ // </div>
+ // </div>
+ // </div>';
+
+ // XXX for Servo, build the tree without using innerHTML
+ var a1 = document.createElement("div"); a1.id = "A1";
+ var b1 = document.createElement("div"); b1.id = "B1";
+ var b2 = document.createElement("div"); b2.id = "B2";
+ var c1 = document.createElement("div"); c1.id = "C1";
+ var c2 = document.createElement("div"); c2.id = "C2";
+ var c3 = document.createElement("div"); c3.id = "C3";
+ var c4 = document.createElement("div"); c4.id = "C4";
+ var d1 = document.createElement("div"); d1.id = "D1";
+ var d2 = document.createElement("div"); d2.id = "D2";
+
+ testElement.appendChild(a1);
+ a1.appendChild(b1);
+ a1.appendChild(b2);
+ b1.appendChild(c1);
+ b1.appendChild(c2);
+ b2.appendChild(c3);
+ b2.appendChild(c4);
+ c2.appendChild(d1);
+ c2.appendChild(d2);
+});
+
+test(function()
+{
+ function filter(node)
+ {
+ if (node.id == "B1")
+ return NodeFilter.FILTER_SKIP;
+ return NodeFilter.FILTER_ACCEPT;
+ }
+
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, filter);
+ assert_node(walker.currentNode, { type: Element, id: 'root' });
+ assert_node(walker.firstChild(), { type: Element, id: 'A1' });
+ assert_node(walker.currentNode, { type: Element, id: 'A1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'C1' });
+ assert_node(walker.currentNode, { type: Element, id: 'C1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'C2' });
+ assert_node(walker.currentNode, { type: Element, id: 'C2' });
+ assert_node(walker.nextNode(), { type: Element, id: 'D1' });
+ assert_node(walker.currentNode, { type: Element, id: 'D1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'D2' });
+ assert_node(walker.currentNode, { type: Element, id: 'D2' });
+ assert_node(walker.nextNode(), { type: Element, id: 'B2' });
+ assert_node(walker.currentNode, { type: Element, id: 'B2' });
+ assert_node(walker.previousSibling(), { type: Element, id: 'C2' });
+ assert_node(walker.currentNode, { type: Element, id: 'C2' });
+}, 'Test that previousSibling properly respects the filter.');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/traversal/TreeWalker-traversal-reject.html b/testing/web-platform/tests/dom/traversal/TreeWalker-traversal-reject.html
new file mode 100644
index 0000000000..d6c96adc11
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/TreeWalker-traversal-reject.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html>
+<!--
+Test adapted from chromium/source/src/third_party/WebKit/LayoutTests/fast/dom/TreeWalker/script-tests/traversal-reject.js
+-->
+<head>
+<title>TreeWalker: traversal-reject</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/assert-node.js"></script>
+<div id=log></div>
+</head>
+<body>
+<p>Test TreeWalker with rejection</p>
+<script>
+var testElement;
+setup(function() {
+ testElement = document.createElement("div");
+ testElement.id = 'root';
+ //testElement.innerHTML='<div id="A1"> <div id="B1"> <div id="C1"></div> </div> <div id="B2"></div><div id="B3"></div> </div>';
+ // <div id="A1">
+ // <div id="B1">
+ // <div id="C1"></div>
+ // </div>
+ // <div id="B2"></div>
+ // <div id="B3"></div>
+ // </div>
+
+
+ // XXX for Servo, build the tree without using innerHTML
+ var a1 = document.createElement("div"); a1.id = "A1";
+ var b1 = document.createElement("div"); b1.id = "B1";
+ var b2 = document.createElement("div"); b2.id = "B2";
+ var b3 = document.createElement("div"); b3.id = "B3";
+ var c1 = document.createElement("div"); c1.id = "C1";
+
+ testElement.appendChild(a1);
+ a1.appendChild(b1);
+ a1.appendChild(b2);
+ a1.appendChild(b3);
+ b1.appendChild(c1);
+});
+
+var rejectB1Filter = {
+ acceptNode: function(node) {
+ if (node.id == 'B1')
+ return NodeFilter.FILTER_REJECT;
+
+ return NodeFilter.FILTER_ACCEPT;
+ }
+}
+
+var skipB2Filter = {
+ acceptNode: function(node) {
+ if (node.id == 'B2')
+ return NodeFilter.FILTER_SKIP;
+
+ return NodeFilter.FILTER_ACCEPT;
+ }
+}
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, rejectB1Filter);
+ assert_node(walker.nextNode(), { type: Element, id: 'A1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'B2' });
+ assert_node(walker.nextNode(), { type: Element, id: 'B3' });
+}, 'Testing nextNode');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, rejectB1Filter);
+ assert_node(walker.firstChild(), { type: Element, id: 'A1' });
+ assert_node(walker.firstChild(), { type: Element, id: 'B2' });
+}, 'Testing firstChild');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, skipB2Filter);
+ assert_node(walker.firstChild(), { type: Element, id: 'A1' });
+ assert_node(walker.firstChild(), { type: Element, id: 'B1' });
+ assert_node(walker.nextSibling(), { type: Element, id: 'B3' });
+}, 'Testing nextSibling');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, rejectB1Filter);
+ walker.currentNode = testElement.querySelectorAll('#C1')[0];
+ assert_node(walker.parentNode(), { type: Element, id: 'A1' });
+}, 'Testing parentNode');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, skipB2Filter);
+ walker.currentNode = testElement.querySelectorAll('#B3')[0];
+ assert_node(walker.previousSibling(), { type: Element, id: 'B1' });
+}, 'Testing previousSibling');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, rejectB1Filter);
+ walker.currentNode = testElement.querySelectorAll('#B3')[0];
+ assert_node(walker.previousNode(), { type: Element, id: 'B2' });
+ assert_node(walker.previousNode(), { type: Element, id: 'A1' });
+}, 'Testing previousNode');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/traversal/TreeWalker-traversal-skip-most.html b/testing/web-platform/tests/dom/traversal/TreeWalker-traversal-skip-most.html
new file mode 100644
index 0000000000..b6eafd4596
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/TreeWalker-traversal-skip-most.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<!--
+Test adapted from chromium/source/src/third_party/WebKit/LayoutTests/fast/dom/TreeWalker/script-tests/traversal-skip-most.js
+-->
+<head>
+<title>TreeWalker: traversal-skip-most</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/assert-node.js"></script>
+<div id=log></div>
+</head>
+<body>
+<p>Test TreeWalker with skipping</p>
+<script>
+var testElement;
+setup(function() {
+ testElement = document.createElement("div");
+ testElement.id = 'root';
+ // testElement.innerHTML='<div id="A1"><div id="B1" class="keep"></div><div id="B2">this text matters</div><div id="B3" class="keep"></div></div>';
+ // <div id="A1">
+ // <div id="B1" class="keep"></div>
+ // <div id="B2">this text matters</div>
+ // <div id="B3" class="keep"></div>
+ // </div>
+
+
+ // XXX for Servo, build the tree without using innerHTML
+ var a1 = document.createElement("div"); a1.id = "A1";
+ var b1 = document.createElement("div"); b1.id = "B1"; b1.className = "keep";
+ var b2 = document.createElement("div"); b2.id = "B2";
+ var b3 = document.createElement("div"); b3.id = "B3"; b3.className = "keep";
+
+ testElement.appendChild(a1);
+ a1.appendChild(b1);
+ a1.appendChild(b2)
+ .appendChild(document.createTextNode("this text matters"));
+ a1.appendChild(b3);
+});
+
+var filter = {
+ acceptNode: function(node) {
+ if (node.className == 'keep')
+ return NodeFilter.FILTER_ACCEPT;
+
+ return NodeFilter.FILTER_SKIP;
+ }
+}
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, filter);
+ assert_node(walker.firstChild(), { type: Element, id: 'B1' });
+ assert_node(walker.nextSibling(), { type: Element, id: 'B3' });
+}, 'Testing nextSibling');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, filter);
+ walker.currentNode = testElement.querySelectorAll('#B3')[0];
+ assert_node(walker.previousSibling(), { type: Element, id: 'B1' });
+}, 'Testing previousSibling');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/traversal/TreeWalker-traversal-skip.html b/testing/web-platform/tests/dom/traversal/TreeWalker-traversal-skip.html
new file mode 100644
index 0000000000..6bbebe667e
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/TreeWalker-traversal-skip.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<html>
+<!--
+Test adapted from chromium/source/src/third_party/WebKit/LayoutTests/fast/dom/TreeWalker/script-tests/traversal-skip.js
+-->
+<head>
+<title>TreeWalker: traversal-skip</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/assert-node.js"></script>
+<div id=log></div>
+</head>
+<body>
+<p>Test TreeWalker with skipping</p>
+<script>
+var testElement;
+setup(function() {
+ testElement = document.createElement("div");
+ testElement.id = 'root';
+ // testElement.innerHTML='<div id="A1"> <div id="B1"> <div id="C1"></div> </div> <div id="B2"></div><div id="B3"></div> </div>';
+ // <div id="A1">
+ // <div id="B1">
+ // <div id="C1"></div>
+ // </div>
+ // <div id="B2"></div>
+ // <div id="B3"></div>
+ // </div>
+
+
+ // XXX for Servo, build the tree without using innerHTML
+ var a1 = document.createElement("div"); a1.id = "A1";
+ var b1 = document.createElement("div"); b1.id = "B1";
+ var b2 = document.createElement("div"); b2.id = "B2";
+ var b3 = document.createElement("div"); b3.id = "B3";
+ var c1 = document.createElement("div"); c1.id = "C1";
+
+ testElement.appendChild(a1);
+ a1.appendChild(b1);
+ a1.appendChild(b2);
+ a1.appendChild(b3);
+ b1.appendChild(c1);
+});
+
+var skipB1Filter = {
+ acceptNode: function(node) {
+ if (node.id == 'B1')
+ return NodeFilter.FILTER_SKIP;
+
+ return NodeFilter.FILTER_ACCEPT;
+ }
+}
+
+var skipB2Filter = {
+ acceptNode: function(node) {
+ if (node.id == 'B2')
+ return NodeFilter.FILTER_SKIP;
+
+ return NodeFilter.FILTER_ACCEPT;
+ }
+}
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, skipB1Filter);
+ assert_node(walker.nextNode(), { type: Element, id: 'A1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'C1' });
+ assert_node(walker.nextNode(), { type: Element, id: 'B2' });
+ assert_node(walker.nextNode(), { type: Element, id: 'B3' });
+}, 'Testing nextNode');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, skipB1Filter);
+ assert_node(walker.firstChild(), { type: Element, id: 'A1' });
+ assert_node(walker.firstChild(), { type: Element, id: 'C1' });
+}, 'Testing firstChild');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, skipB2Filter);
+ assert_node(walker.firstChild(), { type: Element, id: 'A1' });
+ assert_node(walker.firstChild(), { type: Element, id: 'B1' });
+ assert_node(walker.nextSibling(), { type: Element, id: 'B3' });
+}, 'Testing nextSibling');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, skipB1Filter);
+ walker.currentNode = testElement.querySelectorAll('#C1')[0];
+ assert_node(walker.parentNode(), { type: Element, id: 'A1' });
+}, 'Testing parentNode');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, skipB2Filter);
+ walker.currentNode = testElement.querySelectorAll('#B3')[0];
+ assert_node(walker.previousSibling(), { type: Element, id: 'B1' });
+}, 'Testing previousSibling');
+
+test(function()
+{
+ var walker = document.createTreeWalker(testElement, NodeFilter.SHOW_ELEMENT, skipB1Filter);
+ walker.currentNode = testElement.querySelectorAll('#B3')[0];
+ assert_node(walker.previousNode(), { type: Element, id: 'B2' });
+ assert_node(walker.previousNode(), { type: Element, id: 'C1' });
+ assert_node(walker.previousNode(), { type: Element, id: 'A1' });
+}, 'Testing previousNode');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/traversal/TreeWalker-walking-outside-a-tree.html b/testing/web-platform/tests/dom/traversal/TreeWalker-walking-outside-a-tree.html
new file mode 100644
index 0000000000..b99e33e01f
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/TreeWalker-walking-outside-a-tree.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<!--
+Test adapted from https://github.com/operasoftware/presto-testo/blob/master/core/standards/acid3/individual/006a.html
+-->
+<head>
+<title>TreeWalker: walking-outside-a-tree</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/assert-node.js"></script>
+<div id=log></div>
+</head>
+<body>
+<p>[Acid3 - Test 006a] walking outside a tree</p>
+<script>
+test(function () {
+ // test 6: walking outside a tree
+ var doc = document.createElement("div");
+ var head = document.createElement('head');
+ var title = document.createElement('title');
+ var body = document.createElement('body');
+ var p = document.createElement('p');
+ doc.appendChild(head);
+ head.appendChild(title);
+ doc.appendChild(body);
+ body.appendChild(p);
+
+ var w = document.createTreeWalker(body, 0xFFFFFFFF, null);
+ doc.removeChild(body);
+ assert_equals(w.lastChild(), p, "TreeWalker failed after removing the current node from the tree");
+ doc.appendChild(p);
+ assert_equals(w.previousNode(), title, "failed to handle regrafting correctly");
+ p.appendChild(body);
+ assert_equals(w.nextNode(), p, "couldn't retrace steps");
+ assert_equals(w.nextNode(), body, "couldn't step back into root");
+ assert_equals(w.previousNode(), null, "root didn't retake its rootish position");
+}, "walking outside a tree");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/dom/traversal/TreeWalker.html b/testing/web-platform/tests/dom/traversal/TreeWalker.html
new file mode 100644
index 0000000000..093c781447
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/TreeWalker.html
@@ -0,0 +1,324 @@
+<!doctype html>
+<title>TreeWalker tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+// TODO .previousNode, .nextNode
+
+test(function() {
+ var depth = 0;
+ var walker = document.createTreeWalker(document, NodeFilter.SHOW_ALL,
+ function() {
+ if (depth == 0) {
+ depth++;
+ walker.firstChild();
+ }
+ return NodeFilter.FILTER_ACCEPT;
+ });
+ walker.currentNode = document.body;
+ assert_throws_dom("InvalidStateError", function() { walker.parentNode() });
+ depth--;
+ assert_throws_dom("InvalidStateError", function() { walker.firstChild() });
+ depth--;
+ assert_throws_dom("InvalidStateError", function() { walker.lastChild() });
+ depth--;
+ assert_throws_dom("InvalidStateError", function() { walker.previousSibling() });
+ depth--;
+ assert_throws_dom("InvalidStateError", function() { walker.nextSibling() });
+ depth--;
+ assert_throws_dom("InvalidStateError", function() { walker.previousNode() });
+ depth--;
+ assert_throws_dom("InvalidStateError", function() { walker.nextNode() });
+}, "Recursive filters need to throw");
+
+function filterNode(node, whatToShow, filter) {
+ // "If active flag is set throw an "InvalidStateError"."
+ // Ignore active flag for these tests, we aren't calling recursively
+ // TODO Test me
+
+ // "Let n be node's nodeType attribute value minus 1."
+ var n = node.nodeType - 1;
+
+ // "If the nth bit (where 0 is the least significant bit) of whatToShow is
+ // not set, return FILTER_SKIP."
+ if (!(whatToShow & (1 << n))) {
+ return NodeFilter.FILTER_SKIP;
+ }
+
+ // "If filter is null, return FILTER_ACCEPT."
+ if (!filter) {
+ return NodeFilter.FILTER_ACCEPT;
+ }
+
+ // "Set the active flag."
+ //
+ // "Let result be the return value of invoking filter."
+ //
+ // "Unset the active flag."
+ //
+ // "If an exception was thrown, re-throw the exception."
+ // TODO Test me
+ //
+ // "Return result."
+ return filter(node);
+}
+
+function testTraverseChildren(type, walker, root, whatToShow, filter) {
+ // TODO We don't test .currentNode other than the root
+ walker.currentNode = root;
+ assert_equals(walker.currentNode, root, "Setting .currentNode");
+
+ var expectedReturn = null;
+ var expectedCurrentNode = root;
+
+ // "To traverse children of type type, run these steps:
+ //
+ // "Let node be the value of the currentNode attribute."
+ var node = walker.currentNode;
+
+ // "Set node to node's first child if type is first, and node's last child
+ // if type is last."
+ node = type == "first" ? node.firstChild : node.lastChild;
+
+ // "Main: While node is not null, run these substeps:"
+ while (node) {
+ // "Filter node and let result be the return value."
+ var result = filterNode(node, whatToShow, filter);
+
+ // "If result is FILTER_ACCEPT, then set the currentNode attribute to
+ // node and return node."
+ if (result == NodeFilter.FILTER_ACCEPT) {
+ expectedCurrentNode = expectedReturn = node;
+ break;
+ }
+
+ // "If result is FILTER_SKIP, run these subsubsteps:"
+ if (result == NodeFilter.FILTER_SKIP) {
+ // "Let child be node's first child if type is first, and node's
+ // last child if type is last."
+ var child = type == "first" ? node.firstChild : node.lastChild;
+
+ // "If child is not null, set node to child and goto Main."
+ if (child) {
+ node = child;
+ continue;
+ }
+ }
+
+ // "While node is not null, run these subsubsteps:"
+ while (node) {
+ // "Let sibling be node's next sibling if type is first, and node's
+ // previous sibling if type is last."
+ var sibling = type == "first" ? node.nextSibling
+ : node.previousSibling;
+
+ // "If sibling is not null, set node to sibling and goto Main."
+ if (sibling) {
+ node = sibling;
+ break;
+ }
+
+ // "Let parent be node's parent."
+ var parent = node.parentNode;
+
+ // "If parent is null, parent is root, or parent is currentNode
+ // attribute's value, return null."
+ if (!parent || parent == root || parent == walker.currentNode) {
+ expectedReturn = node = null;
+ break;
+ } else {
+ // "Otherwise, set node to parent."
+ node = parent;
+ }
+ }
+ }
+
+ if (type == "first") {
+ assert_equals(walker.firstChild(), expectedReturn, ".firstChild()");
+ assert_equals(walker.currentNode, expectedCurrentNode,
+ ".currentNode after .firstChild()");
+ } else {
+ assert_equals(walker.lastChild(), expectedReturn, ".lastChild()");
+ assert_equals(walker.currentNode, expectedCurrentNode,
+ ".currentNode after .lastChild()");
+ }
+}
+
+function testTraverseSiblings(type, walker, root, whatToShow, filter) {
+ // TODO We don't test .currentNode other than the root's first or last child
+ if (!root.firstChild) {
+ // Nothing much to test
+
+ walker.currentNode = root;
+ assert_equals(walker.currentNode, root, "Setting .currentNode");
+
+ if (type == "next") {
+ assert_equals(walker.nextSibling(), null, ".nextSibling()");
+ assert_equals(walker.currentNode, root,
+ ".currentNode after .nextSibling()")
+ } else {
+ assert_equals(walker.previousSibling(), null, ".previousSibling()");
+ assert_equals(walker.currentNode, root,
+ ".currentNode after .previousSibling()")
+ }
+ return;
+ }
+
+ if (type == "next") {
+ walker.currentNode = root.firstChild;
+ assert_equals(walker.currentNode, root.firstChild,
+ "Setting .currentNode");
+ } else {
+ walker.currentNode = root.lastChild;
+ assert_equals(walker.currentNode, root.lastChild,
+ "Setting .currentNode");
+ }
+
+ var expectedReturn = null;
+ var expectedCurrentNode = type == "next" ? root.firstChild : root.lastChild;
+
+ // "To traverse siblings of type type run these steps:"
+ (function() {
+ // "Let node be the value of the currentNode attribute."
+ var node = type == "next" ? root.firstChild : root.lastChild;
+
+ // "If node is root, return null.
+ //
+ // "Run these substeps:
+ do {
+ // "Let sibling be node's next sibling if type is next, and node's
+ // previous sibling if type is previous."
+ var sibling = type == "next" ? node.nextSibling :
+ node.previousSibling;
+
+ // "While sibling is not null, run these subsubsteps:"
+ while (sibling) {
+ // "Set node to sibling."
+ node = sibling;
+
+ // "Filter node and let result be the return value."
+ var result = filterNode(node, whatToShow, filter);
+
+ // "If result is FILTER_ACCEPT, then set the currentNode
+ // attribute to node and return node."
+ if (result == NodeFilter.FILTER_ACCEPT) {
+ expectedCurrentNode = expectedReturn = node;
+ return;
+ }
+
+ // "Set sibling to node's first child if type is next, and
+ // node's last child if type is previous."
+ sibling = type == "next" ? node.firstChild : node.lastChild;
+
+ // "If result is FILTER_REJECT or sibling is null, then set
+ // sibling to node's next sibling if type is next, and node's
+ // previous sibling if type is previous."
+ if (result == NodeFilter.FILTER_REJECT || !sibling) {
+ sibling = type == "next" ? node.nextSibling :
+ node.previousSibling;
+ }
+ }
+
+ // "Set node to its parent."
+ node = node.parentNode;
+
+ // "If node is null or is root, return null.
+ if (!node || node == root) {
+ return;
+ }
+ // "Filter node and if the return value is FILTER_ACCEPT, then
+ // return null."
+ if (filterNode(node, whatToShow, filter)) {
+ return;
+ }
+
+ // "Run these substeps again."
+ } while (true);
+ })();
+
+ if (type == "next") {
+ assert_equals(walker.nextSibling(), expectedReturn, ".nextSibling()");
+ assert_equals(walker.currentNode, expectedCurrentNode,
+ ".currentNode after .nextSibling()");
+ } else {
+ assert_equals(walker.previousSibling(), expectedReturn, ".previousSibling()");
+ assert_equals(walker.currentNode, expectedCurrentNode,
+ ".currentNode after .previousSibling()");
+ }
+}
+
+function testWalker(root, whatToShow, filter) {
+ var walker = document.createTreeWalker(root, whatToShow, filter);
+
+ assert_equals(walker.root, root, ".root");
+ assert_equals(walker.whatToShow, whatToShow, ".whatToShow");
+ assert_equals(walker.filter, filter, ".filter");
+ assert_equals(walker.currentNode, root, ".currentNode");
+
+ var expectedReturn = null;
+ var expectedCurrentNode = walker.currentNode;
+ // "The parentNode() method must run these steps:"
+ //
+ // "Let node be the value of the currentNode attribute."
+ var node = walker.currentNode;
+
+ // "While node is not null and is not root, run these substeps:"
+ while (node && node != root) {
+ // "Let node be node's parent."
+ node = node.parentNode;
+
+ // "If node is not null and filtering node returns FILTER_ACCEPT, then
+ // set the currentNode attribute to node, return node."
+ if (node && filterNode(node, whatToShow, filter) ==
+ NodeFilter.FILTER_ACCEPT) {
+ expectedCurrentNode = expectedReturn = node;
+ }
+ }
+ assert_equals(walker.parentNode(), expectedReturn, ".parentNode()");
+ assert_equals(walker.currentNode, expectedCurrentNode,
+ ".currentNode after .parentNode()");
+
+ testTraverseChildren("first", walker, root, whatToShow, filter);
+ testTraverseChildren("last", walker, root, whatToShow, filter);
+
+ testTraverseSiblings("next", walker, root, whatToShow, filter);
+ testTraverseSiblings("previous", walker, root, whatToShow, filter);
+}
+
+var whatToShows = [
+ "0",
+ "0xFFFFFFFF",
+ "NodeFilter.SHOW_ELEMENT",
+ "NodeFilter.SHOW_ATTRIBUTE",
+ "NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_DOCUMENT",
+];
+
+var callbacks = [
+ "null",
+ "(function(node) { return true })",
+ "(function(node) { return false })",
+ "(function(node) { return node.nodeName[0] == '#' })",
+];
+
+var tests = [];
+for (var i = 0; i < testNodes.length; i++) {
+ for (var j = 0; j < whatToShows.length; j++) {
+ for (var k = 0; k < callbacks.length; k++) {
+ tests.push([
+ "document.createTreeWalker(" + testNodes[i] +
+ ", " + whatToShows[j] + ", " + callbacks[k] + ")",
+ eval(testNodes[i]), eval(whatToShows[j]), eval(callbacks[k])
+ ]);
+ }
+ }
+}
+generate_tests(testWalker, tests);
+
+testDiv.style.display = "none";
+</script>
diff --git a/testing/web-platform/tests/dom/traversal/support/TreeWalker-acceptNode-filter-cross-realm-null-browsing-context-subframe.html b/testing/web-platform/tests/dom/traversal/support/TreeWalker-acceptNode-filter-cross-realm-null-browsing-context-subframe.html
new file mode 100644
index 0000000000..f5e393d0f0
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/support/TreeWalker-acceptNode-filter-cross-realm-null-browsing-context-subframe.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+
+<script>
+function createNodeIterator() {
+ function filter() {
+ nodeIterator.dummyFilterCalled = true;
+ return true;
+ }
+ const nodeIterator = parent.document.createNodeIterator(parent.document.body, NodeFilter.SHOW_ELEMENT, filter);
+ nodeIterator.dummyFilterCalled = false;
+ return nodeIterator;
+}
+</script>
diff --git a/testing/web-platform/tests/dom/traversal/support/assert-node.js b/testing/web-platform/tests/dom/traversal/support/assert-node.js
new file mode 100644
index 0000000000..0d5d8ad74f
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/support/assert-node.js
@@ -0,0 +1,10 @@
+// |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.nodeType + ', expected = ' + expected.nodeType);
+ if (typeof(expected.id) !== 'undefined')
+ assert_equals(actual.id, expected.id);
+ if (typeof(expected.nodeValue) !== 'undefined')
+ assert_equals(actual.nodeValue, expected.nodeValue);
+}
diff --git a/testing/web-platform/tests/dom/traversal/support/empty-document.html b/testing/web-platform/tests/dom/traversal/support/empty-document.html
new file mode 100644
index 0000000000..b9cd130a07
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/support/empty-document.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<body>
diff --git a/testing/web-platform/tests/dom/traversal/unfinished/001.xml b/testing/web-platform/tests/dom/traversal/unfinished/001.xml
new file mode 100644
index 0000000000..08bce72fcf
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/unfinished/001.xml
@@ -0,0 +1,53 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>DOM Traversal: NodeIterator: Basics</title>
+ <script type="text/javascript"> <![CDATA[
+ function doTest() {
+ var iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, null, false);
+ var expected = new Array(9, // document
+ 1, // html
+ 3, 1, // head
+ 3, 1, 3, // title
+ 3, 1, 3, 4, // script and CDATA block
+ 3, 3, 1, // body
+ 3, 1, 3, // pre
+ 3, // </body>
+ 3, 8, // <!-- -->
+ 3, 7, // <? ?>,
+ 3, 4, 3); // CDATA
+ var found = new Array();
+
+ // walk document
+ var node;
+ while (node = iterator.nextNode())
+ found.push(node.nodeType);
+
+ // check results
+ var errors = 0;
+ var s = '';
+ var length = (found.length > expected.length) ? found.length : expected.length;
+ s += 'EXPECTED FOUND\n';
+ for (var i = 0; i < length; i += 1) {
+ s += ' ' + (expected[i] ? expected[i] : '-') +
+ ' ' + (found[i] ? found[i] : '-');
+ if (found[i] != expected[i]) {
+ s += ' MISMATCH';
+ errors += 1;
+ }
+ s += '\n';
+ }
+ var p = document.getElementsByTagNameNS('http://www.w3.org/1999/xhtml', 'pre')[0];
+ if (errors)
+ p.firstChild.data = 'FAIL: ' + errors + ' errors found:\n\n' + s;
+ else
+ p.firstChild.data = 'PASS';
+ }
+ ]]></script>
+ </head>
+ <body onload="doTest()">
+ <pre id="result">FAIL: Script failed to run.</pre>
+ </body>
+ <!-- some more nodes to test this: -->
+ <?test node?>
+ <![CDATA[]]>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/traversal/unfinished/002.xml b/testing/web-platform/tests/dom/traversal/unfinished/002.xml
new file mode 100644
index 0000000000..bf3489688c
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/unfinished/002.xml
@@ -0,0 +1,54 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>DOM Traversal: NodeIterator: Basics Backwards</title>
+ <script type="text/javascript"> <![CDATA[
+ function doTest() {
+ var iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, null, false);
+ var expected = new Array(9, // document
+ 1, // html
+ 3, 1, // head
+ 3, 1, 3, // title
+ 3, 1, 3, 4, // script and CDATA block
+ 3, 3, 1, // body
+ 3, 1, 3, // pre
+ 3, // </body>
+ 3, 8, // <!-- -->
+ 3, 7, // <? ?>,
+ 3, 4, 3); // CDATA
+ var found = new Array();
+
+ // walk document
+ var node;
+ while (node = iterator.nextNode());
+ while (node = iterator.previousNode())
+ found.unshift(node.nodeType);
+
+ // check results
+ var errors = 0;
+ var s = '';
+ var length = (found.length > expected.length) ? found.length : expected.length;
+ s += 'EXPECTED FOUND\n';
+ for (var i = 0; i < length; i += 1) {
+ s += ' ' + (expected[i] ? expected[i] : '-') +
+ ' ' + (found[i] ? found[i] : '-');
+ if (found[i] != expected[i]) {
+ s += ' MISMATCH';
+ errors += 1;
+ }
+ s += '\n';
+ }
+ var p = document.getElementsByTagNameNS('http://www.w3.org/1999/xhtml', 'pre')[0];
+ if (errors)
+ p.firstChild.data = 'FAIL: ' + errors + ' errors found:\n\n' + s;
+ else
+ p.firstChild.data = 'PASS';
+ }
+ ]]></script>
+ </head>
+ <body onload="doTest()">
+ <pre id="result">FAIL: Script failed to run.</pre>
+ </body>
+ <!-- some more nodes to test this: -->
+ <?test node?>
+ <![CDATA[]]>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/traversal/unfinished/003.xml b/testing/web-platform/tests/dom/traversal/unfinished/003.xml
new file mode 100644
index 0000000000..268e6bb4d7
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/unfinished/003.xml
@@ -0,0 +1,58 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>DOM Traversal: NodeIterator: Removal of nodes that should have no effect</title>
+ <!--
+ This tests these cases that should have no effect:
+ 1. Remove a node unrelated to the reference node
+ 2. Remove an ancestor of the root node
+ 3. Remove the root node itself
+ 4. Remove descendant of reference node
+ -->
+ <script type="text/javascript"> <![CDATA[
+ var errors = 0;
+ var log = '';
+ function doTest() {
+ var iterator = document.createNodeIterator(document.getElementById('root'), NodeFilter.SHOW_ALL, null, false);
+ var root = document.getElementById('root');
+ var A = document.getElementById('A');
+ var B = document.getElementById('B');
+ var C = document.getElementById('C');
+ var D = document.getElementById('D');
+ var E = document.getElementById('E');
+ check(iterator.nextNode(), root);
+ remove(document.getElementById('X'));
+ check(iterator.nextNode(), A);
+ remove(document.getElementById('Y'));
+ check(iterator.nextNode(), B);
+ remove(root);
+ check(iterator.nextNode(), C);
+ remove(E);
+ check(iterator.nextNode(), D);
+ if (errors)
+ document.getElementById('result').firstChild.data = 'FAIL: ' + errors + ' errors:\n' + log;
+ else
+ document.getElementById('result').firstChild.data = 'PASS';
+ }
+ function check(a, b) {
+ if (!a) {
+ errors += 1;
+ log += 'Found null but expected ' + b + ' (' + b.id + ').\n';
+ } else if (a != b) {
+ errors += 1;
+ log += 'Found ' + a + ' (' + a.id + ') but expected ' + b + ' (' + b.id + ').\n';
+ }
+ }
+ function remove(a) {
+ if (!a) {
+ errors += 1;
+ log += 'Tried removing null node.\n';
+ } else
+ a.parentNode.removeChild(a);
+ }
+ ]]></script>
+ </head>
+ <body onload="doTest()">
+ <pre id="result">FAIL: Script did not complete.</pre>
+ <p><span id="X"></span><span id="Y"><span id="root"><span id="A"><span id="B"><span id="C"><span id="D"><span id="E"></span></span></span></span></span></span></span></p>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/traversal/unfinished/004.xml b/testing/web-platform/tests/dom/traversal/unfinished/004.xml
new file mode 100644
index 0000000000..618978f021
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/unfinished/004.xml
@@ -0,0 +1,49 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>DOM Traversal: NodeIterator: Removal of the Reference Node</title>
+ <script type="text/javascript"> <![CDATA[
+ var errors = 0;
+ var log = '';
+ function doTest() {
+ var iterator = document.createNodeIterator(document.getElementById('root'), NodeFilter.SHOW_ALL, null, false);
+ var root = document.getElementById('root');
+ var A = document.getElementById('A');
+ var AA = document.getElementById('AA');
+ var B = document.getElementById('B');
+ var C = document.getElementById('C');
+ check(iterator.nextNode(), root);
+ check(iterator.nextNode(), A);
+ check(iterator.nextNode(), AA);
+ check(iterator.nextNode(), B);
+ remove(B);
+ check(iterator.previousNode(), AA);
+ remove(AA);
+ check(iterator.nextNode(), C);
+ if (errors)
+ document.getElementById('result').firstChild.data = 'FAIL: ' + errors + ' errors:\n' + log;
+ else
+ document.getElementById('result').firstChild.data = 'PASS';
+ }
+ function check(a, b) {
+ if (!a) {
+ errors += 1;
+ log += 'Found null but expected ' + b + ' (' + b.id + ').\n';
+ } else if (a != b) {
+ errors += 1;
+ log += 'Found ' + a + ' (' + a.id + ') but expected ' + b + ' (' + b.id + ').\n';
+ }
+ }
+ function remove(a) {
+ if (!a) {
+ errors += 1;
+ log += 'Tried removing null node.\n';
+ } else
+ a.parentNode.removeChild(a);
+ }
+ ]]></script>
+ </head>
+ <body onload="doTest()">
+ <pre id="result">FAIL: Script did not complete.</pre>
+ <p><span id="root"><span id="A"><span id="AA"></span></span><span id="B"></span><span id="C"><span id="CC"></span></span></span></p>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/traversal/unfinished/005.xml b/testing/web-platform/tests/dom/traversal/unfinished/005.xml
new file mode 100644
index 0000000000..643e2f1cd4
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/unfinished/005.xml
@@ -0,0 +1,57 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>DOM Traversal: NodeIterator: Removal of the Reference Node (deep check)</title>
+ <script type="text/javascript"> <![CDATA[
+ var errors = 0;
+ var log = '';
+ function doTest() {
+ var iterator = document.createNodeIterator(document.getElementById('root'), NodeFilter.SHOW_ALL, null, false);
+ var root = document.getElementById('root');
+ var A = document.getElementById('A');
+ var AA = document.getElementById('AA');
+ var B = document.getElementById('B');
+ var C = document.getElementById('C');
+ check(iterator.nextNode(), root);
+ check(iterator.nextNode(), A);
+ check(iterator.nextNode(), AA);
+ check(iterator.nextNode(), B);
+ remove(B);
+ var X = addChildTo(AA);
+ check(iterator.nextNode(), X);
+ check(iterator.previousNode(), X);
+ remove(X);
+ var Y = addChildTo(AA);
+ check(iterator.previousNode(), Y);
+ if (errors)
+ document.getElementById('result').firstChild.data = 'FAIL: ' + errors + ' errors:\n' + log;
+ else
+ document.getElementById('result').firstChild.data = 'PASS';
+ }
+ function check(a, b) {
+ if (!a) {
+ errors += 1;
+ log += 'Found null but expected ' + b + ' (' + b.id + ').\n';
+ } else if (a != b) {
+ errors += 1;
+ log += 'Found ' + a + ' (' + a.id + ') but expected ' + b + ' (' + b.id + ').\n';
+ }
+ }
+ function remove(a) {
+ if (!a) {
+ errors += 1;
+ log += 'Tried removing null node.\n';
+ } else
+ a.parentNode.removeChild(a);
+ }
+ function addChildTo(a) {
+ var x = document.createElementNS('http://www.w3.org/1999/xhtml', 'span');
+ a.appendChild(x);
+ return x;
+ }
+ ]]></script>
+ </head>
+ <body onload="doTest()">
+ <pre id="result">FAIL: Script did not complete.</pre>
+ <p><span id="root"><span id="A"><span id="AA"></span></span><span id="B"></span><span id="C"><span id="CC"></span></span></span></p>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/traversal/unfinished/006.xml b/testing/web-platform/tests/dom/traversal/unfinished/006.xml
new file mode 100644
index 0000000000..c2302af836
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/unfinished/006.xml
@@ -0,0 +1,47 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>DOM Traversal: NodeIterator: Removal of an ancestor of the Reference Node (forwards)</title>
+ <script type="text/javascript"> <![CDATA[
+ var errors = 0;
+ var log = '';
+ function doTest() {
+ var iterator = document.createNodeIterator(document.getElementById('root'), NodeFilter.SHOW_ALL, null, false);
+ var root = document.getElementById('root');
+ var A = document.getElementById('A');
+ var B = document.getElementById('B');
+ var BB = document.getElementById('BB');
+ var C = document.getElementById('C');
+ check(iterator.nextNode(), root);
+ check(iterator.nextNode(), A);
+ check(iterator.nextNode(), B);
+ check(iterator.nextNode(), BB);
+ remove(B);
+ check(iterator.previousNode(), A);
+ if (errors)
+ document.getElementById('result').firstChild.data = 'FAIL: ' + errors + ' errors:\n' + log;
+ else
+ document.getElementById('result').firstChild.data = 'PASS';
+ }
+ function check(a, b) {
+ if (!a) {
+ errors += 1;
+ log += 'Found null but expected ' + b + ' (' + b.id + ').\n';
+ } else if (a != b) {
+ errors += 1;
+ log += 'Found ' + a + ' (' + a.id + ') but expected ' + b + ' (' + b.id + ').\n';
+ }
+ }
+ function remove(a) {
+ if (!a) {
+ errors += 1;
+ log += 'Tried removing null node.\n';
+ } else
+ a.parentNode.removeChild(a);
+ }
+ ]]></script>
+ </head>
+ <body onload="doTest()">
+ <pre id="result">FAIL: Script did not complete.</pre>
+ <p><span id="root"><span id="A"></span><span id="B"><span id="BB"></span></span><span id="C"></span></span></p>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/traversal/unfinished/007.xml b/testing/web-platform/tests/dom/traversal/unfinished/007.xml
new file mode 100644
index 0000000000..98b212e4e5
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/unfinished/007.xml
@@ -0,0 +1,54 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>DOM Traversal: NodeIterator: Removal of an ancestor of the Reference Node (forwards) (deep check)</title>
+ <script type="text/javascript"> <![CDATA[
+ var errors = 0;
+ var log = '';
+ function doTest() {
+ var iterator = document.createNodeIterator(document.getElementById('root'), NodeFilter.SHOW_ALL, null, false);
+ var root = document.getElementById('root');
+ var A = document.getElementById('A');
+ var B = document.getElementById('B');
+ var BB = document.getElementById('BB');
+ var C = document.getElementById('C');
+ check(iterator.nextNode(), root);
+ check(iterator.nextNode(), A);
+ check(iterator.nextNode(), B);
+ check(iterator.nextNode(), BB);
+ remove(B);
+ var X = addChildTo(A);
+ check(iterator.nextNode(), X);
+ if (errors)
+ document.getElementById('result').firstChild.data = 'FAIL: ' + errors + ' errors:\n' + log;
+ else
+ document.getElementById('result').firstChild.data = 'PASS';
+ }
+ function check(a, b) {
+ if (!a) {
+ errors += 1;
+ log += 'Found null but expected ' + b + ' (' + b.id + ').\n';
+ } else if (a != b) {
+ errors += 1;
+ log += 'Found ' + a + ' (' + a.id + ') but expected ' + b + ' (' + b.id + ').\n';
+ }
+ }
+ function remove(a) {
+ if (!a) {
+ errors += 1;
+ log += 'Tried removing null node.\n';
+ } else
+ a.parentNode.removeChild(a);
+ }
+ function addChildTo(a) {
+ var x = document.createElementNS('http://www.w3.org/1999/xhtml', 'span');
+ x.id = 'X';
+ a.appendChild(x);
+ return x;
+ }
+ ]]></script>
+ </head>
+ <body onload="doTest()">
+ <pre id="result">FAIL: Script did not complete.</pre>
+ <p><span id="root"><span id="A"></span><span id="B"><span id="BB"></span></span><span id="C"></span></span></p>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/traversal/unfinished/008.xml b/testing/web-platform/tests/dom/traversal/unfinished/008.xml
new file mode 100644
index 0000000000..41d7008ae4
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/unfinished/008.xml
@@ -0,0 +1,48 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>DOM Traversal: NodeIterator: Removal of an ancestor of the Reference Node (backwards)</title>
+ <script type="text/javascript"> <![CDATA[
+ var errors = 0;
+ var log = '';
+ function doTest() {
+ var iterator = document.createNodeIterator(document.getElementById('root'), NodeFilter.SHOW_ALL, null, false);
+ var root = document.getElementById('root');
+ var A = document.getElementById('A');
+ var B = document.getElementById('B');
+ var BB = document.getElementById('BB');
+ var C = document.getElementById('C');
+ check(iterator.nextNode(), root);
+ check(iterator.nextNode(), A);
+ check(iterator.nextNode(), B);
+ check(iterator.nextNode(), BB);
+ check(iterator.previousNode(), BB);
+ remove(B);
+ check(iterator.nextNode(), C);
+ if (errors)
+ document.getElementById('result').firstChild.data = 'FAIL: ' + errors + ' errors:\n' + log;
+ else
+ document.getElementById('result').firstChild.data = 'PASS';
+ }
+ function check(a, b) {
+ if (!a) {
+ errors += 1;
+ log += 'Found null but expected ' + b + ' (' + b.id + ').\n';
+ } else if (a != b) {
+ errors += 1;
+ log += 'Found ' + a + ' (' + a.id + ') but expected ' + b + ' (' + b.id + ').\n';
+ }
+ }
+ function remove(a) {
+ if (!a) {
+ errors += 1;
+ log += 'Tried removing null node.\n';
+ } else
+ a.parentNode.removeChild(a);
+ }
+ ]]></script>
+ </head>
+ <body onload="doTest()">
+ <pre id="result">FAIL: Script did not complete.</pre>
+ <p><span id="root"><span id="A"></span><span id="B"><span id="BB"></span></span><span id="C"></span></span></p>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/traversal/unfinished/009.xml b/testing/web-platform/tests/dom/traversal/unfinished/009.xml
new file mode 100644
index 0000000000..c3006ecbd6
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/unfinished/009.xml
@@ -0,0 +1,55 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>DOM Traversal: NodeIterator: Removal of an ancestor of the Reference Node (backwards) (deep check)</title>
+ <script type="text/javascript"> <![CDATA[
+ var errors = 0;
+ var log = '';
+ function doTest() {
+ var iterator = document.createNodeIterator(document.getElementById('root'), NodeFilter.SHOW_ALL, null, false);
+ var root = document.getElementById('root');
+ var A = document.getElementById('A');
+ var B = document.getElementById('B');
+ var BB = document.getElementById('BB');
+ var C = document.getElementById('C');
+ check(iterator.nextNode(), root);
+ check(iterator.nextNode(), A);
+ check(iterator.nextNode(), B);
+ check(iterator.nextNode(), BB);
+ check(iterator.previousNode(), BB);
+ remove(B);
+ var X = addChildTo(A);
+ check(iterator.previousNode(), X);
+ if (errors)
+ document.getElementById('result').firstChild.data = 'FAIL: ' + errors + ' errors:\n' + log;
+ else
+ document.getElementById('result').firstChild.data = 'PASS';
+ }
+ function check(a, b) {
+ if (!a) {
+ errors += 1;
+ log += 'Found null but expected ' + b + ' (' + b.id + ').\n';
+ } else if (a != b) {
+ errors += 1;
+ log += 'Found ' + a + ' (' + a.id + ') but expected ' + b + ' (' + b.id + ').\n';
+ }
+ }
+ function remove(a) {
+ if (!a) {
+ errors += 1;
+ log += 'Tried removing null node.\n';
+ } else
+ a.parentNode.removeChild(a);
+ }
+ function addChildTo(a) {
+ var x = document.createElementNS('http://www.w3.org/1999/xhtml', 'span');
+ x.id = 'X';
+ a.appendChild(x);
+ return x;
+ }
+ ]]></script>
+ </head>
+ <body onload="doTest()">
+ <pre id="result">FAIL: Script did not complete.</pre>
+ <p><span id="root"><span id="A"></span><span id="B"><span id="BB"></span></span><span id="C"></span></span></p>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/dom/traversal/unfinished/010.xml b/testing/web-platform/tests/dom/traversal/unfinished/010.xml
new file mode 100644
index 0000000000..63263a5fd7
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/unfinished/010.xml
@@ -0,0 +1,64 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>DOM Traversal: NodeIterator: Filters</title>
+ <script type="text/javascript"> <![CDATA[
+ function doTest() {
+ var iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, testFilter, false);
+ // skips text nodes and body element
+ var expected = new Array(9, // document
+ 1, // html
+ 1, // head
+ 1, // title
+ 1, 4, // script and CDATA block
+ // body (skipped)
+ 1, // pre
+ // </body>
+ 8, // <!-- -->
+ // PI skipped
+ 4); // CDATA
+ var found = new Array();
+
+ // walk document
+ var node;
+ while (node = iterator.nextNode())
+ found.push(node.nodeType);
+
+ // check results
+ var errors = 0;
+ var s = '';
+ var length = (found.length > expected.length) ? found.length : expected.length;
+ s += 'EXPECTED FOUND\n';
+ for (var i = 0; i < length; i += 1) {
+ s += ' ' + (expected[i] ? expected[i] : '-') +
+ ' ' + (found[i] ? found[i] : '-');
+ if (found[i] != expected[i]) {
+ s += ' MISMATCH';
+ errors += 1;
+ }
+ s += '\n';
+ }
+ var p = document.getElementsByTagNameNS('http://www.w3.org/1999/xhtml', 'pre')[0];
+ if (errors)
+ p.firstChild.data = 'FAIL: ' + errors + ' errors found:\n\n' + s;
+ else
+ p.firstChild.data = 'PASS';
+ }
+
+ function testFilter(n) {
+ if (n.nodeType == 3) {
+ return NodeFilter.FILTER_SKIP;
+ } else if (n.nodeName == 'body') {
+ return NodeFilter.FILTER_REJECT; // same as _SKIP
+ }
+ return 1; // FILTER_ACCEPT
+ }
+
+ ]]></script>
+ </head>
+ <body onload="doTest()">
+ <pre id="result">FAIL: Script failed to run.</pre>
+ </body>
+ <!-- some more nodes to test this: -->
+ <?body test?>
+ <![CDATA[]]>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/traversal/unfinished/TODO b/testing/web-platform/tests/dom/traversal/unfinished/TODO
new file mode 100644
index 0000000000..cecdf98b08
--- /dev/null
+++ b/testing/web-platform/tests/dom/traversal/unfinished/TODO
@@ -0,0 +1 @@
+Check what happens when a NodeFilter turns a number not in the range 1..3 \ No newline at end of file
diff --git a/testing/web-platform/tests/dom/window-extends-event-target.html b/testing/web-platform/tests/dom/window-extends-event-target.html
new file mode 100644
index 0000000000..3b690324e5
--- /dev/null
+++ b/testing/web-platform/tests/dom/window-extends-event-target.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Window extends EventTarget</title>
+<link rel="help" href="https://github.com/jsdom/jsdom/issues/2830">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+test(() => {
+
+ assert_equals(window.addEventListener, EventTarget.prototype.addEventListener);
+ assert_equals(window.removeEventListener, EventTarget.prototype.removeEventListener);
+ assert_equals(window.dispatchEvent, EventTarget.prototype.dispatchEvent);
+
+}, "EventTarget methods on Window instances are inherited from the EventTarget prototype");
+
+test(() => {
+
+ const kCustom = "custom-event";
+ const customEvent = new CustomEvent(kCustom, {
+ bubbles: true
+ });
+
+ let target;
+ window.addEventListener.call(document.body, kCustom, function () {
+ target = this;
+ });
+
+ document.body.dispatchEvent(customEvent);
+
+ assert_equals(target, document.body);
+
+}, "window.addEventListener respects custom `this`");
+
+test(() => {
+
+ const kCustom = "custom-event";
+ const customEvent = new CustomEvent(kCustom, {
+ bubbles: true
+ });
+
+ let target;
+ window.addEventListener.call(null, kCustom, function () {
+ target = this;
+ });
+
+ document.body.dispatchEvent(customEvent);
+
+ assert_equals(target, window);
+
+}, "window.addEventListener treats nullish `this` as `window`");
+</script>
diff --git a/testing/web-platform/tests/dom/xslt/README.md b/testing/web-platform/tests/dom/xslt/README.md
new file mode 100644
index 0000000000..ac713ce179
--- /dev/null
+++ b/testing/web-platform/tests/dom/xslt/README.md
@@ -0,0 +1,11 @@
+# XSLT
+
+This directory contains tentative tests for [XSLT](https://dom.spec.whatwg.org/#xslt).
+
+See [whatwg/dom#181](https://github.com/whatwg/dom/issues/181) for getting XSLT
+better specified.
+
+There are additional details on XSLT in HTML:
+- [Interactions with XPath and XSLT](https://html.spec.whatwg.org/multipage/infrastructure.html#interactions-with-xpath-and-xslt)
+- [Interaction of `script` elements and XSLT](https://html.spec.whatwg.org/multipage/scripting.html#scriptTagXSLT) (non-normative)
+- [Interaction of `template` elements with XSLT and XPath](https://html.spec.whatwg.org/multipage/scripting.html#template-XSLT-XPath) (non-normative)
diff --git a/testing/web-platform/tests/dom/xslt/externalScript.js b/testing/web-platform/tests/dom/xslt/externalScript.js
new file mode 100644
index 0000000000..7a2bf36225
--- /dev/null
+++ b/testing/web-platform/tests/dom/xslt/externalScript.js
@@ -0,0 +1 @@
+window.externalScript = true;
diff --git a/testing/web-platform/tests/dom/xslt/functions.tentative.window.js b/testing/web-platform/tests/dom/xslt/functions.tentative.window.js
new file mode 100644
index 0000000000..81240022dc
--- /dev/null
+++ b/testing/web-platform/tests/dom/xslt/functions.tentative.window.js
@@ -0,0 +1,252 @@
+const xslt_functions = [
+ "QName",
+ "abs",
+ "accumulator-after",
+ "accumulator-before",
+ "adjust-date-to-timezone",
+ "adjust-dateTime-to-timezone",
+ "adjust-time-to-timezone",
+ "analyze-string",
+ "apply",
+ "available-environment-variables",
+ "available-system-properties",
+ "avg",
+ "base-uri",
+ "boolean",
+ "ceiling",
+ "codepoint-equal",
+ "codepoints-to-string",
+ "collation-key",
+ "collection",
+ "compare",
+ "concat",
+ "contains",
+ "contains-token",
+ "copy-of",
+ "count",
+ "current",
+ "current-date",
+ "current-dateTime",
+ "current-group",
+ "current-grouping-key",
+ "current-merge-group",
+ "current-merge-key",
+ "current-output-uri",
+ "current-time",
+ "data",
+ "dateTime",
+ "day-from-date",
+ "day-from-dateTime",
+ "days-from-duration",
+ "deep-equal",
+ "default-collation",
+ "default-language",
+ "distinct-values",
+ "doc",
+ "doc-available",
+ "document",
+ "document-uri",
+ "element-available",
+ "element-with-id",
+ "empty",
+ "encode-for-uri",
+ "ends-with",
+ "environment-variable",
+ "error",
+ "escape-html-uri",
+ "exactly-one",
+ "exists",
+ "false",
+ "filter",
+ "floor",
+ "fold-left",
+ "fold-right",
+ "for-each",
+ "for-each-pair",
+ "format-date",
+ "format-dateTime",
+ "format-integer",
+ "format-number",
+ "format-time",
+ "function-arity",
+ "function-available",
+ "function-lookup",
+ "function-name",
+ "generate-id",
+ "has-children",
+ "head",
+ "hours-from-dateTime",
+ "hours-from-duration",
+ "hours-from-time",
+ "id",
+ "idref",
+ "implicit-timezone",
+ "in-scope-prefixes",
+ "index-of",
+ "innermost",
+ "insert-before",
+ "iri-to-uri",
+ "json-doc",
+ "json-to-xml",
+ "key",
+ "lang",
+ "last",
+ "load-xquery-module",
+ "local-name",
+ "local-name-from-QName",
+ "lower-case",
+ "matches",
+ "max",
+ "min",
+ "minutes-from-dateTime",
+ "minutes-from-duration",
+ "minutes-from-time",
+ "month-from-date",
+ "month-from-dateTime",
+ "months-from-duration",
+ "name",
+ "namespace-uri",
+ "namespace-uri-for-prefix",
+ "namespace-uri-from-QName",
+ "nilled",
+ "node-name",
+ "normalize-space",
+ "normalize-unicode",
+ "not",
+ "number",
+ "one-or-more",
+ "outermost",
+ "parse-ietf-date",
+ "parse-json",
+ "parse-xml",
+ "parse-xml-fragment",
+ "path",
+ "position",
+ "prefix-from-QName",
+ "put",
+ "random-number-generator",
+ "regex-group",
+ "remove",
+ "replace",
+ "resolve-QName",
+ "resolve-uri",
+ "reverse",
+ "root",
+ "round",
+ "round-half-to-even",
+ "seconds-from-dateTime",
+ "seconds-from-duration",
+ "seconds-from-time",
+ "serialize",
+ "snapshot",
+ "sort",
+ "starts-with",
+ "static-base-uri",
+ "stream-available",
+ "string",
+ "string-join",
+ "string-length",
+ "string-to-codepoints",
+ "subsequence",
+ "substring",
+ "substring-after",
+ "substring-before",
+ "sum",
+ "system-property",
+ "tail",
+ "timezone-from-date",
+ "timezone-from-dateTime",
+ "timezone-from-time",
+ "tokenize",
+ "trace",
+ "transform",
+ "translate",
+ "true",
+ "type-available",
+ "unordered",
+ "unparsed-entity-public-id",
+ "unparsed-entity-uri",
+ "unparsed-text",
+ "unparsed-text-available",
+ "unparsed-text-lines",
+ "upper-case",
+ "uri-collection",
+ "xml-to-json",
+ "year-from-date",
+ "year-from-dateTime",
+ "years-from-duration",
+ "zero-or-one",
+];
+
+const xslt1_functions = [
+ "boolean",
+ "ceiling",
+ "concat",
+ "contains",
+ "count",
+ "current",
+ "document",
+ "element-available",
+ "false",
+ "floor",
+ "format-number",
+ "function-available",
+ "generate-id",
+ "id",
+ "key",
+ "lang",
+ "last",
+ "local-name",
+ "name",
+ "namespace-uri",
+ "normalize-space",
+ "not",
+ "number",
+ "position",
+ "round",
+ "starts-with",
+ "string",
+ "string-length",
+ "substring",
+ "substring-after",
+ "substring-before",
+ "sum",
+ "system-property",
+ "translate",
+ "true",
+ "unparsed-entity-uri",
+];
+
+for (const fn of xslt_functions) {
+ test(() => {
+ const xsltSrc = `<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="html" encoding="utf-8" version="5"/>
+<xsl:template match="/">
+ <xsl:choose>
+ <xsl:when test='function-available("${fn}")'>
+ <div>available</div>
+ </xsl:when>
+ <xsl:otherwise>
+ <div>unavailable</div>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+</xsl:stylesheet>`;
+
+ const processor = new XSLTProcessor();
+ const parser = new DOMParser();
+ processor.importStylesheet(
+ parser.parseFromString(xsltSrc, "application/xml")
+ );
+ const output = processor.transformToFragment(
+ parser.parseFromString("<x/>", "application/xml"),
+ document
+ );
+
+ // Note we only expect functions defined in XSLT 1.0 to be defined.
+ assert_equals(
+ output.textContent.trim(),
+ xslt1_functions.indexOf(fn) >= 0 ? "available" : "unavailable"
+ );
+ }, fn);
+}
diff --git a/testing/web-platform/tests/dom/xslt/invalid-output-encoding-crash.html b/testing/web-platform/tests/dom/xslt/invalid-output-encoding-crash.html
new file mode 100644
index 0000000000..d84bb5b3c3
--- /dev/null
+++ b/testing/web-platform/tests/dom/xslt/invalid-output-encoding-crash.html
@@ -0,0 +1,26 @@
+<body>
+
+<script id=o_xml type="text/plain">
+ <?xml version="1.0" encoding="UTF-8"?>
+</script>
+
+<script id=o_xslt type="text/plain"><?xml version="1.0" encoding="UTF-8"?>
+ <xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output indent="no" omit-xml-declaration="no" encoding="bad-encoding" standalone="yes" />
+ </xsl:transform>
+</script>
+
+<script>
+addEventListener("load", function() {
+ const doc = new DOMParser();
+ const xml = doc.parseFromString(o_xml.textContent, "text/xml");
+ const xsl = doc.parseFromString(o_xslt.textContent, "text/xml");
+ const xsltPrs = new XSLTProcessor();
+ xsltPrs.importStylesheet(xsl);
+ xsltPrs.transformToDocument(xml);
+
+ document.body.appendChild(document.createTextNode("PASS: renderer didn't crash"));
+});
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/dom/xslt/sort-ref.html b/testing/web-platform/tests/dom/xslt/sort-ref.html
new file mode 100644
index 0000000000..163002d0d0
--- /dev/null
+++ b/testing/web-platform/tests/dom/xslt/sort-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1399858">
+
+<div>
+ <div>1</div>
+ <div>2</div>
+ <div>3</div>
+ <div>7</div>
+</div>
diff --git a/testing/web-platform/tests/dom/xslt/sort.html b/testing/web-platform/tests/dom/xslt/sort.html
new file mode 100644
index 0000000000..631c3edd6a
--- /dev/null
+++ b/testing/web-platform/tests/dom/xslt/sort.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<link rel=match href="sort-ref.html">
+
+<body>
+ <div id="container"></div>
+</body>
+
+<script type="text/xml" id="sampleXml">
+ <root>
+ <node id="1" />
+ <node id="7" />
+ <node id="3" />
+ <node id="2" />
+ </root>
+</script>
+
+<script type="text/xml" id="sampleXsl">
+ <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
+
+ <xsl:template match="/">
+ <xsl:apply-templates select="//node">
+ <xsl:sort select="@id" data-type="number" />
+ </xsl:apply-templates>
+ </xsl:template>
+
+ <xsl:template match="node">
+ <div>
+ <xsl:value-of select="@id"/>
+ </div>
+ </xsl:template>
+
+ </xsl:stylesheet>
+</script>
+
+<script>
+ let parser = new DOMParser();
+ const xslStyleSheet = parser.parseFromString(document.getElementById('sampleXsl').textContent, 'text/xml');
+
+ const xsltProcessor = new XSLTProcessor();
+ xsltProcessor.importStylesheet(xslStyleSheet);
+
+ parser = new DOMParser();
+ const xmlDoc = parser.parseFromString(document.getElementById('sampleXml').textContent, 'text/xml');
+
+ const fragment = xsltProcessor.transformToFragment(xmlDoc, document);
+
+ document.getElementById('container').appendChild(fragment);
+</script>
diff --git a/testing/web-platform/tests/dom/xslt/strip-space-crash.xml b/testing/web-platform/tests/dom/xslt/strip-space-crash.xml
new file mode 100644
index 0000000000..61a906a5e7
--- /dev/null
+++ b/testing/web-platform/tests/dom/xslt/strip-space-crash.xml
@@ -0,0 +1,33 @@
+<?xml-stylesheet type="text/xsl" href="#style"?>
+<xsl:stylesheet
+ version="1.0"
+ xml:id="style"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ extension-element-prefixes="exsl"
+>
+ <xsl:strip-space elements="s"/>
+
+ <xsl:template match="/">
+ <xsl:variable name="space">
+ <s>
+ <xsl:text> </xsl:text>
+ <e/>
+ <xsl:text> </xsl:text>
+ <e/>
+ <xsl:text> </xsl:text>
+ </s>
+ </xsl:variable>
+ <xsl:apply-templates select="exsl:node-set($space)/s"/>
+ </xsl:template>
+
+ <xsl:template match="s">
+ <r>
+ <xsl:variable name="text-nodes" select="text()"/>
+ <xsl:apply-templates/>
+ <xsl:copy-of select="$text-nodes"/>
+ </r>
+ </xsl:template>
+
+ <xsl:template match="node()"/>
+</xsl:stylesheet>
diff --git a/testing/web-platform/tests/dom/xslt/transformToFragment-on-node-from-inactive-document-crash.html b/testing/web-platform/tests/dom/xslt/transformToFragment-on-node-from-inactive-document-crash.html
new file mode 100644
index 0000000000..38a62a0a9d
--- /dev/null
+++ b/testing/web-platform/tests/dom/xslt/transformToFragment-on-node-from-inactive-document-crash.html
@@ -0,0 +1,11 @@
+<body>
+<iframe id=i></iframe>
+<script>
+var el = i.contentDocument.documentElement;
+i.remove()
+var x = new XSLTProcessor();
+var xsl =new DOMParser().parseFromString('<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"/>','application/xml');
+x.importStylesheet(xsl);
+x.transformToDocument(el);
+</script>
+</body>
diff --git a/testing/web-platform/tests/dom/xslt/transformToFragment.tentative.window.js b/testing/web-platform/tests/dom/xslt/transformToFragment.tentative.window.js
new file mode 100644
index 0000000000..7bb6a56855
--- /dev/null
+++ b/testing/web-platform/tests/dom/xslt/transformToFragment.tentative.window.js
@@ -0,0 +1,39 @@
+const cases = {
+ internal: '<script>window.internalScript = true;</script>',
+ external: '<script src="externalScript.js"></script>',
+};
+
+const loaded = new Promise(resolve => {
+ window.addEventListener('load', resolve);
+});
+
+Object.entries(cases).forEach(([k, v]) => {
+ const xsltSrc = `<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="html" encoding="utf-8" version="5"/>
+<xsl:template match="/">
+ <section>
+ ${v}
+ </section>
+</xsl:template>
+</xsl:stylesheet>`;
+
+ const processor = new XSLTProcessor();
+ const parser = new DOMParser();
+ processor.importStylesheet(
+ parser.parseFromString(xsltSrc, 'application/xml')
+ );
+ document.body.appendChild(
+ processor.transformToFragment(
+ parser.parseFromString('<x/>', 'application/xml'),
+ document
+ )
+ );
+
+ promise_test(async () => {
+ await loaded;
+ assert_true(
+ window[`${k}Script`],
+ 'script element from XSLTProcessor.transformToFragment() is evaluated'
+ );
+ }, `${k} script`);
+})