summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/mozilla
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/mozilla
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/mozilla')
-rw-r--r--testing/web-platform/mozilla/README4
-rw-r--r--testing/web-platform/mozilla/meta/__dir__.ini4
-rw-r--r--testing/web-platform/mozilla/meta/audio-output/__dir__.ini1
-rw-r--r--testing/web-platform/mozilla/meta/baselinecoverage/wpt_baselinecoverage.html.ini3
-rw-r--r--testing/web-platform/mozilla/meta/css/css-contain/__dir__.ini1
-rw-r--r--testing/web-platform/mozilla/meta/css/css-overflow/scrollbar-gutter-reflow-counts-001.html.ini24
-rw-r--r--testing/web-platform/mozilla/meta/css/cssom/window_size_rounding.html.ini5
-rw-r--r--testing/web-platform/mozilla/meta/css/mediaqueries/mq-gamut-resist-fingerprinting.html.ini2
-rw-r--r--testing/web-platform/mozilla/meta/dom/delayed_window_print.html.ini9
-rw-r--r--testing/web-platform/mozilla/meta/dom/throttling/__dir__.ini5
-rw-r--r--testing/web-platform/mozilla/meta/dom/throttling/throttling-1.window.js.ini4
-rw-r--r--testing/web-platform/mozilla/meta/dom/throttling/throttling-2.window.js.ini4
-rw-r--r--testing/web-platform/mozilla/meta/dom/throttling/throttling-3.window.js.ini4
-rw-r--r--testing/web-platform/mozilla/meta/dom/throttling/throttling-4.window.js.ini4
-rw-r--r--testing/web-platform/mozilla/meta/dom/throttling/throttling-indexeddb.window.js.ini4
-rw-r--r--testing/web-platform/mozilla/meta/dom/throttling/throttling-webaudio.window.js.ini5
-rw-r--r--testing/web-platform/mozilla/meta/dom/throttling/throttling-webrtc.window.js.ini4
-rw-r--r--testing/web-platform/mozilla/meta/dom/throttling/throttling-ws.window.js.ini4
-rw-r--r--testing/web-platform/mozilla/meta/editor/delete-space-after-double-click-selection.html.ini6
-rw-r--r--testing/web-platform/mozilla/meta/fetch/api/redirect/redirect-referrer.https.html.ini3
-rw-r--r--testing/web-platform/mozilla/meta/focus/Selection_extend.html.ini20
-rw-r--r--testing/web-platform/mozilla/meta/focus/focus-next-tick-before-iframe-loaded-different-site.html.ini4
-rw-r--r--testing/web-platform/mozilla/meta/html/semantics/scripting-1/the-script-element/__dir__.ini1
-rw-r--r--testing/web-platform/mozilla/meta/html/syntax/parsing/math-parse01.html.ini3
-rw-r--r--testing/web-platform/mozilla/meta/infrastructure/prefs/prefs-1.html.ini2
-rw-r--r--testing/web-platform/mozilla/meta/mediacapture-streams/enumerateDevices-in-background.https.html.ini3
-rw-r--r--testing/web-platform/mozilla/meta/screen-capture/getdisplaymedia-user-activation-consumed.https.html.ini5
-rw-r--r--testing/web-platform/mozilla/meta/wasm/__dir__.ini1
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/__dir__.ini1
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/bidi/__dir__.ini1
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/bidi/browsing_context/create/reference_context.py.ini4
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/bidi/browsing_context/create/type_hint.py.ini9
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/bidi/interface/interface.py.ini4
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/bidi/websocket_upgrade.py.ini3
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/cdp/debugger_address.py.ini7
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/cdp/port_file.py.ini3
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/execute_async_script/execute_async.py.ini16
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/get_window_handles/chrome.py.ini4
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/new_session/__dir__.ini2
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/new_session/binary.py.ini4
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/new_session/profile_root.py.ini4
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/protocol/marionette_port.py.ini8
-rw-r--r--testing/web-platform/mozilla/meta/webdriver/send_alert_text.py.ini4
-rw-r--r--testing/web-platform/mozilla/meta/workers/__dir__.ini1
-rw-r--r--testing/web-platform/mozilla/meta/workers/bug1674278-crash.html.ini2
-rw-r--r--testing/web-platform/mozilla/tests/audio-output/selectAudioOutput-user-activation-consumed.https.html30
-rw-r--r--testing/web-platform/mozilla/tests/baselinecoverage/wpt_baselinecoverage.html22
-rw-r--r--testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-document-reflow-count.html44
-rw-r--r--testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-reflow-count.html120
-rw-r--r--testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-gutter-reflow-counts-001.html115
-rw-r--r--testing/web-platform/mozilla/tests/css/cssom/media-print-change-print-ref.html2
-rw-r--r--testing/web-platform/mozilla/tests/css/cssom/media-print-change-print.html8
-rw-r--r--testing/web-platform/mozilla/tests/css/cssom/size-change-print-notref.html5
-rw-r--r--testing/web-platform/mozilla/tests/css/cssom/size-change-print.html10
-rw-r--r--testing/web-platform/mozilla/tests/css/cssom/window_size_rounding.html35
-rw-r--r--testing/web-platform/mozilla/tests/css/file-selector-button-margin-notref.html2
-rw-r--r--testing/web-platform/mozilla/tests/css/file-selector-button-margin.html10
-rw-r--r--testing/web-platform/mozilla/tests/css/mediaqueries/mq-gamut-resist-fingerprinting.html40
-rw-r--r--testing/web-platform/mozilla/tests/css/quirks-invalidation-standard-sheet.html14
-rw-r--r--testing/web-platform/mozilla/tests/css/reference/ref-filled-green-100px-square.xht19
-rw-r--r--testing/web-platform/mozilla/tests/dom/classList.html526
-rw-r--r--testing/web-platform/mozilla/tests/dom/delayed_window_print.html39
-rw-r--r--testing/web-platform/mozilla/tests/dom/dispatch_select_event.html35
-rw-r--r--testing/web-platform/mozilla/tests/dom/focus-invalid-uri-link.html63
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/resources/test.html5
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/resources/throttling.js136
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/resources/ws.sub.js3
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-1.window.js10
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-2.window.js11
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-3.window.js11
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-4.window.js11
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-indexeddb.window.js35
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-webaudio.window.js35
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-webrtc.window.js35
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-ws.window.js37
-rw-r--r--testing/web-platform/mozilla/tests/editor/delete-space-after-double-click-selection.html278
-rw-r--r--testing/web-platform/mozilla/tests/editor/white-space-handling-in-mail-editor.html371
-rw-r--r--testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer-mixed-content.js51
-rw-r--r--testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer.https.html18
-rw-r--r--testing/web-platform/mozilla/tests/focus/Range_collapse.html207
-rw-r--r--testing/web-platform/mozilla/tests/focus/Range_selectNode.html267
-rw-r--r--testing/web-platform/mozilla/tests/focus/Range_setEnd.html364
-rw-r--r--testing/web-platform/mozilla/tests/focus/Range_setStart.html353
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_addRange.html1242
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_addRange_in_iframe.html63
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe.html67
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe_iframe.html9
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_collapse.html148
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_collapseToEnd.html134
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_collapseToStart.html142
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_extend.html189
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_removeAllRanges.html112
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_removeRange.html112
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_selectAllChildren.html254
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_setBaseAndExtent.html926
-rw-r--r--testing/web-platform/mozilla/tests/focus/delegateFocus-is-focusable.html19
-rw-r--r--testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-different-site.html16
-rw-r--r--testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-same-site.html16
-rw-r--r--testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-different-site.html16
-rw-r--r--testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-same-site.html16
-rw-r--r--testing/web-platform/mozilla/tests/focus/iframe-focus-event-after-iframe-gets-focus.html75
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-inner.html17
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-outer.sub.html42
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-inner.html17
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-outer.html42
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-inner.html17
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-outer.sub.html50
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-inner.html17
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-outer.html50
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-different-site-iframe-gets-focus-outer.sub.html42
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-iframe-gets-focus-inner.html31
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-same-site-iframe-gets-focus-outer.html42
-rw-r--r--testing/web-platform/mozilla/tests/html/browsers/browsing-the-web/read-media/sandboxed-video.html24
-rw-r--r--testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-01.html59
-rw-r--r--testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-02.html41
-rw-r--r--testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-03.html45
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/form-submission-0/non-usv-filenames.window.js95
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/input-radio-key-navigation.html61
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html203
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01-notref.html3
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01.html13
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02-notref.html9
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02.html14
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03-ref.html7
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03.html15
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04-ref.html7
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04.html14
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/time-enter-keypress.html48
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-circular.html25
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-error.html25
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module.html25
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-circular.html28
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-error.html25
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module.html21
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/circular-module-import-with-syntax-error.html26
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/create-module-script.html25
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/mixed-content-import.https.html27
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/module-error-reporting.html89
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/reload-failed-module-script.html41
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module.js11
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_circular.js5
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_error.js5
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module.js14
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_circular.js3
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_error.js4
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module.js12
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_1.js3
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_2.js5
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_3.js8
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_failure.js6
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/bad_local_export.js3
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error1.js2
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error2.js1
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error3.js1
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/evaluation-order-setup.mjs19
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/import_resolve_failure.js2
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/indirect_export_resolve_failure.js2
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_import.js2
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_indirect_export.js2
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import.js1
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import2.js1
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module.js2
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module_eval_error.js3
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/README.md7
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-after-template.html10
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-ncr.html10
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript.html10
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/in-svg-in-cdata-after-gt.html10
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-after-template-ref.html9
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ncr-ref.html9
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ref.html9
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/references/in-svg-in-cdata-after-gt-ref.html10
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/parsing/math-parse01.html62
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/LICENSE359
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/README.txt14
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/baskar-jagran.html10
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/elango.html10
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/htchanakya.html12
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/shreetam.html10
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/tab.html10
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/tam.html10
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/tboomi.html10
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/tscii.html10
-rw-r--r--testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-0.html12
-rw-r--r--testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-1.html11
-rw-r--r--testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-2.html10
-rw-r--r--testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-false.html5
-rw-r--r--testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-true.html5
-rw-r--r--testing/web-platform/mozilla/tests/infrastructure/specialPowers/specialpowers.html7
-rw-r--r--testing/web-platform/mozilla/tests/media/2x2-green.ogvbin0 -> 7660 bytes
-rw-r--r--testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-in-background.https.html67
-rw-r--r--testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-without-focus.https.html58
-rw-r--r--testing/web-platform/mozilla/tests/placeholder6
-rw-r--r--testing/web-platform/mozilla/tests/screen-capture/getdisplaymedia-user-activation-consumed.https.html30
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/bug1675097.https.html34
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/no_intercept_for_crossorigin_media.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/blank.html2
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-iframe.html15
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-sw.js26
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe.html24
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe_nonrange.html22
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/empty.js0
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/fetch_video.py14
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/green.pngbin0 -> 87 bytes
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/intercept_media_sw.js14
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/update_completes_in_disconnected_global.https.html63
-rw-r--r--testing/web-platform/mozilla/tests/web-animations/web-animations-print-ref.html4
-rw-r--r--testing/web-platform/mozilla/tests/web-animations/web-animations-print.html20
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/__init__.py20
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/reference_context.py72
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/type_hint.py31
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/error.py48
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/errors/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/errors/errors.py8
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/interface/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/interface/interface.py26
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/script/exception_details.py69
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/websocket_upgrade.py156
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/cdp/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/cdp/debugger_address.py77
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/cdp/port_file.py30
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/conftest.py15
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/element_send_keys/scroll_into_view.py50
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/execute_async_script/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/execute_async_script/execute_async.py59
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/get_window_handle/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/get_window_handle/chrome.py25
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/get_window_handles/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/get_window_handles/chrome.py43
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/new_session/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/new_session/bidi_disabled.py33
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/new_session/binary.py40
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/new_session/conftest.py58
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/new_session/create.py11
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/new_session/invalid.py53
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/new_session/profile_root.py36
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/protocol/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/protocol/allow_hosts.py53
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/protocol/allow_origins.py56
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/protocol/marionette_port.py41
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/protocol/request.py72
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/send_alert_text.py22
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/support/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/support/context.py20
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/support/fixtures.py258
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/support/network.py78
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/take_full_screenshot/__init__.py12
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/take_full_screenshot/iframe.py47
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/take_full_screenshot/screenshot.py51
-rw-r--r--testing/web-platform/mozilla/tests/websockets/bug1793935.any.js23
-rw-r--r--testing/web-platform/mozilla/tests/workers/2-mib-file.py7
-rw-r--r--testing/web-platform/mozilla/tests/workers/bug1674278-crash.html6
-rw-r--r--testing/web-platform/mozilla/tests/workers/bug1674278.js6
-rw-r--r--testing/web-platform/mozilla/tests/workers/resources/worker.js129
-rw-r--r--testing/web-platform/mozilla/tests/workers/worker_timer_nesting_level.html52
-rw-r--r--testing/web-platform/mozilla/tests/xml/parsedepth.html62
259 files changed, 11695 insertions, 0 deletions
diff --git a/testing/web-platform/mozilla/README b/testing/web-platform/mozilla/README
new file mode 100644
index 0000000000..a586769844
--- /dev/null
+++ b/testing/web-platform/mozilla/README
@@ -0,0 +1,4 @@
+This directory is intended for web-platform-tests that currently
+cannot be upstreamed for some reason (e.g. because they depend on
+gecko-specific APIs). When run they are mounted on the server under
+/_mozilla/. \ No newline at end of file
diff --git a/testing/web-platform/mozilla/meta/__dir__.ini b/testing/web-platform/mozilla/meta/__dir__.ini
new file mode 100644
index 0000000000..48ebb02088
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/__dir__.ini
@@ -0,0 +1,4 @@
+leak-threshold:
+ if os == "mac": [tab:10000, gmplugin:20000, default:2100, rdd:400]
+ [tab:10000, gmplugin:20000, default:16000]
+
diff --git a/testing/web-platform/mozilla/meta/audio-output/__dir__.ini b/testing/web-platform/mozilla/meta/audio-output/__dir__.ini
new file mode 100644
index 0000000000..59af59bd2a
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/audio-output/__dir__.ini
@@ -0,0 +1 @@
+prefs: [media.setsinkid.enabled:true]
diff --git a/testing/web-platform/mozilla/meta/baselinecoverage/wpt_baselinecoverage.html.ini b/testing/web-platform/mozilla/meta/baselinecoverage/wpt_baselinecoverage.html.ini
new file mode 100644
index 0000000000..786eb0d391
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/baselinecoverage/wpt_baselinecoverage.html.ini
@@ -0,0 +1,3 @@
+[wpt_baselinecoverage.html]
+ disabled:
+ if not ccov: Code-Coverage-Test \ No newline at end of file
diff --git a/testing/web-platform/mozilla/meta/css/css-contain/__dir__.ini b/testing/web-platform/mozilla/meta/css/css-contain/__dir__.ini
new file mode 100644
index 0000000000..e977af25bc
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/css/css-contain/__dir__.ini
@@ -0,0 +1 @@
+prefs: [layout.css.content-visibility.enabled:true]
diff --git a/testing/web-platform/mozilla/meta/css/css-overflow/scrollbar-gutter-reflow-counts-001.html.ini b/testing/web-platform/mozilla/meta/css/css-overflow/scrollbar-gutter-reflow-counts-001.html.ini
new file mode 100644
index 0000000000..3489dc31c9
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/css/css-overflow/scrollbar-gutter-reflow-counts-001.html.ini
@@ -0,0 +1,24 @@
+[scrollbar-gutter-reflow-counts-001.html]
+ [Enlarge the child's block-size to 200%]
+ expected:
+ if os == "android": FAIL # Overlay scrollbars do not create scrollbar gutters.
+
+ [Enlarge the child's block-size to 300px]
+ expected:
+ if os == "android": FAIL # Overlay scrollbars do not create scrollbar gutters.
+
+ [Enlarge the child's block-size to 200% in a vertical-lr scroll container]
+ expected:
+ if os == "android": FAIL # Overlay scrollbars do not create scrollbar gutters.
+
+ [Enlarge the child's block-size to 300px in a vertical-lr scroll container]
+ expected:
+ if os == "android": FAIL # Overlay scrollbars do not create scrollbar gutters.
+
+ [Enlarge the child's block-size to 200% in a vertical-rl scroll container]
+ expected:
+ if os == "android": FAIL # Overlay scrollbars do not create scrollbar gutters.
+
+ [Enlarge the child's block-size to 300px in a vertical-rl scroll container]
+ expected:
+ if os == "android": FAIL # Overlay scrollbars do not create scrollbar gutters.
diff --git a/testing/web-platform/mozilla/meta/css/cssom/window_size_rounding.html.ini b/testing/web-platform/mozilla/meta/css/cssom/window_size_rounding.html.ini
new file mode 100644
index 0000000000..c8cba67eee
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/css/cssom/window_size_rounding.html.ini
@@ -0,0 +1,5 @@
+[window_size_rounding.html]
+ bug: Different viewport size
+ [window_size_rounding]
+ expected:
+ if os == "android": FAIL
diff --git a/testing/web-platform/mozilla/meta/css/mediaqueries/mq-gamut-resist-fingerprinting.html.ini b/testing/web-platform/mozilla/meta/css/mediaqueries/mq-gamut-resist-fingerprinting.html.ini
new file mode 100644
index 0000000000..d6641989c9
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/css/mediaqueries/mq-gamut-resist-fingerprinting.html.ini
@@ -0,0 +1,2 @@
+[mq-gamut-resist-fingerprinting.html]
+ prefs: [privacy.resistFingerprinting:true]
diff --git a/testing/web-platform/mozilla/meta/dom/delayed_window_print.html.ini b/testing/web-platform/mozilla/meta/dom/delayed_window_print.html.ini
new file mode 100644
index 0000000000..3cc920b58a
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/dom/delayed_window_print.html.ini
@@ -0,0 +1,9 @@
+bug: https://bugzilla.mozilla.org/show_bug.cgi?id=739038
+[delayed_window_print.html]
+ comment: Our own save-to-pdf "printer" knows how to respect print.always_print_silent
+ prefs: [print.always_print_silent:true, "print_printer:Mozilla Save to PDF"]
+ disabled:
+ if (asan or debug): https://bugzilla.mozilla.org/show_bug.cgi?id=1661162
+ [Delayed print before load]
+ expected:
+ if os == "android": FAIL
diff --git a/testing/web-platform/mozilla/meta/dom/throttling/__dir__.ini b/testing/web-platform/mozilla/meta/dom/throttling/__dir__.ini
new file mode 100644
index 0000000000..2c01fc801d
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/dom/throttling/__dir__.ini
@@ -0,0 +1,5 @@
+prefs: [dom.timeout.enable_budget_timer_throttling:true,
+ dom.timeout.foreground_budget_regeneration_rate:100,
+ dom.timeout.foreground_throttling_max_budget:10,
+ dom.timeout.budget_throttling_max_delay:2000,
+ dom.timeout.throttling_delay:1]
diff --git a/testing/web-platform/mozilla/meta/dom/throttling/throttling-1.window.js.ini b/testing/web-platform/mozilla/meta/dom/throttling/throttling-1.window.js.ini
new file mode 100644
index 0000000000..99a37fb0e7
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/dom/throttling/throttling-1.window.js.ini
@@ -0,0 +1,4 @@
+[throttling-1.window.html]
+ type: testharness
+ disabled:
+ if debug: disabled
diff --git a/testing/web-platform/mozilla/meta/dom/throttling/throttling-2.window.js.ini b/testing/web-platform/mozilla/meta/dom/throttling/throttling-2.window.js.ini
new file mode 100644
index 0000000000..3ef84823d4
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/dom/throttling/throttling-2.window.js.ini
@@ -0,0 +1,4 @@
+[throttling-2.window.html]
+ type: testharness
+ disabled:
+ if debug: disabled
diff --git a/testing/web-platform/mozilla/meta/dom/throttling/throttling-3.window.js.ini b/testing/web-platform/mozilla/meta/dom/throttling/throttling-3.window.js.ini
new file mode 100644
index 0000000000..04fc9caa02
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/dom/throttling/throttling-3.window.js.ini
@@ -0,0 +1,4 @@
+[throttling-3.window.html]
+ type: testharness
+ disabled:
+ if debug: disabled
diff --git a/testing/web-platform/mozilla/meta/dom/throttling/throttling-4.window.js.ini b/testing/web-platform/mozilla/meta/dom/throttling/throttling-4.window.js.ini
new file mode 100644
index 0000000000..efb637225f
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/dom/throttling/throttling-4.window.js.ini
@@ -0,0 +1,4 @@
+[throttling-4.window.html]
+ type: testharness
+ disabled:
+ if debug: disabled
diff --git a/testing/web-platform/mozilla/meta/dom/throttling/throttling-indexeddb.window.js.ini b/testing/web-platform/mozilla/meta/dom/throttling/throttling-indexeddb.window.js.ini
new file mode 100644
index 0000000000..e462d45d21
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/dom/throttling/throttling-indexeddb.window.js.ini
@@ -0,0 +1,4 @@
+[throttling-indexeddb.window.html]
+ type: testharness
+ disabled:
+ if debug: disabled
diff --git a/testing/web-platform/mozilla/meta/dom/throttling/throttling-webaudio.window.js.ini b/testing/web-platform/mozilla/meta/dom/throttling/throttling-webaudio.window.js.ini
new file mode 100644
index 0000000000..6322e40b6d
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/dom/throttling/throttling-webaudio.window.js.ini
@@ -0,0 +1,5 @@
+[throttling-webaudio.window.html]
+ type: testharness
+ disabled:
+ if debug: disabled
+ if (os == 'win' and processor == 'aarch64'): https://bugzilla.mozilla.org/show_bug.cgi?id=1533892 if (os == 'win' and processor == 'aarch64'): disabled
diff --git a/testing/web-platform/mozilla/meta/dom/throttling/throttling-webrtc.window.js.ini b/testing/web-platform/mozilla/meta/dom/throttling/throttling-webrtc.window.js.ini
new file mode 100644
index 0000000000..f3f12fb571
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/dom/throttling/throttling-webrtc.window.js.ini
@@ -0,0 +1,4 @@
+[throttling-webrtc.window.html]
+ type: testharness
+ disabled:
+ if debug: disabled
diff --git a/testing/web-platform/mozilla/meta/dom/throttling/throttling-ws.window.js.ini b/testing/web-platform/mozilla/meta/dom/throttling/throttling-ws.window.js.ini
new file mode 100644
index 0000000000..219764bcca
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/dom/throttling/throttling-ws.window.js.ini
@@ -0,0 +1,4 @@
+[throttling-ws.window.html]
+ type: testharness
+ disabled:
+ if debug: disabled
diff --git a/testing/web-platform/mozilla/meta/editor/delete-space-after-double-click-selection.html.ini b/testing/web-platform/mozilla/meta/editor/delete-space-after-double-click-selection.html.ini
new file mode 100644
index 0000000000..690b6cbe0a
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/editor/delete-space-after-double-click-selection.html.ini
@@ -0,0 +1,6 @@
+[delete-space-after-double-click-selection.html]
+ [DIV: Only one whitespace character should be removed when there are multiple.]
+ expected: FAIL
+
+ [DIV: Only one whitespace character should be removed when there are multiple whitespaces and the deleted range is the end of the string.]
+ expected: FAIL
diff --git a/testing/web-platform/mozilla/meta/fetch/api/redirect/redirect-referrer.https.html.ini b/testing/web-platform/mozilla/meta/fetch/api/redirect/redirect-referrer.https.html.ini
new file mode 100644
index 0000000000..e934862b92
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/fetch/api/redirect/redirect-referrer.https.html.ini
@@ -0,0 +1,3 @@
+[redirect-referrer.https.html]
+ type: testharness
+ prefs: [security.mixed_content.block_active_content:false, security.mixed_content.block_display_content:false]
diff --git a/testing/web-platform/mozilla/meta/focus/Selection_extend.html.ini b/testing/web-platform/mozilla/meta/focus/Selection_extend.html.ini
new file mode 100644
index 0000000000..541e964a2d
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/focus/Selection_extend.html.ini
@@ -0,0 +1,20 @@
+[Selection_extend.html]
+ type: testharness
+ [Active element should be 'editor' after Selection.extend() from selection at start of the first text node of 'editor' to start of the first text node of 'outerEditor']
+ expected: FAIL
+
+ [Active element should be 'editor' after Selection.extend() from selection at start of the first text node of 'editor' to start of the first text node of 'innerEditor']
+ expected: FAIL
+
+ [Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'outerEditor' to start of the first text node of 'editor']
+ expected: FAIL
+
+ [Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'innerEditor' to start of the first text node of 'outerEditor']
+ expected: FAIL
+
+ [Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'innerEditor' to start of the first text node of 'staticInEditor']
+ expected: FAIL
+
+ [Active element should be 'innerEditor' after Selection.extend() from selection at start of the first text node of 'innerEditor' to start of the first text node of 'anchor']
+ expected: FAIL
+
diff --git a/testing/web-platform/mozilla/meta/focus/focus-next-tick-before-iframe-loaded-different-site.html.ini b/testing/web-platform/mozilla/meta/focus/focus-next-tick-before-iframe-loaded-different-site.html.ini
new file mode 100644
index 0000000000..ef5535b108
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/focus/focus-next-tick-before-iframe-loaded-different-site.html.ini
@@ -0,0 +1,4 @@
+[focus-next-tick-before-iframe-loaded-different-site.html]
+ [Check result]
+ expected:
+ ["PASS", "FAIL"]
diff --git a/testing/web-platform/mozilla/meta/html/semantics/scripting-1/the-script-element/__dir__.ini b/testing/web-platform/mozilla/meta/html/semantics/scripting-1/the-script-element/__dir__.ini
new file mode 100644
index 0000000000..ac824e7911
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/html/semantics/scripting-1/the-script-element/__dir__.ini
@@ -0,0 +1 @@
+prefs: [dom.moduleScripts.enabled:true]
diff --git a/testing/web-platform/mozilla/meta/html/syntax/parsing/math-parse01.html.ini b/testing/web-platform/mozilla/meta/html/syntax/parsing/math-parse01.html.ini
new file mode 100644
index 0000000000..1894b82e0f
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/html/syntax/parsing/math-parse01.html.ini
@@ -0,0 +1,3 @@
+[math-parse01.html]
+ type: testharness
+ prefs: [mathml.disabled:true]
diff --git a/testing/web-platform/mozilla/meta/infrastructure/prefs/prefs-1.html.ini b/testing/web-platform/mozilla/meta/infrastructure/prefs/prefs-1.html.ini
new file mode 100644
index 0000000000..e106f6c252
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/infrastructure/prefs/prefs-1.html.ini
@@ -0,0 +1,2 @@
+[prefs-1.html]
+ prefs: [apz.allow_zooming_out:true]
diff --git a/testing/web-platform/mozilla/meta/mediacapture-streams/enumerateDevices-in-background.https.html.ini b/testing/web-platform/mozilla/meta/mediacapture-streams/enumerateDevices-in-background.https.html.ini
new file mode 100644
index 0000000000..0b5b37ae28
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/mediacapture-streams/enumerateDevices-in-background.https.html.ini
@@ -0,0 +1,3 @@
+[enumerateDevices-in-background.https.html]
+ disabled:
+ if buildapp != 'browser': uses Firefox-for-Desktop specific API
diff --git a/testing/web-platform/mozilla/meta/screen-capture/getdisplaymedia-user-activation-consumed.https.html.ini b/testing/web-platform/mozilla/meta/screen-capture/getdisplaymedia-user-activation-consumed.https.html.ini
new file mode 100644
index 0000000000..edce878a20
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/screen-capture/getdisplaymedia-user-activation-consumed.https.html.ini
@@ -0,0 +1,5 @@
+[getdisplaymedia-user-activation-consumed.https.html]
+ [Test getDisplayMedia() after user activation is consumed]
+ expected:
+ if os == "android": FAIL
+
diff --git a/testing/web-platform/mozilla/meta/wasm/__dir__.ini b/testing/web-platform/mozilla/meta/wasm/__dir__.ini
new file mode 100644
index 0000000000..bcdf039b0e
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/wasm/__dir__.ini
@@ -0,0 +1 @@
+prefs: [javascript.options.wasm:true]
diff --git a/testing/web-platform/mozilla/meta/webdriver/__dir__.ini b/testing/web-platform/mozilla/meta/webdriver/__dir__.ini
new file mode 100644
index 0000000000..c80539ac42
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/__dir__.ini
@@ -0,0 +1 @@
+lsan-allowed: [Alloc, Realloc, XPCNativeInterface::NewInstance, XPCNativeSet::NewInstance, XPCWrappedNative::GetNewOrUsed, XPCWrappedNativeProto::GetNewOrUsed, XPCWrappedNativeTearOff::AddTearOff, mozilla::extensions::AtomSet::Get, mozilla::extensions::MatchPattern::Constructor, mozilla::extensions::MatchPattern::Init, mozilla::net::AddStaticElement, nsDynamicAtom::Create]
diff --git a/testing/web-platform/mozilla/meta/webdriver/bidi/__dir__.ini b/testing/web-platform/mozilla/meta/webdriver/bidi/__dir__.ini
new file mode 100644
index 0000000000..daf68383df
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/bidi/__dir__.ini
@@ -0,0 +1 @@
+leak-threshold: [default:51200]
diff --git a/testing/web-platform/mozilla/meta/webdriver/bidi/browsing_context/create/reference_context.py.ini b/testing/web-platform/mozilla/meta/webdriver/bidi/browsing_context/create/reference_context.py.ini
new file mode 100644
index 0000000000..354cf0417e
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/bidi/browsing_context/create/reference_context.py.ini
@@ -0,0 +1,4 @@
+[reference_context.py]
+ [test_reference_context]
+ disabled:
+ if os == "android": Not supported
diff --git a/testing/web-platform/mozilla/meta/webdriver/bidi/browsing_context/create/type_hint.py.ini b/testing/web-platform/mozilla/meta/webdriver/bidi/browsing_context/create/type_hint.py.ini
new file mode 100644
index 0000000000..1f7dbfcfb5
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/bidi/browsing_context/create/type_hint.py.ini
@@ -0,0 +1,9 @@
+[type_hint.py]
+ [test_type_hint[tab\]]
+ bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1795346
+ expected:
+ if os == "android": FAIL
+
+ [test_type_hint[window\]]
+ disabled:
+ if os == "android": Not supported
diff --git a/testing/web-platform/mozilla/meta/webdriver/bidi/interface/interface.py.ini b/testing/web-platform/mozilla/meta/webdriver/bidi/interface/interface.py.ini
new file mode 100644
index 0000000000..8a597d7ffe
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/bidi/interface/interface.py.ini
@@ -0,0 +1,4 @@
+[interface.py]
+ [test_navigator_webdriver_enabled]
+ disabled:
+ if os == "android": bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1762135
diff --git a/testing/web-platform/mozilla/meta/webdriver/bidi/websocket_upgrade.py.ini b/testing/web-platform/mozilla/meta/webdriver/bidi/websocket_upgrade.py.ini
new file mode 100644
index 0000000000..c6e13dcecd
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/bidi/websocket_upgrade.py.ini
@@ -0,0 +1,3 @@
+[websocket_upgrade.py]
+ disabled:
+ if os == "android": https://bugzilla.mozilla.org/show_bug.cgi?id=1762164
diff --git a/testing/web-platform/mozilla/meta/webdriver/cdp/debugger_address.py.ini b/testing/web-platform/mozilla/meta/webdriver/cdp/debugger_address.py.ini
new file mode 100644
index 0000000000..62f49cf5fd
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/cdp/debugger_address.py.ini
@@ -0,0 +1,7 @@
+[debugger_address.py]
+ disabled:
+ if os == 'win' and ccov: https://bugzilla.mozilla.org/show_bug.cgi?id=1677750
+
+ [test_debugger_address_true_fission_disabled[capabilities0\]]
+ disabled:
+ if fission: (not supported due to Fission override)
diff --git a/testing/web-platform/mozilla/meta/webdriver/cdp/port_file.py.ini b/testing/web-platform/mozilla/meta/webdriver/cdp/port_file.py.ini
new file mode 100644
index 0000000000..bd43509a4e
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/cdp/port_file.py.ini
@@ -0,0 +1,3 @@
+[port_file.py]
+ disabled:
+ if os == "android": https://bugzilla.mozilla.org/show_bug.cgi?id=1762164
diff --git a/testing/web-platform/mozilla/meta/webdriver/execute_async_script/execute_async.py.ini b/testing/web-platform/mozilla/meta/webdriver/execute_async_script/execute_async.py.ini
new file mode 100644
index 0000000000..98efbecd45
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/execute_async_script/execute_async.py.ini
@@ -0,0 +1,16 @@
+[execute_async.py]
+ bug: [1789355, 1806999]
+ [test_no_abort_by_user_prompt_in_other_tab[alert\]]
+ expected:
+ if os == "android" or tsan: [PASS, FAIL]
+ PASS
+
+ [test_no_abort_by_user_prompt_in_other_tab[confirm\]]
+ expected:
+ if os == "android" or tsan: [PASS, FAIL]
+ PASS
+
+ [test_no_abort_by_user_prompt_in_other_tab[prompt\]]
+ expected:
+ if os == "android" or tsan: [PASS, FAIL]
+ PASS
diff --git a/testing/web-platform/mozilla/meta/webdriver/get_window_handles/chrome.py.ini b/testing/web-platform/mozilla/meta/webdriver/get_window_handles/chrome.py.ini
new file mode 100644
index 0000000000..4c686ef883
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/get_window_handles/chrome.py.ini
@@ -0,0 +1,4 @@
+[chrome.py]
+ [test_multiple_windows_and_tabs]
+ disabled:
+ if os == "android": Not supported
diff --git a/testing/web-platform/mozilla/meta/webdriver/new_session/__dir__.ini b/testing/web-platform/mozilla/meta/webdriver/new_session/__dir__.ini
new file mode 100644
index 0000000000..4909cb7221
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/new_session/__dir__.ini
@@ -0,0 +1,2 @@
+lsan-allowed: [MakeUnique, XPCNativeSet::NewInstanceMutate, nsStringBuffer::Alloc, nsStringBundleService::insertIntoCache]
+leak-threshold: [default:150000]
diff --git a/testing/web-platform/mozilla/meta/webdriver/new_session/binary.py.ini b/testing/web-platform/mozilla/meta/webdriver/new_session/binary.py.ini
new file mode 100644
index 0000000000..515482e26f
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/new_session/binary.py.ini
@@ -0,0 +1,4 @@
+[binary.py]
+ [test_shell_script_binary]
+ disabled:
+ if os == "android": bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1762135
diff --git a/testing/web-platform/mozilla/meta/webdriver/new_session/profile_root.py.ini b/testing/web-platform/mozilla/meta/webdriver/new_session/profile_root.py.ini
new file mode 100644
index 0000000000..e2fcdc116f
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/new_session/profile_root.py.ini
@@ -0,0 +1,4 @@
+[profile_root.py]
+ [test_profile_root]
+ expected:
+ if os == "android": FAIL
diff --git a/testing/web-platform/mozilla/meta/webdriver/protocol/marionette_port.py.ini b/testing/web-platform/mozilla/meta/webdriver/protocol/marionette_port.py.ini
new file mode 100644
index 0000000000..04befe8e5d
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/protocol/marionette_port.py.ini
@@ -0,0 +1,8 @@
+[marionette_port.py]
+ [test_marionette_port[system allocated\]]
+ disabled:
+ if os == "android": bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1752284
+
+ [test_marionette_port_outdated_active_port_file]
+ disabled:
+ if os == "android": bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1752284
diff --git a/testing/web-platform/mozilla/meta/webdriver/send_alert_text.py.ini b/testing/web-platform/mozilla/meta/webdriver/send_alert_text.py.ini
new file mode 100644
index 0000000000..0f70ffcc6d
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/webdriver/send_alert_text.py.ini
@@ -0,0 +1,4 @@
+[send_alert_text.py]
+ [test_basic_auth_unsupported_operation]
+ disabled:
+ if os == "android": bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1761480
diff --git a/testing/web-platform/mozilla/meta/workers/__dir__.ini b/testing/web-platform/mozilla/meta/workers/__dir__.ini
new file mode 100644
index 0000000000..4dea21a2a6
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/workers/__dir__.ini
@@ -0,0 +1 @@
+prefs: [dom.workers.testing.enabled:true]
diff --git a/testing/web-platform/mozilla/meta/workers/bug1674278-crash.html.ini b/testing/web-platform/mozilla/meta/workers/bug1674278-crash.html.ini
new file mode 100644
index 0000000000..51ccbf2b7a
--- /dev/null
+++ b/testing/web-platform/mozilla/meta/workers/bug1674278-crash.html.ini
@@ -0,0 +1,2 @@
+[bug1674278-crash.html]
+ expected: TIMEOUT
diff --git a/testing/web-platform/mozilla/tests/audio-output/selectAudioOutput-user-activation-consumed.https.html b/testing/web-platform/mozilla/tests/audio-output/selectAudioOutput-user-activation-consumed.https.html
new file mode 100644
index 0000000000..01758fa179
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/audio-output/selectAudioOutput-user-activation-consumed.https.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<head>
+<title>Test selectAudioOutput() after user activation is consumed</title>
+<link rel="help" href="https://github.com/w3c/mediacapture-output/issues/107">
+</head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+'use strict';
+
+promise_test(async t => {
+ await test_driver.bless('transient activation');
+ // SpecialPowers is used to consume user activation because the only
+ // spec-compliant Gecko API that consumes user activation is
+ // navigator.share(), which is disabled on CI versions of WINNT.
+ // https://searchfox.org/mozilla-central/rev/66547980e8e8ca583473c74f207cae5bac1ed541/testing/web-platform/meta/web-share/share-consume-activation.https.html.ini#4
+ const had_transient_activation =
+ SpecialPowers.wrap(document).consumeTransientUserGestureActivation();
+ assert_true(had_transient_activation,
+ 'should have had transient activation');
+ const p = navigator.mediaDevices.selectAudioOutput();
+ // Race a settled promise to check that the returned promise is already
+ // rejected.
+ await promise_rejects_dom(
+ t, 'InvalidStateError', Promise.race([p, Promise.resolve()]),
+ 'selectAudioOutput should have returned an already-rejected promise.');
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/baselinecoverage/wpt_baselinecoverage.html b/testing/web-platform/mozilla/tests/baselinecoverage/wpt_baselinecoverage.html
new file mode 100644
index 0000000000..889ee9367f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/baselinecoverage/wpt_baselinecoverage.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>Baseline Coverage</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ test(function() {
+ assert_equals(true, true);
+ }, "Collecting basic baseline coverage for web-platform tests.");
+
+ async_test(function(t) {
+ function delayBaseline() {
+ return new Promise((c) => setTimeout(c, 30 * 1000));
+ }
+
+ delayBaseline().then(() => {
+ assert_equals(true, true);
+ t.done();
+ });
+ }, "Collecting delayed baseline coverage for web-platform tests.");
+</script>
diff --git a/testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-document-reflow-count.html b/testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-document-reflow-count.html
new file mode 100644
index 0000000000..69c1c4b7dd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-document-reflow-count.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+ <meta charset="utf-8">
+ <title>CSS Contain: Test content-visibility:hidden reflow counts</title>
+ <link rel="author" title="Martin Robinson" href="mailto:mrobinson@igalia.com">
+
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <body style="content-visibility: hidden;">
+ hello
+ </body>
+
+ <script>
+ let gUtils = SpecialPowers.getDOMWindowUtils(window);
+ let gTestContainer = document.getElementById("test");
+
+ function flushLayout() {
+ document.documentElement.offsetHeight;
+ }
+
+ function getReflowCount() {
+ flushLayout();
+ return gUtils.framesReflowed;
+ }
+
+ function runTestFunctionAndCountReflows(testFunction) {
+ const beforeCount = getReflowCount();
+ testFunction();
+ const afterCount = getReflowCount();
+ console.log(afterCount - beforeCount);
+ return afterCount - beforeCount;
+ }
+
+ test(() => {
+ flushLayout();
+
+ const reflows = runTestFunctionAndCountReflows(() => {
+ document.body.innerText = "something else";
+ });
+ assert_equals(reflows, 1, "Reflow only triggered on body.");
+ }, "Changing text of 'content-visibility: hidden' body only triggers a single reflow.");
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-reflow-count.html b/testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-reflow-count.html
new file mode 100644
index 0000000000..c1484d9c54
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-reflow-count.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+ <meta charset="utf-8">
+ <title>CSS Contain: Test content-visibility:hidden reflow counts</title>
+ <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1746098">
+
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <style>
+ .container {
+ content-visibility: visible;
+ contain: strict;
+ }
+ .flex {
+ display: flex;
+ }
+ .grid {
+ display: grid;
+ grid: repeat(2, 60px) / auto-flow 80px;
+ }
+ </style>
+
+ <div id="test"></div>
+
+ <script>
+ let gUtils = SpecialPowers.getDOMWindowUtils(window);
+ let gTestContainer = document.getElementById("test");
+
+ function setupContainerWithStrictContainment() {
+ const container = document.createElement("div");
+ container.classList.add("container");
+ gTestContainer.innerHTML = "";
+ gTestContainer.appendChild(container);
+ return container;
+ }
+
+ function flushLayout() {
+ document.documentElement.offsetHeight;
+ }
+
+ function getReflowCount() {
+ flushLayout();
+ return gUtils.framesReflowed;
+ }
+
+ function runTestFunctionAndCountReflows(testFunction, container) {
+ const beforeCount = getReflowCount();
+ testFunction(container);
+ const afterCount = getReflowCount();
+ return afterCount - beforeCount;
+ }
+
+ function assertContentVisibilityHiddenHasFewerReflows(testSetup, testFunction) {
+ let container = setupContainerWithStrictContainment();
+ testSetup(container);
+ flushLayout();
+
+ const visibleReflows = runTestFunctionAndCountReflows(testFunction, container);
+
+ container = setupContainerWithStrictContainment();
+ testSetup(container);
+ container.style.contentVisibility = "hidden";
+ flushLayout();
+
+ const hiddenReflows = runTestFunctionAndCountReflows(testFunction, container);
+ assert_less_than(hiddenReflows, visibleReflows,
+ "Style / layout changes in hidden content resulted in fewer reflows than visible content.");
+ }
+
+ test(() => {
+ assertContentVisibilityHiddenHasFewerReflows(
+ (container) => {
+ const div = document.createElement("div");
+ div.innerText = "Test Content";
+ container.appendChild(div);
+ },
+ (container) => {
+ container.children[0].style.width = "100px";
+ container.children[0].style.height = "100px";
+ });
+ }, `Avoiding layout while modifying a simple div's style.`);
+
+ test(() => {
+ assertContentVisibilityHiddenHasFewerReflows(
+ (container) => {
+ container.classList.add("flex");
+
+ const flexContainer = document.createElement("div");
+ flexContainer.classList.add("flex");
+ container.appendChild(flexContainer);
+
+ container.appendChild(document.createElement("div"));
+ },
+ (container) => {
+ container.children[0].style.flexDirection = "row-reverse";
+ }
+ );
+ }, `Avoiding layout while modifying a div with flex display mode.`);
+
+ test(() => {
+ assertContentVisibilityHiddenHasFewerReflows(
+ (container) => {
+ container.classList.add("grid");
+
+ const gridChild = document.createElement("div");
+ gridChild.style.display = "grid";
+ container.appendChild(gridChild);
+
+ container.appendChild(document.createElement("div"));
+ },
+ (container) => {
+ container.children[0].style.rowGap = "30px";
+ },
+ );
+ }, `Avoiding layout while modifying a div with grid display mode.`);
+
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-gutter-reflow-counts-001.html b/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-gutter-reflow-counts-001.html
new file mode 100644
index 0000000000..aa4ed4667b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-gutter-reflow-counts-001.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html>
+ <meta charset="utf-8">
+ <title>CSS Overflow: Test scrollbar-gutter reflow counts</title>
+ <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1746098">
+
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <style>
+ #target {
+ inline-size: 200px;
+ block-size: 100px;
+ background: lightgray;
+ overflow: auto;
+ }
+
+ #targetChild {
+ inline-size: 100%;
+ block-size: 100%;
+ background: orange;
+ }
+ </style>
+
+ <p>Here is a scroll contaier for testing:</p>
+ <div id="target">
+ <div id="targetChild"></div>
+ </div>
+
+ <script>
+ let gUtils = SpecialPowers.getDOMWindowUtils(window);
+ let gTarget = document.getElementById("target");
+ let gTargetChild = document.getElementById("targetChild");
+
+ function getReflowCount()
+ {
+ document.documentElement.offsetHeight; // flush layout
+ return gUtils.framesReflowed;
+ }
+
+ function cleanUp() {
+ gTarget.style.writingMode = "";
+ gTarget.style.scrollbarGutter = "";
+ gTargetChild.style.blockSize = "";
+ }
+
+ function tweakStyleAndCountReflows(aAddStyle, aAddScrollbarGutter)
+ {
+ let beforeCount = getReflowCount();
+ if (aAddScrollbarGutter) {
+ gTarget.style.scrollbarGutter = "stable";
+ }
+ aAddStyle();
+ let afterCount = getReflowCount();
+ cleanUp();
+
+ let numReflows = afterCount - beforeCount;
+ assert_greater_than(numReflows, 0, "We should've reflowed *something* after changing styles:");
+ return numReflows;
+ }
+
+ let gTestCases = [
+ {
+ name : "Enlarge the child's block-size to 200%",
+ addStyle : function () {
+ gTargetChild.style.blockSize = "200%";
+ },
+ },
+ {
+ name : "Enlarge the child's block-size to 300px",
+ addStyle : function () {
+ gTargetChild.style.blockSize = "300px";
+ },
+ },
+ {
+ name : "Enlarge the child's block-size to 200% in a vertical-lr scroll container",
+ addStyle : function () {
+ gTarget.style.writingMode = "vertical-lr";
+ gTargetChild.style.blockSize = "200%";
+ },
+ },
+ {
+ name : "Enlarge the child's block-size to 300px in a vertical-lr scroll container",
+ addStyle : function () {
+ gTarget.style.writingMode = "vertical-lr";
+ gTargetChild.style.blockSize = "300px";
+ },
+ },
+ {
+ name : "Enlarge the child's block-size to 200% in a vertical-rl scroll container",
+ addStyle : function () {
+ gTarget.style.writingMode = "vertical-rl";
+ gTargetChild.style.blockSize = "200%";
+ },
+ },
+ {
+ name : "Enlarge the child's block-size to 300px in a vertical-rl scroll container",
+ addStyle : function () {
+ gTarget.style.writingMode = "vertical-rl";
+ gTargetChild.style.blockSize = "300px";
+ },
+ },
+ ];
+
+ for (let testcase of gTestCases) {
+ test(function () {
+ let numTestReflows = tweakStyleAndCountReflows(testcase.addStyle, true);
+ let numReferenceReflows = tweakStyleAndCountReflows(testcase.addStyle, false);
+ assert_less_than(numTestReflows, numReferenceReflows,
+ "A scroll container with 'scrollbar-gutter:stable' should have less reflow counts:");
+ }, testcase.name)
+ }
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/css/cssom/media-print-change-print-ref.html b/testing/web-platform/mozilla/tests/css/cssom/media-print-change-print-ref.html
new file mode 100644
index 0000000000..1fc00e7e39
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/cssom/media-print-change-print-ref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<div>PASS</div>
diff --git a/testing/web-platform/mozilla/tests/css/cssom/media-print-change-print.html b/testing/web-platform/mozilla/tests/css/cssom/media-print-change-print.html
new file mode 100644
index 0000000000..8e9172956c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/cssom/media-print-change-print.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<link rel=match href="media-print-change-print-ref.html">
+<div id="target">FAIL</div>
+<script>
+ matchMedia("print").addEventListener("change", function() {
+ document.getElementById("target").innerHTML = "PASS";
+ });
+</script>
diff --git a/testing/web-platform/mozilla/tests/css/cssom/size-change-print-notref.html b/testing/web-platform/mozilla/tests/css/cssom/size-change-print-notref.html
new file mode 100644
index 0000000000..d7423b9127
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/cssom/size-change-print-notref.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<div id="target">FAIL</div>
+<script>
+ document.getElementById("target").innerHTML = `${window.innerWidth}x${window.innerHeight}`;
+</script>
diff --git a/testing/web-platform/mozilla/tests/css/cssom/size-change-print.html b/testing/web-platform/mozilla/tests/css/cssom/size-change-print.html
new file mode 100644
index 0000000000..564e71c071
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/cssom/size-change-print.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<link rel="mismatch" href="size-change-print-notref.html">
+<div id="target">FAIL</div>
+<script>
+function update() {
+ document.getElementById("target").innerHTML = `${window.innerWidth}x${window.innerHeight}`;
+}
+update();
+window.addEventListener("resize", update);
+</script>
diff --git a/testing/web-platform/mozilla/tests/css/cssom/window_size_rounding.html b/testing/web-platform/mozilla/tests/css/cssom/window_size_rounding.html
new file mode 100644
index 0000000000..695bf8f34b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/cssom/window_size_rounding.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<pre>
+<script>
+test(function() {
+ let originalWidth = window.innerWidth;
+ let originalHeight = window.innerHeight;
+
+ assert_equals(window.devicePixelRatio, 1, `precondition: ${originalWidth}x${originalHeight}`);
+
+ // This precondition holds because of:
+ // https://searchfox.org/mozilla-central/rev/50215d649d4854812837f1343e8f47bd998dacb5/browser/base/content/browser.js#1717
+ //
+ // But if this test starts failing you can just update the assert and the
+ // factor below accordingly so that the asserts keep passing.
+ assert_equals(originalWidth, 1280, "precondition");
+
+ // Set a fractional scale factor that guarantees that we get a fractional innerWidth
+ const kFactor = 1.5;
+ const kOneAppUnit = 1 / 60;
+
+ SpecialPowers.setFullZoom(window, kFactor);
+ assert_approx_equals(window.devicePixelRatio, kFactor, kOneAppUnit);
+ assert_not_equals(window.innerWidth, originalWidth);
+ assert_not_equals(window.innerHeight, originalHeight);
+ if (SpecialPowers.getBoolPref("dom.innerSize.rounded")) {
+ assert_equals(window.innerWidth, Math.round(originalWidth / kFactor));
+ assert_equals(window.innerHeight, Math.round(originalHeight / kFactor));
+ } else {
+ assert_not_equals(window.innerWidth, Math.round(window.innerWidth));
+ }
+ SpecialPowers.setFullZoom(window, 1); // Restore zoom so results can be seen fine...
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/css/file-selector-button-margin-notref.html b/testing/web-platform/mozilla/tests/css/file-selector-button-margin-notref.html
new file mode 100644
index 0000000000..67fc0af389
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/file-selector-button-margin-notref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<input type=file>
diff --git a/testing/web-platform/mozilla/tests/css/file-selector-button-margin.html b/testing/web-platform/mozilla/tests/css/file-selector-button-margin.html
new file mode 100644
index 0000000000..46c1bd0e3e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/file-selector-button-margin.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>CSS Test: margin can be used to shrink the spacing between the file selector button and its label</title>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1673895">
+<link rel="mismatch" href="file-selector-margin-notref.html">
+<style>
+::file-selector-button {
+ margin: 0;
+}
+</style>
+<input type=file>
diff --git a/testing/web-platform/mozilla/tests/css/mediaqueries/mq-gamut-resist-fingerprinting.html b/testing/web-platform/mozilla/tests/css/mediaqueries/mq-gamut-resist-fingerprinting.html
new file mode 100644
index 0000000000..06424705ac
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/mediaqueries/mq-gamut-resist-fingerprinting.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<title>Test: color-gamut only matches sRGB when resisting fingerprinting</title>
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<!--
+ This test is trivial as long as Firefox only matches the sRGB color-gamut media
+ query. Once Firefox can match additional color-gamuts, though, we should still
+ only match sRGB when resisting fingerprinting.
+-->
+<style>
+div {
+ width: 100px;
+ height: 100px;
+}
+
+div.with-gamut {
+ background-color: red;
+}
+
+div.without-gamuts {
+ background-color: green;
+}
+
+@media (color-gamut: srgb) {
+ div.with-gamut {
+ background-color: green;
+ }
+}
+
+@media (color-gamut: p3), (color-gamut: rec2020) {
+ div.without-gamuts {
+ background-color: red;
+ }
+}
+</style>
+
+<p>Test passes if there are two filled green squares and <strong>no red</strong>.
+
+<div class=with-gamut></div>
+<p></p>
+<div class=without-gamuts></div>
diff --git a/testing/web-platform/mozilla/tests/css/quirks-invalidation-standard-sheet.html b/testing/web-platform/mozilla/tests/css/quirks-invalidation-standard-sheet.html
new file mode 100644
index 0000000000..b926f81af4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/quirks-invalidation-standard-sheet.html
@@ -0,0 +1,14 @@
+<!-- quirks, intentionally -->
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div class="mixedCase"></div>
+<script>
+ test(function() {
+ const kLime = "rgb(0, 255, 0)";
+
+ let div = document.querySelector("div");
+ assert_not_equals(getComputedStyle(div).color, kLime);
+ SpecialPowers.DOMWindowUtils.loadSheetUsingURIString('data:text/css,.mixedCase{color:lime}', 1)
+ assert_equals(getComputedStyle(div).color, kLime);
+ }, "Invalidation of quirks documents when standard sheets are inserted works properly")
+</script>
diff --git a/testing/web-platform/mozilla/tests/css/reference/ref-filled-green-100px-square.xht b/testing/web-platform/mozilla/tests/css/reference/ref-filled-green-100px-square.xht
new file mode 100644
index 0000000000..9b647491e9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/reference/ref-filled-green-100px-square.xht
@@ -0,0 +1,19 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>CSS Reftest Reference</title>
+ <style type="text/css"><![CDATA[
+ div {
+ background-color: green;
+ height: 100px;
+ width: 100px;
+ }
+ ]]></style>
+ </head>
+ <body>
+ <p>Test passes if there are two filled green squares and <strong>no red</strong>.</p>
+ <div></div>
+ <p></p>
+ <div></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/dom/classList.html b/testing/web-platform/mozilla/tests/dom/classList.html
new file mode 100644
index 0000000000..21d79f49e3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/classList.html
@@ -0,0 +1,526 @@
+<!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>
+// This should be the same as dom/nodes/Element-classlist.html in the upstream
+// tests! We have a couple of bits changed, which are marked with comments
+// "LOCAL MODIFICATION". Do not change this without changing the upstream test
+// as well! Merging upstream changes here occasionally might also be nice.
+
+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";
+
+// BEGIN LOCAL MODIFICATION: The spec does not have the onAttrModified event
+// and does not want it, but we still support it.
+var gMutationEvents = [];
+
+function onAttrModified(event) {
+ assert_equals(event.attrName, "class", "mutation on unexpected attribute");
+
+ gMutationEvents.push({
+ attrChange: event.attrChange,
+ prevValue: event.prevValue,
+ newValue: event.newValue,
+ });
+}
+// END LOCAL MODIFICATION
+
+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);
+
+ // BEGIN LOCAL MODIFICATION
+ gMutationEvents = [];
+ e.addEventListener("DOMAttrModified", onAttrModified);
+ // END LOCAL MODIFICATION
+ 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.
+ }
+ }
+ // BEGIN LOCAL MODIFICATION
+ e.removeEventListener("DOMAttrModified", onAttrModified);
+ // END LOCAL MODIFICATION
+ if (!shouldThrow) {
+ assert_equals(res, expectedRes, "wrong return value");
+ }
+
+ var expectedAfter = after;
+
+ assert_equals(e.getAttribute("class"), expectedAfter,
+ "wrong class after modification");
+ // BEGIN LOCAL MODIFICATION
+ var expectedMutation = before != after;
+ assert_equals(gMutationEvents.length, expectedMutation ? 1 : 0,
+ "unexpected mutation event count");
+ if (expectedMutation && gMutationEvents.length) {
+ assert_equals(gMutationEvents[0].attrChange,
+ before == null ? MutationEvent.ADDITION
+ : MutationEvent.MODIFICATION,
+ "wrong type of attribute change");
+ // If there wasn't any previous attribute, prevValue will return an empty
+ // string.
+ var expectedPrevValue = before === null ? "" : before;
+ assert_equals(gMutationEvents[0].prevValue, expectedPrevValue,
+ "wrong previous value");
+ assert_equals(gMutationEvents[0].newValue, after, "wrong new value");
+ }
+ // END LOCAL 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, expectedException) {
+ checkModification(e, "add", argument, undefined, before, after,
+ expectedException, desc);
+ // Also check force toggle
+ // XXX https://github.com/whatwg/dom/issues/443
+ //if (!Array.isArray(argument)) {
+ // checkModification(e, "toggle", [argument, true], true, before, after,
+ // expectedException);
+ //}
+ }
+
+ 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");
+ 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");
+ 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, expectedException) {
+ checkModification(e, "remove", argument, undefined, before, after,
+ expectedException, desc);
+ // Also check force toggle
+ // XXX https://github.com/whatwg/dom/issues/443
+ //if (!Array.isArray(argument)) {
+ // checkModification(e, "toggle", [argument, false], false, before, after,
+ // expectedException);
+ //}
+ }
+
+ 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");
+ checkRemove("a b c", "A", "a b c");
+ 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");
+
+
+ // tests for the force argument handling
+ // XXX Remove these if https://github.com/whatwg/dom/issues/443 is fixed
+
+ function checkForceToggle(before, argument, force, expectedRes, after, expectedException) {
+ checkModification(e, "toggle", [argument, force], expectedRes, before,
+ after, expectedException, desc);
+ }
+
+ checkForceToggle("", "a", true, true, "a");
+ checkForceToggle("a", "a", true, true, "a");
+ checkForceToggle("a", "b", true, true, "a b");
+ checkForceToggle("a b", "b", true, true, "a b");
+ checkForceToggle("", "a", false, false, "");
+ checkForceToggle("a", "a", false, false, "");
+ checkForceToggle("a", "b", false, false, "a");
+ checkForceToggle("a b", "b", false, false, "a");
+
+
+ // 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/mozilla/tests/dom/delayed_window_print.html b/testing/web-platform/mozilla/tests/dom/delayed_window_print.html
new file mode 100644
index 0000000000..0bb9977184
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/delayed_window_print.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for delaying window.print() before load</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+<script>
+let t = async_test("Delayed print before load");
+let beforePrintCalled = false;
+window.addEventListener("beforeprint", t.step_func(function() {
+ assert_false(beforePrintCalled, "Should only call beforeprint once");
+ beforePrintCalled = true;
+ assert_true(
+ !!document.getElementById("before-load"),
+ "Should show contents that get added before load"
+ );
+ assert_true(
+ !!document.getElementById("during-load"),
+ "Should show contents that get added during load"
+ );
+ setTimeout(function() { t.done(); }, 0);
+}));
+
+t.step(function() {
+ window.print();
+
+ let div = document.createElement("div");
+ div.id = "before-load";
+ document.body.appendChild(div);
+});
+
+window.addEventListener("load", t.step_func(function() {
+ window.print();
+
+ let div = document.createElement("div");
+ div.id = "during-load";
+ document.body.appendChild(div);
+}));
+</script>
diff --git a/testing/web-platform/mozilla/tests/dom/dispatch_select_event.html b/testing/web-platform/mozilla/tests/dom/dispatch_select_event.html
new file mode 100644
index 0000000000..1fb70aa5b1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/dispatch_select_event.html
@@ -0,0 +1,35 @@
+<!-- See also Bug 1679427.
+Ensure `select` event is only fired once when tab-ing to an `<input>` element.
+-->
+<!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-vendor.js"></script>
+</head>
+<body>
+ <button>Press this button and Press Tab</button><input value="abc">
+ <script>
+ promise_test(async t => {
+ await new Promise(resolve => { window.onload = resolve; });
+ const button = document.querySelector("button");
+ const input = document.querySelector("input");
+
+ let countSelectEvent = 0;
+ input.addEventListener("select", event => {
+ countSelectEvent++;
+ });
+
+ button.focus();
+ const tabKey = "\uE004";
+ await test_driver.send_keys(button, tabKey);
+ await new Promise(resolve => requestAnimationFrame(
+ () => requestAnimationFrame(resolve)
+ ));
+ assert_equals(countSelectEvent, 1, "Select event was fired more than once!");
+ }, "Select event should only be fired once.");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/dom/focus-invalid-uri-link.html b/testing/web-platform/mozilla/tests/dom/focus-invalid-uri-link.html
new file mode 100644
index 0000000000..5de81c866f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/focus-invalid-uri-link.html
@@ -0,0 +1,63 @@
+<!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>
+function abs(uri) {
+ return new URL(uri, window.location.href).href;
+}
+const CHILD_DOC = `
+<!doctype html>
+<script src="${abs("/resources/testdriver.js")}"></` + `script>
+<script src="${abs("/resources/testdriver-actions.js")}"></` + `script>
+<script src="${abs("/resources/testdriver-vendor.js")}"></` + `script>
+<script>
+ test_driver.set_test_context(opener);
+</` + `script>
+
+<a href>To link or not to link</a>
+
+<script>
+onload = async function() {
+ let link = document.querySelector("a");
+ link.focus();
+ let focused = document.activeElement == link;
+ let clicked = new Promise(resolve => {
+ link.addEventListener("click", resolve, { once: true });
+ });
+ const enterKey = '\\uE007';
+ await test_driver.send_keys(link, enterKey);
+ await clicked;
+ test_driver.message_test({ testResult: true, focused, clicked: true });
+};
+</` + `script>
+`
+
+promise_test(async function(t) {
+ await new Promise(resolve => {
+ window.onload = resolve;
+ })
+
+ let messagePromise = new Promise(resolve => {
+ addEventListener("message", function(msg) {
+ if (msg.data.testResult) {
+ resolve(msg);
+ }
+ });
+ });
+
+ let win = window.open("data:text/html," + escape(CHILD_DOC));
+
+ assert_true(true, "Window opened");
+ let message = await messagePromise;
+ assert_true(true, "message: " + JSON.stringify(message));
+
+ let { focused, clicked } = message.data;
+ assert_true(focused, "Link should be focusable");
+ assert_true(clicked, "Link should be keyboard activatable");
+
+ win.close();
+}, "Link to invalid URI should be focusable and keyboard activatable");
+</script>
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/resources/test.html b/testing/web-platform/mozilla/tests/dom/throttling/resources/test.html
new file mode 100644
index 0000000000..7eb9dd1e40
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/resources/test.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="throttling.js"></script>
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/resources/throttling.js b/testing/web-platform/mozilla/tests/dom/throttling/resources/throttling.js
new file mode 100644
index 0000000000..280b0adc0d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/resources/throttling.js
@@ -0,0 +1,136 @@
+function waitForLoad() {
+ return new Promise(resolve => addEventListener('load', resolve))
+ .then(() => delay(10));
+}
+
+function delay(timeout) {
+ return new Promise(resolve => step_timeout(() => resolve(), 10));
+}
+
+function busy(work) {
+ return delay(10).then(() => new Promise(resolve => {
+ step_timeout(() => {
+ let end = performance.now() + work;
+ while (performance.now() < end) {
+
+ }
+
+ resolve();
+ }, 1);
+ }));
+}
+
+function getThrottlingRate(delay) {
+ return new Promise(resolve => {
+ let start = performance.now();
+ setTimeout(() => {
+ let rate = Math.floor((performance.now() - start) / delay);
+ resolve(rate);
+ }, delay);
+ });
+}
+
+function addElement(t, element, src) {
+ return new Promise((resolve, reject) => {
+ let e = document.createElement(element);
+ e.addEventListener('load', () => resolve(e));
+ if (src) {
+ e.src = src;
+ }
+ document.body.appendChild(e);
+ t.add_cleanup(() => e.remove());
+ });
+}
+
+function inFrame(t) {
+ return addElement(t, "iframe", "resources/test.html")
+ .then(frame => delay(10).then(() => Promise.resolve(frame.contentWindow)));
+}
+
+function addWebSocket(t, url) {
+ return new Promise((resolve, reject) => {
+ let socket = new WebSocket(url);
+ socket.onopen = () => {
+ t.add_cleanup(() => socket.close());
+ resolve();
+ };
+ socket.onerror = reject;
+ });
+}
+
+function addRTCPeerConnection(t) {
+ return new Promise((resolve, reject) => {
+ let connection = new RTCPeerConnection();
+ t.add_cleanup(() => {
+ connection.close()
+ });
+
+ resolve();
+ });
+}
+
+function addIndexedDB(t) {
+ return new Promise((resolve, reject) => {
+ let iDBState = {
+ running: false,
+ db: null
+ };
+
+ let req = indexedDB.open("testDB", 1);
+
+ req.onupgradeneeded = e => {
+ let db = e.target.result;
+ let store = db.createObjectStore("testOS", {keyPath: "id"});
+ let index = store.createIndex("index", ["col"]);
+ };
+
+ req.onsuccess = e => {
+ let db = iDBState.db = e.target.result;
+ let store = db.transaction("testOS", "readwrite").objectStore("testOS");
+ let ctr = 0;
+
+ iDBState.running = true;
+
+ function putLoop() {
+ if (!iDBState.running) {
+ return;
+ }
+
+ let req = store.put({id: ctr++, col: "foo"});
+ req.onsuccess = putLoop;
+
+ if (!iDBState.request) {
+ iDBState.request = req;
+ }
+ }
+
+ putLoop();
+ resolve();
+ };
+
+ t.add_cleanup(() => {
+ iDBState.running = false;
+ iDBState.db && iDBState.db.close();
+ iDBState.db = null;
+ });
+ });
+}
+
+function addWebAudio(t) {
+ return new Promise(resolve => {
+ let context = new (window.AudioContext || window.webkitAudioContext)();
+ context.onstatechange = () => (context.state === "running") && resolve();
+
+ let gain = context.createGain();
+ gain.gain.value = 0.1;
+ gain.connect(context.destination);
+
+ let webaudionode = context.createOscillator();
+ webaudionode.type = 'square';
+ webaudionode.frequency.value = 440; // value in hertz
+ webaudionode.connect(gain);
+ webaudionode.start();
+
+ t.add_cleanup(() => webaudionode.stop());
+ });
+}
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/resources/ws.sub.js b/testing/web-platform/mozilla/tests/dom/throttling/resources/ws.sub.js
new file mode 100644
index 0000000000..a1ac273a54
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/resources/ws.sub.js
@@ -0,0 +1,3 @@
+var __SERVER__NAME = "{{host}}";
+var __PORT = "{{ports[ws][0]}}";
+var __PATH = "echo";
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-1.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-1.window.js
new file mode 100644
index 0000000000..86cefc8a81
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-1.window.js
@@ -0,0 +1,10 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => busy(100)
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_greater_than(rate, 10, "Timeout wasn't throttled");
+ }), "Throttle when all budget has been used.");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-2.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-2.window.js
new file mode 100644
index 0000000000..3ccb35dc08
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-2.window.js
@@ -0,0 +1,11 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => inFrame(t)
+ .then(win => win.busy(100)
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_greater_than(rate, 10, "Timeout wasn't throttled");
+ }), "Throttle iframe when all budget has been used");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-3.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-3.window.js
new file mode 100644
index 0000000000..d1c38bcc12
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-3.window.js
@@ -0,0 +1,11 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => inFrame(t)
+ .then(win => busy(100)
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when all budget in parent has been used");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-4.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-4.window.js
new file mode 100644
index 0000000000..b072b51809
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-4.window.js
@@ -0,0 +1,11 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => inFrame(t)
+ .then(win => win.busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle parent when all budget in iframe has been used");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-indexeddb.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-indexeddb.window.js
new file mode 100644
index 0000000000..73c734a584
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-indexeddb.window.js
@@ -0,0 +1,35 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => addIndexedDB(t)
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there are open IndexedDB transactions.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addIndexedDB(t))
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there are open IndexedDB transactions in iframe.");
+
+promise_test(t => inFrame(t)
+ .then(win => addIndexedDB(t)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there are open IndexedDB transactions in parent.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addIndexedDB(t)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there are open IndexedDB transactions in iframe.");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-webaudio.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-webaudio.window.js
new file mode 100644
index 0000000000..5cd7193788
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-webaudio.window.js
@@ -0,0 +1,35 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => addWebAudio(t)
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there is active WebAudio.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addWebAudio(t))
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there is active WebAudio in iframe.");
+
+promise_test(t => inFrame(t)
+ .then(win => addWebAudio(t)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there is active WebAudio in parent.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addWebAudio(t)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there is active WebAudio in iframe.");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-webrtc.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-webrtc.window.js
new file mode 100644
index 0000000000..2842f77e44
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-webrtc.window.js
@@ -0,0 +1,35 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => addRTCPeerConnection(t)
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there are open RTCPeerConnections.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addRTCPeerConnection(t))
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there are open RTCPeerConnections in iframe.");
+
+promise_test(t => inFrame(t)
+ .then(win => addRTCPeerConnection(t)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there are open RTCPeerConnections in parent.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addRTCPeerConnection(t)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there are open RTCPeerConnections in iframe.");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-ws.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-ws.window.js
new file mode 100644
index 0000000000..185654e04d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-ws.window.js
@@ -0,0 +1,37 @@
+// META: script=resources/ws.sub.js
+// META: script=resources/throttling.js
+let server = "ws://" + __SERVER__NAME + ":" + __PORT + "/" + __PATH;
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => addWebSocket(t, server)
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there are open WebSockets.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addWebSocket(t, server))
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there are open WebSockets in iframe.");
+
+promise_test(t => inFrame(t)
+ .then(win => addWebSocket(t, server)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there are open WebSockets in parent.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addWebSocket(t, server)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there are open WebSockets in iframe.");
diff --git a/testing/web-platform/mozilla/tests/editor/delete-space-after-double-click-selection.html b/testing/web-platform/mozilla/tests/editor/delete-space-after-double-click-selection.html
new file mode 100644
index 0000000000..6c065cbc12
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/editor/delete-space-after-double-click-selection.html
@@ -0,0 +1,278 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <title>Test for Bug 1783641</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>
+ <style>
+ .testStyle {
+ font-family: 'Courier New', Courier, monospace;
+ font-size: 12px;
+ padding: 0px;
+ width: 200px;
+ }
+ </style>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1783641">Mozilla Bug 1783641</a><br />
+ <span class="testStyle" id="placeholder"></span>
+ <input class="testStyle" type="text" />
+ <div class="testStyle" contenteditable></div>
+ <textarea class="testStyle"></textarea>
+ <script>
+
+ promise_test(async t => {
+ await new Promise(resolve => { window.onload = resolve; });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["editor.word_select.delete_space_after_doubleclick_selection", true],
+ ["layout.word_select.eat_space_to_next_word", false]
+ ]
+ });
+ }, "Test setup");
+ const placeHolder = document.getElementById("placeholder");
+ const deleteKey = "\uE017";
+
+ function waitForRender() {
+ return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ };
+
+ for (const selector of ["input", "div[contenteditable]", "textarea"]) {
+ const editableElement = document.querySelector(selector);
+ editableElement.focus();
+
+ /**
+ * Helper functions to set or get the value and the current selection of `editableElement`,
+ * regardless of its actual type.
+ */
+ const setValue = (aValue) => {
+ editableElement.tagName.toLowerCase() == "div"
+ ? editableElement.innerHTML = aValue
+ : editableElement.value = aValue;
+ }
+ const getValue = () => {
+ return editableElement.tagName.toLowerCase() == "div"
+ ? editableElement.innerHTML
+ : editableElement.value;
+ };
+ const getSelection = () => {
+ return editableElement.tagName.toLowerCase() == "div"
+ ? document.getSelection().toString()
+ : editableElement.value.substring(
+ editableElement.selectionStart,
+ editableElement.selectionEnd
+ );
+ };
+
+ /**
+ * Places a double click in `editableElement` at exactly the end of `aPlaceHolderText` and press delete.
+ * `aPlaceholderText` therefore should contain the same text as the value of the `editableElement`
+ * up to the point where the doubleclick should happen.
+ *
+ * If `aSelectionValue` is defined, the selection created by the double click is compared to `aSelectionValue`.
+ */
+ const doubleClickAndDelete = async (aPlaceHolderText, aSelectionValue = undefined) => {
+ placeHolder.innerHTML = aPlaceHolderText;
+ editableElement.focus();
+ await waitForRender();
+ const absInputPos = editableElement.getBoundingClientRect();
+ selectionOffset = {
+ x: placeHolder.getBoundingClientRect().width,
+ y: Math.floor(placeHolder.getBoundingClientRect().height / 2)
+ };
+ await (new test_driver.Actions()
+ // for some reason this still doesn't work:
+ // .pointerMove(Math.floor(selectionOffset.x), Math.floor(selectionOffset.y), { origin: editableElement })
+ // but this does:
+ .pointerMove(
+ Math.floor(absInputPos.x + selectionOffset.x),
+ Math.floor(absInputPos.y + selectionOffset.y),
+ { origin: "viewport" }
+ )
+ .pointerDown()
+ .pointerUp()
+ .pointerDown()
+ .pointerUp())
+ .send()
+ await waitForRender();
+ if (aSelectionValue !== undefined) {
+ assert_equals(getSelection(), aSelectionValue, "Wrong selection value!");
+ }
+ return test_driver.send_keys(editableElement, deleteKey);
+ };
+ if (editableElement.tagName.toLowerCase() == "div") {
+ promise_test(async t => {
+ setValue("<p>abc def<span></span></p>");
+ await doubleClickAndDelete("abc de", "def");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "<p>abc</p>",
+ "The <span> at the end of the string must be removed, as well as the whitespace in between words.");
+ }, `${editableElement.tagName}: An empty span at the end of the selection should be considered end of selection!`);
+ }
+ promise_test(async t => {
+ setValue("one two");
+ await doubleClickAndDelete("on", "one");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "two",
+ "The whitespace between words must be removed when a word at the beginning is selected and deleted!"
+ );
+ }, `${editableElement.tagName}: Remove word at the beginning of string should remove the whitespace in between.`);
+
+ promise_test(async t => {
+ setValue("one two");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one",
+ "The whitespace between words must be removed when a word is selected at the end of the string and deleted!"
+ );
+ }, `${editableElement.tagName}: Remove word at the end of a string should remove the whitespace in between.`);
+
+ promise_test(async t => {
+ setValue("one two three");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one three",
+ "One whitespace between words must be removed when a word is selected and deleted!"
+ );
+ await waitForRender();
+ if (editableElement.tagName.toLowerCase() == "div") {
+ document.getSelection().setBaseAndExtent(
+ editableElement.firstChild,
+ 0,
+ editableElement.firstChild,
+ 3
+ );
+ }
+ else {
+ editableElement.setSelectionRange(0, 3);
+ }
+ await test_driver.send_keys(editableElement, deleteKey);
+ // div[contenteditable] returns '&nbsp;three' here.
+ assert_equals(
+ getValue().replace(/&nbsp;/g, " "),
+ " three",
+ "The whitespace must not be removed when selecting a word without doubleclicking it!"
+ );
+
+ }, `${editableElement.tagName}: Remove word in the middle of a string should remove one whitespace ` +
+ "only if selection is created by double click.");
+
+ promise_test(async t => {
+ setValue("one two three");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one three",
+ "One whitespace between words must be removed when a word is selected and deleted!"
+ );
+ }, `${editableElement.tagName}: Only one whitespace character should be removed when there are multiple.`);
+
+ promise_test(async t => {
+ setValue("one two");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one ",
+ "One whitespace character between words must be removed when a word is selected and deleted!"
+ );
+ }, `${editableElement.tagName}: Only one whitespace character should be removed when ` +
+ "there are multiple whitespaces and the deleted range is the end of the string.");
+
+ promise_test(async t => {
+ setValue("one two, three");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one, three",
+ "The whitespace in front of the selected word must be removed when punctuation follows selection!"
+ );
+ }, `${editableElement.tagName}: Removing a word before punctuation should remove the whitespace.`);
+
+ promise_test(async t => {
+ setValue("one, two");
+ await doubleClickAndDelete("one, tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one,",
+ "The whitespace in front of the selected word must be removed!"
+ );
+ }, `${editableElement.tagName}: Remove a word after punctuation should remove the whitespace.`);
+
+ promise_test(async t => {
+ setValue("one\u00A0two, three"); // adds a &nbsp;
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one, three",
+ "The whitespace between words must be removed when a word is selected and deleted!"
+ );
+ }, `${editableElement.tagName}: Removing a word between a &nbsp; and punctuation should remove the nbsp character.`);
+
+ if (editableElement.tagName.toLowerCase() == "div") {
+ promise_test(async t => {
+ setValue("one two<br>");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one<br>",
+ "The line break must be preserved!"
+ );
+ }, `${editableElement.tagName}: Removing a word in front of a line break should preserve the line break.`);
+ }
+ if (editableElement.tagName.toLowerCase() == "textarea") {
+ promise_test(async t => {
+ setValue("one two\n");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one\n",
+ "The line break must be preserved!"
+ );
+ }, `${editableElement.tagName}: RRemoving a word in front of a line break should preserve the line break.`);
+ }
+ promise_test(async t => {
+ setValue("one two");
+ await doubleClickAndDelete("on", "one");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "two",
+ "The whitespace between words must be removed when a word at the beginning is selected and deleted!"
+ );
+ document.execCommand("undo", false, null);
+ assert_equals(
+ getValue(),
+ "one two",
+ "Undo action must restore the original state!"
+ );
+ document.execCommand("redo", false, null);
+ assert_equals(
+ getValue(),
+ "two",
+ "Redo action must remove the word and whitespace again!"
+ );
+ }, `${editableElement.tagName}: Undo and Redo actions should take the removed whitespace into account.`);
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/editor/white-space-handling-in-mail-editor.html b/testing/web-platform/mozilla/tests/editor/white-space-handling-in-mail-editor.html
new file mode 100644
index 0000000000..06fe0acf6e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/editor/white-space-handling-in-mail-editor.html
@@ -0,0 +1,371 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<meta name="variant" content="?plaintext=true">
+<meta name="variant" content="?plaintext=false">
+<title>Testing white-space handling in mail editor mode</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div contenteditable></div>
+<script>
+"use strict";
+
+const params = new URLSearchParams(location.search);
+const inPlaintextMode = params.get("plaintext") === "true";
+
+const editingHost = document.querySelector("div[contenteditable]");
+// To show white-spaces as-is, editing host should have "pre-wrap" style.
+// Then, editor does not need to convert white-spaces.
+editingHost.style.whiteSpace = "pre-wrap";
+
+const editor = SpecialPowers.wrap(window).docShell.editingSession.getEditorForWindow(window);
+editor.flags |= SpecialPowers.Ci.nsIEditor.eEditorMailMask;
+if (inPlaintextMode) {
+ editor.flags |= SpecialPowers.Ci.nsIEditor.eEditorPlaintextMask;
+}
+
+test(() => {
+ editingHost.innerHTML = "<p><br></p>";
+ getSelection().collapse(editingHost.querySelector("p"), 0);
+ document.execCommand("insertText", false, " ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Inserting first white-space into empty paragraph shouldn't convert the inserting white-space to an NBSP");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertText", false, " ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Inserting second white-space next to a white-space shouldn't convert the inserting white-space nor the existing white-space to NBSP");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertText", false, " ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Inserting 3rd white-space into middle of white-spaces shouldn't convert the inserting white-space nor the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("insertText", false, "a");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> a </p>",
+ "<p> a <br></p>",
+ ]
+ );
+}, "Inserting a character into middle of white-spaces shouldn't convert the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p> a </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 3);
+ document.execCommand("delete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Deleting a character at middle of white-spaces shouldn't convert the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 3);
+ document.execCommand("delete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Deleting a white-space at middle of white-spaces shouldn't convert the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p> a </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("forwardDelete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Forward deleting a character at middle of white-spaces shouldn't convert the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("forwardDelete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Forward deleting at middle of white-spaces shouldn't convert the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p><br></p>";
+ getSelection().collapse(editingHost.querySelector("p"), 0);
+ document.execCommand("insertText", false, "\xA0");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;</p>",
+ "<p>&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting first NBSP into empty paragraph shouldn't convert the inserting NBSP to a white-space");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertText", false, "\xA0");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting second NBSP next to an NBSP shouldn't convert the inserting NBSP nor the existing NBSP to white-space");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertText", false, "\xA0");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting 3rd NBSP into middle of NBSPs shouldn't convert the inserting NBSP nor the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("insertText", false, "a");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;a&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;a&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting a character into middle of NBSPs shouldn't convert the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0a\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 3);
+ document.execCommand("delete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Deleting a character at middle of NBSPs shouldn't convert the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0\xA0\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 3);
+ document.execCommand("delete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Deleting an NBSP at middle of NBSPs shouldn't convert the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0a\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("forwardDelete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Forward deleting a character at middle of NBSPs shouldn't convert the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0\xA0\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("forwardDelete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Forward deleting at middle of NBSPs shouldn't convert the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p><br></p>";
+ getSelection().collapse(editingHost.querySelector("p"), 0);
+ document.execCommand("insertHTML", false, " ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Inserting first white-space with insertHTML command into empty paragraph shouldn't convert the inserting white-space to an NBSP");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertHTML", false, " ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Inserting second white-space with insertHTML command next to a white-space shouldn't convert the inserting white-space nor the existing white-space to NBSP");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertHTML", false, " ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Inserting 3rd white-space with insertHTML command into middle of white-spaces shouldn't convert the inserting white-space nor the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("insertHTML", false, "a");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> a </p>",
+ "<p> a <br></p>",
+ ]
+ );
+}, "Inserting a character with insertHTML command into middle of white-spaces shouldn't convert the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p><br></p>";
+ getSelection().collapse(editingHost.querySelector("p"), 0);
+ document.execCommand("insertHTML", false, "\xA0");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;</p>",
+ "<p>&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting first NBSP with insertHTML command into empty paragraph shouldn't convert the inserting NBSP to a white-space");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertHTML", false, "\xA0");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting second NBSP with insertHTML command next to an NBSP shouldn't convert the inserting NBSP nor the existing NBSP to white-space");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertHTML", false, "\xA0");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting 3rd NBSP with insertHTML command into middle of NBSPs shouldn't convert the inserting NBSP nor the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("insertHTML", false, "a");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;a&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;a&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting a character with insertHTML command into middle of NBSPs shouldn't convert the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>abc</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, "abc".length);
+ document.execCommand("insertHTML", false, "def ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>abcdef </p>",
+ "<p>abcdef <br></p>",
+ ]
+ );
+}, "Inserting multiple white-spaces with insertHTML command shouldn't convert the white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p>abc</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, "abc".length);
+ document.execCommand("insertHTML", false, "def ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>abcdef </p>",
+ "<p>abcdef <br></p>",
+ ]
+ );
+}, "Inserting multiple NBSPs with insertHTML command shouldn't convert the NBSPs to white-spaces");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer-mixed-content.js b/testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer-mixed-content.js
new file mode 100644
index 0000000000..ad59904fd9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer-mixed-content.js
@@ -0,0 +1,51 @@
+if (this.document === undefined) {
+ importScripts("/common/utils.js");
+ importScripts("/resources/testharness.js");
+ importScripts("/fetch/api/resources/utils.js");
+ importScripts("/common/get-host-info.sub.js");
+}
+
+function testReferrerAfterRedirection(desc, redirectUrl, redirectLocation, referrerPolicy, redirectReferrerPolicy, expectedReferrer) {
+ var url = redirectUrl;
+ var urlParameters = "?location=" + encodeURIComponent(redirectLocation);
+
+ if (redirectReferrerPolicy)
+ urlParameters += "&redirect_referrerpolicy=" + redirectReferrerPolicy;
+
+ var requestInit = {"redirect": "follow", "referrerPolicy": referrerPolicy};
+
+ promise_test(function(test) {
+ return fetch(url + urlParameters, requestInit).then(function(response) {
+ assert_equals(response.status, 200, "Inspect header response's status is 200");
+ assert_equals(response.headers.get("x-request-referer"), expectedReferrer ? expectedReferrer : null, "Check referrer header");
+ });
+ }, desc);
+}
+
+var referrerOrigin = get_host_info().HTTPS_ORIGIN + "/";
+var referrerUrl = location.href;
+
+var RESOURCES_DIR = "/fetch/api/resources/";
+var redirectUrl = RESOURCES_DIR + "redirect.py";
+var locationUrl = get_host_info().HTTPS_ORIGIN + RESOURCES_DIR + "inspect-headers.py?headers=referer";
+var httpLocationUrl = get_host_info().HTTP_REMOTE_ORIGIN + RESOURCES_DIR + "inspect-headers.py?cors&headers=referer";
+
+testReferrerAfterRedirection("Downgrade, empty init, unsafe-url redirect header ", redirectUrl, httpLocationUrl, "", "unsafe-url", referrerUrl);
+testReferrerAfterRedirection("Downgrade, empty init, no-referrer-when-downgrade redirect header ", redirectUrl, httpLocationUrl, "", "no-referrer-when-downgrade", null);
+testReferrerAfterRedirection("Downgrade, empty init, same-origin redirect header ", redirectUrl, httpLocationUrl, "", "same-origin", null);
+testReferrerAfterRedirection("Downgrade, empty init, origin redirect header ", redirectUrl, httpLocationUrl, "", "origin", referrerOrigin);
+testReferrerAfterRedirection("Downgrade, empty init, origin-when-cross-origin redirect header ", redirectUrl, httpLocationUrl, "", "origin-when-cross-origin", referrerOrigin);
+testReferrerAfterRedirection("Downgrade, empty init, no-referrer redirect header ", redirectUrl, httpLocationUrl, "", "no-referrer", null);
+testReferrerAfterRedirection("Downgrade, empty init, strict-origin redirect header ", redirectUrl, httpLocationUrl, "", "strict-origin", null);
+testReferrerAfterRedirection("Downgrade, empty init, strict-origin-when-cross-origin redirect header ", redirectUrl, httpLocationUrl, "", "strict-origin-when-cross-origin", null);
+
+testReferrerAfterRedirection("Downgrade, empty redirect header, unsafe-url init ", redirectUrl, httpLocationUrl, "unsafe-url", "", referrerUrl);
+testReferrerAfterRedirection("Downgrade, empty redirect header, no-referrer-when-downgrade init ", redirectUrl, httpLocationUrl, "no-referrer-when-downgrade", "", null);
+testReferrerAfterRedirection("Downgrade, empty redirect header, same-origin init ", redirectUrl, httpLocationUrl, "same-origin", "", null);
+testReferrerAfterRedirection("Downgrade, empty redirect header, origin init ", redirectUrl, httpLocationUrl, "origin", "", referrerOrigin);
+testReferrerAfterRedirection("Downgrade, empty redirect header, origin-when-cross-origin init ", redirectUrl, httpLocationUrl, "origin-when-cross-origin", "", referrerOrigin);
+testReferrerAfterRedirection("Downgrade, empty redirect header, no-referrer init ", redirectUrl, httpLocationUrl, "no-referrer", "", null);
+testReferrerAfterRedirection("Downgrade, empty redirect header, strict-origin init ", redirectUrl, httpLocationUrl, "strict-origin", "", null);
+testReferrerAfterRedirection("Downgrade, empty redirect header, strict-origin-when-cross-origin init ", redirectUrl, httpLocationUrl, "strict-origin-when-cross-origin", "", null);
+
+
diff --git a/testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer.https.html b/testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer.https.html
new file mode 100644
index 0000000000..bcd24892e2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer.https.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Fetch: redirect referrer handling, mixed content</title>
+ <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
+ <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
+ <meta name="help" href="https://fetch.spec.whatwg.org/#http-redirect-fetch">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script src="/common/utils.js"></script>
+ <script src="/fetch/api/resources/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="redirect-referrer-mixed-content.js"></script>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/Range_collapse.html b/testing/web-platform/mozilla/tests/focus/Range_collapse.html
new file mode 100644
index 0000000000..7c16f16bec
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Range_collapse.html
@@ -0,0 +1,207 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Range.collapse()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Range.collapse(true) of selection between start of the first text node of 'anchor' and end of the first text node of 'anchor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Range.collapse(false) of selection between start of the first text node of 'anchor' and end of the first text node of 'anchor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Range.collapse(false) of selection between start of the first text node of 'staticBefore' and end of the first text node of 'anchor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Range.collapse(false) of selection between start of the first text node of 'editor' and end of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Range.collapse(true) of selection between start of the first text node of 'editor' and end of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Range.collapse(false) of selection between start of the first text node of 'innerEditor' and end of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Range.collapse(true) of selection between start of the first text node of 'innerEditor' and end of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Range.collapse(false) of selection between start of the first text node of 'anchor' and end of the first text node of 'anchor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Range.collapse(true) of selection between start of the first text node of 'anchor' and end of the first text node of 'anchor' when active element is 'anchor'");
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Range.collapse(true) of selection between start of the first text node of 'staticBefore' and end of the first text node of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Range.collapse(false) of selection between start of the first text node of 'staticBefore' and end of the first text node of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Range.collapse(true) of selection between start of the first text node of 'editor' and end of the first text node of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Range.collapse(false) of selection between start of the first text node of 'editor' and end of the first text node of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Range.collapse(true) of selection between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Range.collapse(false) of selection between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Range.collapse(true) of selection between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Range.collapse(false) of selection between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' when active element is 'outerEditor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Range_selectNode.html b/testing/web-platform/mozilla/tests/focus/Range_selectNode.html
new file mode 100644
index 0000000000..3975653d2c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Range_selectNode.html
@@ -0,0 +1,267 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Range.selectNode() and Range.selectNodeContents()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+function selectNode(aNodeToSelect)
+{
+ document.getSelection().getRangeAt(0).selectNode(aNodeToSelect);
+}
+
+function selectNodeContents(aNodeToSelectItsContents)
+{
+ document.getSelection().getRangeAt(0).selectNodeContents(aNodeToSelectItsContents);
+}
+
+[{ func: selectNode, doingDescription: "Range.selectNode()" },
+ { func: selectNodeContents, doingDescription: "Range.selectNodeContents()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange();
+ aTest.func(staticBefore.textNode);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with the first text node of 'staticBefore' when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ aTest.func(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'editor' when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ aTest.func(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'outerEditor' when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ aTest.func(staticInEditor.textNode);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with the first text node of 'staticInEditor' when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ aTest.func(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'innerEditor' when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ aTest.func(anchor.textNode);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with the first text node of 'anchor' when active element is the <body>");
+
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ aTest.func(staticBefore.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'staticBefore' when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ aTest.func(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'editor' when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ aTest.func(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'outerEditor' when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ aTest.func(staticInEditor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'staticInEditor' when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ aTest.func(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'innerEditor' when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ aTest.func(anchor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'anchor' when active element is the 'editor'");
+
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ aTest.func(staticBefore.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'staticBefore' when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ aTest.func(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'editor' when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ aTest.func(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'outerEditor' when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ aTest.func(staticInEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'staticInEditor' when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ aTest.func(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'innerEditor' when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ aTest.func(anchor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'anchor' when active element is the 'outerEditor'");
+
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ aTest.func(staticBefore.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'staticBefore' when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ aTest.func(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'editor' when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ aTest.func(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'outerEditor' when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ aTest.func(staticInEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'staticInEditor' when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ aTest.func(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'innerEditor' when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ aTest.func(anchor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'anchor' when active element is the 'innerEditor'");
+
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ aTest.func(staticBefore.textNode);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'anchor' after " + aTest.doingDescription + " with the first text node of 'staticBefore' when active element is 'anchor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ aTest.func(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'editor' when active element is 'anchor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ aTest.func(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'outerEditor' when active element is 'anchor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ aTest.func(staticInEditor.textNode);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'anchor' after " + aTest.doingDescription + " with the first text node of 'staticInEditor' when active element is 'anchor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ aTest.func(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'innerEditor' when active element is 'anchor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ aTest.func(anchor.textNode);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'anchor' after " + aTest.doingDescription + " with the first text node of 'anchor' when active element is the 'anchor'");
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Range_setEnd.html b/testing/web-platform/mozilla/tests/focus/Range_setEnd.html
new file mode 100644
index 0000000000..e1eed4ae71
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Range_setEnd.html
@@ -0,0 +1,364 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Range.setEnd(), Range.setEndAfter() and Range.setEndBefore()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+function setEnd(aNode, aOffset)
+{
+ document.getSelection().getRangeAt(0).setEnd(aNode, aOffset);
+}
+
+function setEndBefore(aNode, aOffset)
+{
+ document.getSelection().getRangeAt(0).setEndBefore(aNode);
+}
+
+function setEndAfter(aNode, aOffset)
+{
+ document.getSelection().getRangeAt(0).setEndAfter(aNode);
+}
+
+// Range.setEnd*() should work same as collapse if specified end position is before its start.
+[{ func: setEnd, doingDescription: "Range.setEnd()" },
+ { func: setEndBefore, doingDescription: "Range.setEndBefore()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.textNode);
+ aTest.func(staticBefore.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'staticBefore' (before the selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ aTest.func(editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'editor' (before the selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticInEditor.textNode);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (before the selection) when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (before the selection) when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(anchor.textNode);
+ aTest.func(staticAfter.textNode, 0);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'anchor' after " + aTest.doingDescription + " with start of the first text node of 'staticAfter' (before the selection) when active element is 'anchor'");
+
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(staticBefore.textNode, staticBefore.textLength);
+ aTest.func(staticBefore.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with start of the first text node of 'staticBefore' (before the collapsed selection) when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().collapse(editor.textNode, editor.textLength);
+ aTest.func(staticBefore.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'staticBefore' (before the collapsed selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(outerEditor.textNode, outerEditor.textLength);
+ aTest.func(editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'editor' (before the collapsed selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(staticInEditor.textNode, staticInEditor.textLength);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (before the collapsed selection) when active element is the <body> and selection is in 'staticInEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(innerEditor.textNode, innerEditor.textLength);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (before the collapsed selection) when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().collapse(anchor.textNode, anchor.textLength);
+ aTest.func(staticAfter.textNode, 0);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'anchor' after " + aTest.doingDescription + " with start of the first text node of 'staticAfter' (before the collapsed selection) when active element is 'anchor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(anchor.textNode, anchor.textLength);
+ aTest.func(anchor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with start of the first text node of 'anchor' (before the collapsed selection) when active element is the <body>");
+
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, staticBefore.textLength,
+ editor.textNode, editor.textLength);
+ aTest.func(staticBefore.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with start of the first text node of 'staticBefore' (before the selection, between end of the first text node of 'staticBefore' and end of the first text node of 'editor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, editor.textLength,
+ outerEditor.textNode, outerEditor.textLength);
+ aTest.func(editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'editor' (before the selection, between end of the first text node of 'editor' and end of the first text node of 'outerEditor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, outerEditor.textLength,
+ innerEditor.textNode, innerEditor.textLength);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (before the selection, between end of the first text node of 'outerEditor' and end of the first text node of 'innerEditor') when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, staticInEditor.textLength,
+ innerEditor.textNode, innerEditor.textLength);
+ aTest.func(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with start of the first text node of 'staticInEditor' (before the selection, between end of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor') when active element is the <body>");
+});
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticAfter.textNode);
+ setEndAfter(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setEndAfter() with the first text node of 'innerEditor' (before the selection) when active element is the <body> and selection is in 'staticAfter'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ setEndAfter(staticInEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setEndAfter() with the first text node of 'staticInEditor' (before the selection) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticInEditor.textNode);
+ setEndAfter(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setEndAfter() with the first text node of 'outerEditor' (before the selection) when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ setEndAfter(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setEndAfter() with the first text node of 'outerEditor' (before the selection) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ setEndAfter(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setEndAfter() with the first text node of 'editor' (before the selection) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.textNode);
+ setEndAfter(staticBefore.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setEndAfter() with the first text node of 'staticBefore' (before the selection) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(anchor.textNode);
+ setEndAfter(staticBefore.textNode);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setEndAfter() with the first text node of 'staticBefore' (before the selection) when active element is 'anchor'");
+
+// Range.setEnd*() should blur focused editing host when it expands selection to outside of it.
+[{ func: setEnd, doingDescription: "Range.setEnd()" },
+ { func: setEndAfter, doingDescription: "Range.setEndAfter()" },
+ { func: setEndBefore, doingDescription: "Range.setEndBefore()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().collapse(editor.textNode, 0);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (after end of the collapsed selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(outerEditor.textNode, 0);
+ aTest.func(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (after end of the collapsed selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(outerEditor.textNode, 0);
+ aTest.func(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'innerEditor' (after end of the collapsed selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(outerEditor.textNode, 0);
+ aTest.func(staticAfter.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'staticAfter' (after end of the collapsed selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.textNode);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (after end of the selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ aTest.func(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (after end of the selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ aTest.func(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'innerEditor' (after end of the selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ aTest.func(staticAfter.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'staticAfter' (after end of the selection) when active element is 'outerEditor'");
+});
+
+// Range.setEnd*() should move focus to an editing host when the range is shrunken into it.
+[{ func: setEnd, doingDescription: "Range.setEnd()" },
+ { func: setEndAfter, doingDescription: "Range.setEndAfter()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ aTest.func(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with end of the first text node of 'editor' (shrunken into 'editor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (shrunken into 'outerEditor') when active element is 'outerEditor' and selection end is in 'staticInEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (shrunken into 'outerEditor') when active element is 'outerEditor' and selection end is in 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ aTest.func(innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'innerEditor' (shrunken into 'innerEditor') when active element is 'innerEditor' and selection end is in 'staticAfter'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ aTest.func(staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with end of the first text node of 'staticInEditor' (shrunken into 'staticInEditor') when active element is the <body>");
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Range_setStart.html b/testing/web-platform/mozilla/tests/focus/Range_setStart.html
new file mode 100644
index 0000000000..9297aa02ab
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Range_setStart.html
@@ -0,0 +1,353 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Range.setStart(), Range.setStartAfter() and Range.setStartBefore()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+function setStart(aNode, aOffset)
+{
+ document.getSelection().getRangeAt(0).setStart(aNode, aOffset);
+}
+
+function setStartBefore(aNode, aOffset)
+{
+ document.getSelection().getRangeAt(0).setStartBefore(aNode);
+}
+
+function setStartAfter(aNode, aOffset)
+{
+ document.getSelection().getRangeAt(0).setStartAfter(aNode);
+}
+
+// Range.setStart*() should work same as collapse if specified start position is after its end.
+[{ func: setStart, doingDescription: "Range.setStart()" },
+ { func: setStartAfter, doingDescription: "Range.setStartAfter()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticBefore.textNode);
+ aTest.func(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with end of the first text node of 'editor' (before the selection) when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.textNode);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (before the selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ aTest.func(staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticInEditor' (before the selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticInEditor.textNode);
+ aTest.func(innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'innerEditor' (before the selection) when active element is the <body> and selection is in 'staticInEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ aTest.func(staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticAfter' (before the selection) when active element is 'innerEditor'");
+
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ aTest.func(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with end of the first text node of 'editor' (before the collapsed selection) when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().collapse(editor.textNode, 0);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (before the collapsed selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(outerEditor.textNode, 0);
+ aTest.func(staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticInEditor' (before the collapsed selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(staticInEditor.textNode, 0);
+ aTest.func(innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'innerEditor' (before the collapsed selection) when active element is the <body> and selection is in 'staticInEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(innerEditor.textNode, 0);
+ aTest.func(staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticAfter' (before the collapsed selection) when active element is 'innerEditor'");
+
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, 0);
+ aTest.func(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with end of the first text node of 'editor' (before the selection, between start of the first text node of 'staticBefore' and start of the first text node of 'editor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, 0);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (before the selection, between start of the first text node of 'editor' and start of the first text node of 'outerEditor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ aTest.func(staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticInEditor' (before the selection, between start of the first text node of 'outerEditor' and start of the first text node of 'staticInEditor') when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ aTest.func(innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'innerEditor' (before the selection, between start of the first text node of 'outerEditor' and start of the first text node of 'innerEditor') when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ aTest.func(innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'innerEditor' (before the selection, between start of the first text node of 'staticInEditor' and start of the first text node of 'innerEditor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, 0);
+ aTest.func(staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with end of the first text node of 'staticAfter' (before the selection, between start of the first text node of 'innerEditor' and start of the first text node of 'staticAfter') when active element is the <body>");
+});
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticBefore.textNode);
+ setStartBefore(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Range.setStartBefore() with the first text node of 'editor' (before the selection) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.textNode);
+ setStartBefore(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Range.setStartBefore() with the first text node of 'outerEditor' (before the selection) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ setStartBefore(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Range.setStartBefore() with the first text node of 'innerEditor' (before the selection) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ setStartBefore(staticAfter.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Range.setStartBefore() with the first text node of 'innerEditor' (before the selection) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticAfter.textNode);
+ setStartBefore(anchor.textNode);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Range.setStartBefore() with the first text node of 'anchor' (before the selection) when active element is the <body>");
+
+// Range.setStart*() should blur focused editing host when it expands selection to outside of it.
+[{ func: setStart, doingDescription: "Range.setStart()" },
+ { func: setStartAfter, doingDescription: "Range.setStartAfter()" },
+ { func: setStartBefore, doingDescription: "Range.setStartBefore()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().collapse(editor.textNode, editor.textLength);
+ aTest.func(staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with end of the first text node of 'staticBefore' (before the collapsed selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(outerEditor.textNode, outerEditor.textLength);
+ aTest.func(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'editor' (before the collapsed selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(innerEditor.textNode, innerEditor.textLength);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (before the collapsed selection) when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(innerEditor.textNode, innerEditor.textLength);
+ aTest.func(staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticInEditor' (before the collapsed selection) when active element is 'innerEditor'");
+
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.textNode);
+ aTest.func(staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with end of the first text node of 'staticBefore' (before the selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ aTest.func(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'editor' (before the selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (before the selection) when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ aTest.func(staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticInEditor' (before the selection) when active element is 'innerEditor'");
+});
+
+// Range.setStart*() should move focus to an editing host when the range is shrunken into it.
+[{ func: setStart, doingDescription: "Range.setStart()" },
+ { func: setStartBefore, doingDescription: "Range.setStartBefore()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ aTest.func(editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'editor' (shrunken into 'editor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (shrunken into 'outerEditor') when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ aTest.func(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'staticInEditor' (shrunken into 'staticInEditor') when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ aTest.func(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with start of the first text node of 'innerEditor' (shrunken into 'innerEditor') when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ aTest.func(anchor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with start of the first text node of 'anchor' (shrunken into 'anchor') when active element is the <body>");
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_addRange.html b/testing/web-platform/mozilla/tests/focus/Selection_addRange.html
new file mode 100644
index 0000000000..d94e688158
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_addRange.html
@@ -0,0 +1,1242 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.addRange()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+/**
+ * NOTE: When you add/modify something in this file, you should add same test to Selection_setBaseAndExtent.html too.
+ */
+
+
+// Selection.addRange() with collapsed range.
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'staticBefore' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'staticInEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'innerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'staticAfter' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'anchor' when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'staticBefore' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'outerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'innerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'staticAfter' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'anchor' when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'staticBefore' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'editor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'outerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'innerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'staticAfter' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'anchor' when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'staticBefore' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'editor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'outerEditor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'staticInEditor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'innerEditor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'staticAfter' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'anchor' when active element is the <body> and selection is in 'staticInEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'staticBefore' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'editor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'outerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'staticAfter' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'anchor' when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with collapsed range at start of the first text node of 'staticBefore' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'editor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'outerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'innerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with collapsed range at start of the first text node of 'staticAfter' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with collapsed range at start of the first text node of 'anchor' when active element is 'anchor'");
+
+// Selection.addRange() with non-collapsed range in a node.
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(staticBefore.textNode, staticBefore.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in the first text node of 'staticBefore' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'staticInEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'innerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'staticAfter' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'anchor' when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(staticBefore.textNode, staticBefore.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in the first text node of 'staticBefore' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'outerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'staticInEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'innerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'staticAfter' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'anchor' when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(staticBefore.textNode, staticBefore.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in the first text node of 'staticBefore' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'editor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'outerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'innerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'staticAfter' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'anchor' when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(staticBefore.textNode, staticBefore.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in the first text node of 'staticBefore' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'editor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'outerEditor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'staticInEditor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'innerEditor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'staticAfter' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'anchor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(staticBefore.textNode, staticBefore.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in the first text node of 'staticBefore' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'editor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'outerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'staticInEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'staticAfter' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'anchor' when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(staticBefore.textNode, staticBefore.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in the first text node of 'staticBefore' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'editor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'outerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'staticInEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'innerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'staticAfter' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'anchor' when active element is 'anchor'");
+
+// Selection.addRange() with a range across editing host boundary.
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'anchor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_addRange_in_iframe.html b/testing/web-platform/mozilla/tests/focus/Selection_addRange_in_iframe.html
new file mode 100644
index 0000000000..71b7c26c46
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_addRange_in_iframe.html
@@ -0,0 +1,63 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move and auto scroll tests caused by a call of Selection.addRange() into a contenteditable element in iframe</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<iframe srcdoc="<div style='height: 1000px;'></div><div id='editor' contenteditable>editor</div>" style="height: 500px;"></iframe>
+<script>
+"use strict";
+setup({explicit_done:true});
+
+window.onload = ()=>{
+ test(function() {
+ var subDocument = document.querySelector("iframe").contentDocument;
+ var editorInFrame = subDocument.getElementById("editor");
+ var range = subDocument.createRange();
+ range.setStart(editorInFrame, 0);
+ var selection = subDocument.getSelection();
+ selection.removeAllRanges();
+ document.documentElement.scrollTop = 0;
+ subDocument.documentElement.scrollTop = 0;
+ selection.addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ assert_equals(subDocument.activeElement, editorInFrame);
+ assert_equals(subDocument.documentElement.scrollTop, 0);
+ }, "Moving selection into inactive contenteditable element in non-focused document shouldn't cause scrolling");
+
+ test(function() {
+ var iframe = document.querySelector("iframe");
+ var subDocument = iframe.contentDocument;
+ var selection = subDocument.getSelection();
+
+ // Reset selection in <iframe>
+ var editorInFrame = subDocument.getElementById("editor");
+ editorInFrame.blur();
+ selection.removeAllRanges();
+ var range = document.createRange();
+ range.setStart(subDocument.body, 0);
+ selection.addRange(range);
+ subDocument.documentElement.scrollTop = 0;
+
+ // Move focus to the <iframe>
+ iframe.contentWindow.focus();
+ document.documentElement.scrollTop = 0;
+ assert_equals(document.activeElement, iframe);
+ assert_equals(subDocument.activeElement, subDocument.body);
+ assert_equals(subDocument.documentElement.scrollTop, 0);
+
+ range = subDocument.createRange();
+ range.setStart(editorInFrame, 0);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ assert_equals(document.activeElement, iframe);
+ assert_equals(document.documentElement.scrollTop, 0);
+ assert_equals(subDocument.activeElement, editorInFrame);
+ assert_equals(subDocument.documentElement.scrollTop, 0);
+ }, "Moving selection into inactive contenteditable element in focused document shouldn't cause scrolling");
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe.html b/testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe.html
new file mode 100644
index 0000000000..e06e9cd17a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe.html
@@ -0,0 +1,67 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.addRange() into iframe</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body onload="doTest()">
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p>Here is an iframe:</p>
+<iframe src="Selection_addRange_into_iframe_iframe.html"></iframe>
+<script>
+"use strict";
+
+setup({explicit_done:true});
+
+function doTest() {
+ var selection = document.getSelection();
+ var childDocument = document.getElementsByTagName("iframe")[0].contentDocument;
+ var childSelection = childDocument.getSelection();
+ test(function() {
+ selection.collapse(document.body.firstChild, 0);
+ childSelection.collapse(childDocument.body.firstChild, 0);
+
+ document.documentElement.scrollTop = 0;
+ childDocument.documentElement.scrollTop = 0;
+ childSelection.removeAllRanges();
+ var range = childDocument.createRange();
+ range.selectNodeContents(childDocument.getElementById("editor1"));
+ childSelection.addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(childDocument.activeElement, childDocument.getElementById("editor1"));
+ assert_equals(document.documentElement.scrollTop, 0);
+ assert_equals(childDocument.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor1' in the <iframe> after Selection.addRange() but parent's active document should be the <body>");
+ test(function() {
+ selection.collapse(document.body.firstChild, 0);
+ childSelection.collapse(childDocument.getElementById("editor1").firstChild, 0);
+
+ document.documentElement.scrollTop = 0;
+ childDocument.documentElement.scrollTop = 0;
+ childSelection.removeAllRanges();
+ var range = childDocument.createRange();
+ range.selectNodeContents(childDocument.getElementById("editor2"));
+ childSelection.addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(childDocument.activeElement, childDocument.getElementById("editor2"));
+ assert_equals(document.documentElement.scrollTop, 0);
+ assert_equals(childDocument.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor2' in the <iframe> after Selection.addRange() but parent's active document should be the <body>");
+ test(function() {
+ selection.collapse(document.body.firstChild, 0);
+ childSelection.collapse(childDocument.getElementById("editor2").firstChild, 0);
+
+ document.documentElement.scrollTop = 0;
+ childDocument.documentElement.scrollTop = 0;
+ childSelection.removeAllRanges();
+ var range = childDocument.createRange();
+ range.selectNodeContents(childDocument.getElementById("non-editor"));
+ childSelection.addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(childDocument.activeElement, childDocument.getElementById("editor2"));
+ assert_equals(document.documentElement.scrollTop, 0);
+ assert_equals(childDocument.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor2' in the <iframe> after Selection.addRange() to non-editable <div> and parent's active document should be the <body>");
+
+ done();
+}
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe_iframe.html b/testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe_iframe.html
new file mode 100644
index 0000000000..946d4aa5e7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe_iframe.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p>Here are two editors:</p>
+<div id="editor1" contenteditable>The first editor.</div>
+<div id="editor2" contenteditable>The second editor.</div>
+<div id="non-editor">The non-editable div.</div>
+</body>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_collapse.html b/testing/web-platform/mozilla/tests/focus/Selection_collapse.html
new file mode 100644
index 0000000000..2e3050d822
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_collapse.html
@@ -0,0 +1,148 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.collapse()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapse() not moving selection from first text node in the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.collapse() moving selection from first text node in the <body> to start of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapse() moving selection from first text node in the <body> to start of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapse() moving selection from first text node in the <body> to start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapse() moving selection from first text node in the <body> to start of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(anchor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapse() moving selection from first text node in the <body> to start of the first text node of 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapse() moving selection from first text node in 'outerEditor' to start of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapse() moving selection from first text node in 'outerEditor' to start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapse() moving selection from first text node in 'innerEditor' to start of the first text node of the <body>");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.collapse() moving selection from first text node in 'innerEditor' to start of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapse() moving selection from first text node in 'innerEditor' to start of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapse() moving selection from first text node in 'innerEditor' to start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(anchor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapse() moving selection from first text node in 'innerEditor' to start of the first text node of 'anchor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_collapseToEnd.html b/testing/web-platform/mozilla/tests/focus/Selection_collapseToEnd.html
new file mode 100644
index 0000000000..f768782650
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_collapseToEnd.html
@@ -0,0 +1,134 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.collapseToEnd()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapseToEnd() with selection between start of the first text node of 'anchor' and end of the first text node of 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapseToEnd() with selection between start of the first text node of 'staticBefore' and end of the first text node of 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.collapseToEnd() with selection between start of the first text node of 'editor' and end of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapseToEnd() with selection between start of the first text node of 'innerEditor' and end of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.collapseToEnd() with selection between start of the first text node of 'anchor' and end of the first text node of 'anchor' and 'anchor' has focus before the call");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.collapseToEnd() with selection between start of the first text node of 'staticBefore' and end of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapseToEnd() with selection between start of the first text node of 'editor' and end of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapseToEnd() with selection between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_collapseToStart.html b/testing/web-platform/mozilla/tests/focus/Selection_collapseToStart.html
new file mode 100644
index 0000000000..238a436c1b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_collapseToStart.html
@@ -0,0 +1,142 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.collapseToStart()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapseToStart() with selection between start of the first text node of 'anchor' and end of the first text node of 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.collapseToStart() with selection between start of the first text node of 'editor' and end of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapseToStart() with selection between start of the first text node of 'innerEditor' and end of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.collapseToStart() with selection between start of the first text node of 'anchor' and end of the first text node of 'anchor' and 'anchor' has focus before the call");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapseToStart() with selection between start of the first text node of 'staticBefore' and end of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapseToStart() with selection between start of the first text node of 'editor' and end of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapseToStart() with selection between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapseToStart() with selection between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapseToStart() with selection between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_extend.html b/testing/web-platform/mozilla/tests/focus/Selection_extend.html
new file mode 100644
index 0000000000..62136cc4a3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_extend.html
@@ -0,0 +1,189 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.extend()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().extend(editor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'staticBefore' to start of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().extend(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'staticBefore' to start of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().extend(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'staticBefore' to start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().extend(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'staticBefore' to start of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().extend(staticAfter.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'staticBefore' to start of the first text node of 'staticAfter'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().extend(anchor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'staticBefore' to start of the first text node of 'anchor'");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().extend(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.extend() from selection at start of the first text node of 'editor' to end of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().extend(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.extend() from selection at start of the first text node of 'editor' to start of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().extend(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.extend() from selection at start of the first text node of 'editor' to start of the first text node of 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().extend(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'outerEditor' to end of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().extend(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'outerEditor' to start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().extend(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'outerEditor' to start of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().extend(editor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'outerEditor' to start of the first text node of 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().extend(innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.extend() from selection at start of the first text node of 'innerEditor' to end of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().extend(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'innerEditor' to start of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().extend(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'innerEditor' to start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().extend(anchor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.extend() from selection at start of the first text node of 'innerEditor' to start of the first text node of 'anchor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().extend(anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.extend() from selection at start of the first text node of 'anchor' to end of the first text node of 'anchor' and 'anchor' has focus before the call");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ anchor.element.blur();
+ document.getSelection().extend(anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'anchor' to end of the first text node of 'anchor' and 'anchor' doesn't have focus before the call");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_removeAllRanges.html b/testing/web-platform/mozilla/tests/focus/Selection_removeAllRanges.html
new file mode 100644
index 0000000000..6e6854d44b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_removeAllRanges.html
@@ -0,0 +1,112 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.removeAllRanges()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange(staticBefore);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.removeAllRanges() when active element is the <body> and selection is at the start of the first text node of 'staticBefore'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.removeAllRanges() when active element is 'editor' and selection is at the start of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.removeAllRanges() when active element is 'outerEditor' and selection is at the start of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.removeAllRanges() when active element is the <body> and selection is at the start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.removeAllRanges() when active element is 'innerEditor' and selection is at the start of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticAfter);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.removeAllRanges() when active element is the <body> and selection is at the start of the first text node of 'staticAfter'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.removeAllRanges() when active element is 'anchor' and selection is at the start of the first text node of 'anchor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_removeRange.html b/testing/web-platform/mozilla/tests/focus/Selection_removeRange.html
new file mode 100644
index 0000000000..14e78381d2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_removeRange.html
@@ -0,0 +1,112 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.removeRange()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange(staticBefore);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.removeRange() to remove selected range at the start of the first text node of 'staticBefore' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.removeRange() to remove selected range at the start of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.removeRange() to remove selected range at the start of the first text node of 'outerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.removeRange() to remove selected range at the start of the first text node of 'staticInEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.removeRange() to remove selected range at the start of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticAfter);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.removeRange() to remove selected range at the start of the first text node of 'staticAfter' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.removeRange() to remove selected range at the start of the first text node of 'anchor' when active element is 'anchor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_selectAllChildren.html b/testing/web-platform/mozilla/tests/focus/Selection_selectAllChildren.html
new file mode 100644
index 0000000000..2753e60851
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_selectAllChildren.html
@@ -0,0 +1,254 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.selectAllChildren()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticBefore.element);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.selectAllChildren() to select the children of 'staticBefore' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(editor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(outerEditor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticInEditor.element);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.selectAllChildren() to select the children of 'staticInEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(innerEditor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'innerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(anchor.element);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.selectAllChildren() to select the children of 'anchor' when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(staticBefore.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'staticBefore' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(outerEditor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'outerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(staticInEditor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'staticInEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(innerEditor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'innerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(anchor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'anchor' when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(staticBefore.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'staticBefore' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(editor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'editor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'outerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(staticInEditor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'ouerEditor' after Selection.selectAllChildren() to select the children of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(innerEditor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'innerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(anchor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'anchor' when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(staticBefore.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'staticBefore' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(editor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'editor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(outerEditor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'outerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(staticInEditor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'staticInEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(anchor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'anchor' when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(staticBefore.element);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.selectAllChildren() to select the children of 'staticBefore' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(editor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'editor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(outerEditor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'outerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(staticInEditor.element);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.selectAllChildren() to select the children of 'staticInEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(innerEditor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'innerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(anchor.element);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.selectAllChildren() to select the children of 'anchor' when active element is 'anchor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_setBaseAndExtent.html b/testing/web-platform/mozilla/tests/focus/Selection_setBaseAndExtent.html
new file mode 100644
index 0000000000..7cfc110009
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_setBaseAndExtent.html
@@ -0,0 +1,926 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.setBaseAndExtent()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+/**
+ * NOTE: When you add/modify something in this file, you should add same test to Selection_addRange.html too.
+ */
+
+// Selection.setBaseAndExtent() with collapsed range.
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticBefore' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticInEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'innerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticAfter' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'anchor' when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticBefore' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'outerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'innerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticAfter' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'anchor' when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticBefore' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'editor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'outerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'innerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticAfter' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'anchor' when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticBefore' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'editor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'outerEditor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticInEditor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'innerEditor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticAfter' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'anchor' when active element is the <body> and selection is in 'staticInEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticBefore' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'editor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'outerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticAfter' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'anchor' when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, 0);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticBefore' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'editor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'outerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'innerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, 0);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticAfter' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, 0);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'anchor' when active element is 'anchor'");
+
+// Selection.setBaseAndExtent() with non-collapsed range in a node.
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in the first text node of 'staticBefore' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticInEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'innerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticAfter' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'anchor' when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in the first text node of 'staticBefore' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'outerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticInEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'innerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticAfter' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'anchor' when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in the first text node of 'staticBefore' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'editor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'outerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'innerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticAfter' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'anchor' when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in the first text node of 'staticBefore' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'editor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'outerEditor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticInEditor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'innerEditor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticAfter' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'anchor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in the first text node of 'staticBefore' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'editor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'outerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticInEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticAfter' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'anchor' when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in the first text node of 'staticBefore' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'editor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'outerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticInEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'innerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticAfter' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'anchor' when active element is 'anchor'");
+
+// Selection.setBaseAndExtent() with a range across editing host boundary.
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'anchor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/delegateFocus-is-focusable.html b/testing/web-platform/mozilla/tests/focus/delegateFocus-is-focusable.html
new file mode 100644
index 0000000000..35fd30f1f6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/delegateFocus-is-focusable.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.addRange()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="host"></div>
+<script>
+let host = document.getElementById("host");
+let shadow = host.attachShadow({ mode: "open", delegatesFocus: true });
+shadow.innerHTML = `<button>Focusable</button>`;
+
+test(function() {
+ assert_true(SpecialPowers.Services.focus.elementIsFocusable(host, 0), "host is focusable");
+ host.focus();
+ assert_equals(document.activeElement, host, "Host is focused");
+ assert_equals(shadow.activeElement, shadow.querySelector("button"), "Button is focused");
+ assert_true(SpecialPowers.Services.focus.elementIsFocusable(host, 0), "host is still focusable");
+}, "isElementFocusable with delegateFocus");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-different-site.html b/testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-different-site.html
new file mode 100644
index 0000000000..e765990145
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-different-site.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus() before iframe loaded different site</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+setup({explicit_done:true});
+window.onmessage = function(e) {
+ test(function() {
+ assert_equals(e.data, "PASS", "Check verdict");
+ }, "Check result");
+ w.close();
+ done();
+};
+var w = window.open("support/focus-before-iframe-loaded-different-site-outer.sub.html");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-same-site.html b/testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-same-site.html
new file mode 100644
index 0000000000..fd7c2bffa6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-same-site.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus() before iframe loaded same site</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+setup({explicit_done:true});
+window.onmessage = function(e) {
+ test(function() {
+ assert_equals(e.data, "PASS", "Check verdict");
+ }, "Check result");
+ w.close();
+ done();
+};
+var w = window.open("support/focus-before-iframe-loaded-same-site-outer.html");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-different-site.html b/testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-different-site.html
new file mode 100644
index 0000000000..58310fd687
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-different-site.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus() from next tick before iframe loaded different site</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+setup({explicit_done:true});
+window.onmessage = function(e) {
+ test(function() {
+ assert_equals(e.data, "PASS", "Check verdict");
+ }, "Check result");
+ w.close();
+ done();
+};
+var w = window.open("support/focus-next-tick-before-iframe-loaded-different-site-outer.sub.html");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-same-site.html b/testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-same-site.html
new file mode 100644
index 0000000000..01b467718c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-same-site.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus() from next tick before iframe loaded same site</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+setup({explicit_done:true});
+window.onmessage = function(e) {
+ test(function() {
+ assert_equals(e.data, "PASS", "Check verdict");
+ }, "Check result");
+ w.close();
+ done();
+};
+var w = window.open("support/focus-next-tick-before-iframe-loaded-same-site-outer.html");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/iframe-focus-event-after-iframe-gets-focus.html b/testing/web-platform/mozilla/tests/focus/iframe-focus-event-after-iframe-gets-focus.html
new file mode 100644
index 0000000000..82a1346ec6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/iframe-focus-event-after-iframe-gets-focus.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test focus event after iframe gets focus</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+function waitForMessage(target, checkFn) {
+ return new Promise(resolve => {
+ target.addEventListener("message", e => {
+ if (checkFn && !checkFn(e)) {
+ return;
+ }
+ resolve();
+ }, { once: true });
+ });
+}
+
+function start(w) {
+ w.postMessage("start", "*");
+}
+
+// This will send message to outer frame and also inner frame to ask them
+// send the log they collect back, the logs of outer and inner will be
+// concatenated.
+async function getLog(w) {
+ let log = "";
+ step_timeout(function() {
+ w.postMessage("getlog", "*");
+ }, 0);
+ await waitForMessage(window, (e) => {
+ log = e.data;
+ return true;
+ });
+ return log;
+}
+
+function runSingleTest(url, focusIframeFunction, expectedResult, description) {
+ promise_test(async t => {
+ let w = window.open(url);
+ t.add_cleanup(() => { w.close(); });
+ await waitForMessage(window, e => e.data === "ready");
+ start(w);
+ focusIframeFunction(w);
+ assert_equals(await getLog(w), expectedResult);
+ }, description);
+}
+
+function runTests(url, description) {
+ // Test calling iframe.focus();
+ runSingleTest(url, (w) => {
+ w.postMessage("iframefocus", "*");
+ }, "outerlog:windowblur,innerlog:windowfocus,",
+ description + " via calling iframe.focus()");
+
+ // Test calling iframe.contentWindow.focus();
+ runSingleTest(url, (w) => {
+ w.postMessage("iframecontentWindowfocus", "*");
+ }, "outerlog:windowblur,innerlog:windowfocus,",
+ description + " via calling iframe.contentWindow.focus()");
+
+ // Test calling window.focus() in iframe;
+ runSingleTest(url, (w) => {
+ w.postMessage("windowfocus", "*");
+ }, "outerlog:windowblur,innerlog:willfocuswindow,windowfocus,didfocuswindow,",
+ description + " via calling window.focus() in iframe");
+}
+
+// Test same site iframe
+runTests("support/iframe-focus-event-after-same-site-iframe-gets-focus-outer.html",
+ "Check iframe focus event after same site iframe gets focus");
+
+// Test different site iframe
+runTests("support/iframe-focus-event-after-different-site-iframe-gets-focus-outer.sub.html",
+ "Check iframe focus event after different site iframe gets focus");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-inner.html b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-inner.html
new file mode 100644
index 0000000000..bcf23627d2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-inner.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>focus() before iframe loaded different site</title>
+</head>
+<body>
+<script>
+ window.onload = function() {
+ parent.postMessage("onload", "*");
+ }
+ document.body.onfocus = function() {
+ parent.postMessage("onfocus", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-outer.sub.html b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-outer.sub.html
new file mode 100644
index 0000000000..e95fe7d292
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-outer.sub.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>focus() before iframe loaded different site</title>
+</head>
+<body>
+<script>
+var pendingTimeout = false;
+window.onmessage = function(e) {
+ if (e.data == "onload") {
+ if (pendingTimeout) {
+ return;
+ }
+ pendingTimeout = opener.step_timeout(function() {
+ opener.postMessage("FAIL missing onfocus", "*");
+ }, 2000);
+ return;
+ }
+ if (pendingTimeout) {
+ clearTimeout(pendingTimeout);
+ }
+ pendingTimeout = true;
+ if (e.data == "onfocus") {
+ // Test not upstreamed, because this even is a Firefoxism
+ // https://github.com/whatwg/html/issues/6209
+ opener.postMessage("PASS", "*");
+ return;
+ }
+ opener.postMessage("FAIL " + e.data, "*");
+}
+
+var iframe = document.createElement("iframe");
+iframe.src = "http://{{hosts[alt][www]}}:{{ports[http][0]}}/_mozilla/focus/support/focus-before-iframe-loaded-different-site-inner.html";
+document.body.appendChild(iframe);
+iframe.focus();
+if (document.activeElement != iframe) {
+ pendingTimeout = true;
+ opener.postMessage("FAIL activeElement", "*");
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-inner.html b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-inner.html
new file mode 100644
index 0000000000..3c277f078f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-inner.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>focus() before iframe loaded same site</title>
+</head>
+<body>
+<script>
+ window.onload = function() {
+ parent.postMessage("onload", "*");
+ }
+ document.body.onfocus = function() {
+ parent.postMessage("onfocus", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-outer.html b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-outer.html
new file mode 100644
index 0000000000..8c829d6d47
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-outer.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>focus() before iframe loaded same site</title>
+</head>
+<body>
+<script>
+var pendingTimeout = false;
+window.onmessage = function(e) {
+ if (e.data == "onload") {
+ if (pendingTimeout) {
+ return;
+ }
+ pendingTimeout = opener.step_timeout(function() {
+ opener.postMessage("FAIL missing onfocus", "*");
+ }, 2000);
+ return;
+ }
+ if (pendingTimeout) {
+ clearTimeout(pendingTimeout);
+ }
+ pendingTimeout = true;
+ if (e.data == "onfocus") {
+ // Test not upstreamed, because this even is a Firefoxism
+ // https://github.com/whatwg/html/issues/6209
+ opener.postMessage("PASS", "*");
+ return;
+ }
+ opener.postMessage("FAIL " + e.data, "*");
+}
+
+var iframe = document.createElement("iframe");
+iframe.src = "focus-before-iframe-loaded-same-site-inner.html";
+document.body.appendChild(iframe);
+iframe.focus();
+if (document.activeElement != iframe) {
+ pendingTimeout = true;
+ opener.postMessage("FAIL activeElement", "*");
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-inner.html b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-inner.html
new file mode 100644
index 0000000000..2c1b35f2b2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-inner.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>focus() from next tick before iframe loaded different site</title>
+</head>
+<body>
+<script>
+ window.onload = function() {
+ parent.postMessage("onload", "*");
+ }
+ document.body.onfocus = function() {
+ parent.postMessage("onfocus", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-outer.sub.html b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-outer.sub.html
new file mode 100644
index 0000000000..83a48e303d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-outer.sub.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>focus() from next tick before iframe loaded different site</title>
+</head>
+<body>
+<script>
+var pendingTimeout = false;
+window.onmessage = function(e) {
+ if (e.data == "tick") {
+ iframe.focus();
+ if (document.activeElement != iframe) {
+ if (pendingTimeout) {
+ clearTimeout(pendingTimeout);
+ }
+ pendingTimeout = true;
+ opener.postMessage("FAIL activeElement", "*");
+ }
+ return;
+ }
+
+ if (e.data == "onload") {
+ if (pendingTimeout) {
+ return;
+ }
+ pendingTimeout = opener.step_timeout(function() {
+ opener.postMessage("FAIL missing onfocus", "*");
+ }, 2000);
+ return;
+ }
+ if (pendingTimeout) {
+ clearTimeout(pendingTimeout);
+ }
+ pendingTimeout = true;
+ if (e.data == "onfocus") {
+ // Test not upstreamed, because this even is a Firefoxism
+ // https://github.com/whatwg/html/issues/6209
+ opener.postMessage("PASS", "*");
+ return;
+ }
+ opener.postMessage("FAIL " + e.data, "*");
+}
+
+var iframe = document.createElement("iframe");
+iframe.src = "http://{{hosts[alt][www]}}:{{ports[http][0]}}/_mozilla/focus/support/focus-next-tick-before-iframe-loaded-different-site-inner.html";
+document.body.appendChild(iframe);
+window.postMessage("tick", "*");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-inner.html b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-inner.html
new file mode 100644
index 0000000000..62add75ed9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-inner.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>focus() from next tick before iframe loaded same site</title>
+</head>
+<body>
+<script>
+ window.onload = function() {
+ parent.postMessage("onload", "*");
+ }
+ document.body.onfocus = function() {
+ parent.postMessage("onfocus", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-outer.html b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-outer.html
new file mode 100644
index 0000000000..a68d34039d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-outer.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>focus() from next tick before iframe loaded same site</title>
+</head>
+<body>
+<script>
+var pendingTimeout = false;
+window.onmessage = function(e) {
+ if (e.data == "tick") {
+ iframe.focus();
+ if (document.activeElement != iframe) {
+ if (pendingTimeout) {
+ clearTimeout(pendingTimeout);
+ }
+ pendingTimeout = true;
+ opener.postMessage("FAIL activeElement", "*");
+ }
+ return;
+ }
+
+ if (e.data == "onload") {
+ if (pendingTimeout) {
+ return;
+ }
+ pendingTimeout = opener.step_timeout(function() {
+ opener.postMessage("FAIL missing onfocus", "*");
+ }, 2000);
+ return;
+ }
+ if (pendingTimeout) {
+ clearTimeout(pendingTimeout);
+ }
+ pendingTimeout = true;
+ if (e.data == "onfocus") {
+ // Test not upstreamed, because this even is a Firefoxism
+ // https://github.com/whatwg/html/issues/6209
+ opener.postMessage("PASS", "*");
+ return;
+ }
+ opener.postMessage("FAIL " + e.data, "*");
+}
+
+var iframe = document.createElement("iframe");
+iframe.src = "focus-next-tick-before-iframe-loaded-same-site-inner.html";
+document.body.appendChild(iframe);
+window.postMessage("tick", "*");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-different-site-iframe-gets-focus-outer.sub.html b/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-different-site-iframe-gets-focus-outer.sub.html
new file mode 100644
index 0000000000..d69580237c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-different-site-iframe-gets-focus-outer.sub.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Iframe focus event after different site iframe gets focus outer</title>
+<iframe src="http://{{hosts[alt][www]}}:{{ports[http][0]}}/_mozilla/focus/support/iframe-focus-event-after-iframe-gets-focus-inner.html"></iframe>
+<script>
+let outerlog = "outerlog:";
+
+let iframe = document.querySelector("iframe");
+window.onmessage = function(e) {
+ if (e.data == "start") {
+ window.onfocus = function() {
+ outerlog += "windowfocus,";
+ };
+ } else if (e.data == "iframefocus") {
+ iframe.focus();
+ } else if (e.data == "iframecontentWindowfocus") {
+ iframe.contentWindow.focus();
+ } else if (e.data == "windowfocus") {
+ iframe.contentWindow.postMessage("windowfocus", "*");
+ } else if (e.data == "getlog") {
+ iframe.contentWindow.postMessage("getlog", "*");
+ } else {
+ opener.postMessage(outerlog + e.data, "*");
+ }
+};
+
+window.onload = function() {
+ window.onblur = function() {
+ outerlog += "windowblur,";
+ };
+
+ iframe.onfocus = function() {
+ outerlog += "iframefocus,";
+ };
+
+ iframe.onblur = function() {
+ outerlog += "iframeblur,";
+ };
+
+ opener.postMessage("ready", "*");
+};
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-iframe-gets-focus-inner.html b/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-iframe-gets-focus-inner.html
new file mode 100644
index 0000000000..64a360d248
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-iframe-gets-focus-inner.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Iframe focus event inner document</title>
+</head>
+<body>
+<h1>Inner</h1>
+<script>
+let innerlog = "innerlog:";
+
+window.onmessage = function(e) {
+ if (e.data == "windowfocus") {
+ innerlog += "willfocuswindow,";
+ window.focus();
+ innerlog += "didfocuswindow,";
+ } else if (e.data == "getlog") {
+ parent.postMessage(innerlog, "*");
+ }
+};
+
+window.onfocus = function() {
+ innerlog += "windowfocus,";
+};
+
+window.onblur = function() {
+ innerlog += "windowblur,";
+};
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-same-site-iframe-gets-focus-outer.html b/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-same-site-iframe-gets-focus-outer.html
new file mode 100644
index 0000000000..06040d6485
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-same-site-iframe-gets-focus-outer.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Iframe focus event after same site iframe gets focus outer</title>
+<iframe src="iframe-focus-event-after-iframe-gets-focus-inner.html"></iframe>
+<script>
+let outerlog = "outerlog:";
+
+let iframe = document.querySelector("iframe");
+window.onmessage = function(e) {
+ if (e.data == "start") {
+ window.onfocus = function() {
+ outerlog += "windowfocus,";
+ };
+ } else if (e.data == "iframefocus") {
+ iframe.focus();
+ } else if (e.data == "iframecontentWindowfocus") {
+ iframe.contentWindow.focus();
+ } else if (e.data == "windowfocus") {
+ iframe.contentWindow.postMessage("windowfocus", "*");
+ } else if (e.data == "getlog") {
+ iframe.contentWindow.postMessage("getlog", "*");
+ } else {
+ opener.postMessage(outerlog + e.data, "*");
+ }
+};
+
+window.onload = function() {
+ window.onblur = function() {
+ outerlog += "windowblur,";
+ };
+
+ iframe.onfocus = function() {
+ outerlog += "iframefocus,";
+ };
+
+ iframe.onblur = function() {
+ outerlog += "iframeblur,";
+ };
+
+ opener.postMessage("ready", "*");
+};
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/browsers/browsing-the-web/read-media/sandboxed-video.html b/testing/web-platform/mozilla/tests/html/browsers/browsing-the-web/read-media/sandboxed-video.html
new file mode 100644
index 0000000000..4c58514e66
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/browsers/browsing-the-web/read-media/sandboxed-video.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>Test load of media document in sandboxed iframe</title>
+<link rel="motivation" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1783601">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body></body>
+<script>
+promise_test(async () => {
+ const frame = document.createElement('iframe');
+ frame.sandbox = '';
+ frame.src =
+ // 'PartialContent' ensures that the entire video resource does not load
+ // in one fetch.
+ '/service-workers/service-worker/resources/fetch-access-control.py?'
+ + 'VIDEO&PartialContent';
+
+ document.body.appendChild(frame);
+ await new Promise(resolve => frame.onload = resolve);
+
+ const video = SpecialPowers.wrap(frame).contentDocument.body.childNodes[0];
+ video.muted = true; // to allow playback
+ return video.play();
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-01.html b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-01.html
new file mode 100644
index 0000000000..ed59c3ae99
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-01.html
@@ -0,0 +1,59 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name=viewport content=width=device-width>
+<title>Snap to a slider's tick marks by clicking near them</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/rendering.html#the-input-element-as-a-range-control">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1803118">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<input type=range list=tickmarks min=-5 max=35>
+<datalist id=tickmarks>
+ <option value=0></option>
+ <option value=3></option>
+</datalist>
+<script>
+ const range = document.querySelector("input[type=range]");
+ const step = 1;
+ promise_test(async function snapToTickMarks() {
+ const assertions = [[-3, "-3"], [-2, "0"], [1, "0"], [2, "3"], [5, "3"], [6, "6"]];
+ const rect = range.getBoundingClientRect();
+ const padding = 10;
+ const left = rect.left + padding;
+ const width = rect.width - 2 * padding;
+ const actions = new test_driver.Actions();
+ const min = parseInt(range.min);
+ const max = parseInt(range.max);
+ for (const assertion of assertions) {
+ const moveTo = (left + width * (assertion[0] - min) / (max - min)) | 0;
+ const expected = assertion[1];
+ await actions
+ .pointerMove(moveTo, rect.top)
+ .pointerDown()
+ .pointerUp()
+ .send();
+ assert_equals(range.value, expected);
+ }
+ });
+ promise_test(async function domDoesNotSnap() {
+ const startAt = -2;
+ range.value = startAt;
+ for (let expectedValue = startAt + 1; expectedValue <= 6; expectedValue++) {
+ range.stepUp();
+ assert_equals(parseInt(range.value), expectedValue);
+ }
+ });
+ promise_test(async function keyboardDoesNotSnap() {
+ const kArrowRight = "\uE014";
+ range.focus();
+ const startAt = -2;
+ range.value = startAt;
+ for (let expectedValue = startAt + 1; expectedValue <= 6; expectedValue++) {
+ await test_driver.send_keys(range, kArrowRight);
+ assert_equals(parseInt(range.value), expectedValue);
+ }
+ });
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-02.html b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-02.html
new file mode 100644
index 0000000000..061f34b3a7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-02.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name=viewport content=width=device-width>
+<title>Snap to an RTL slider's tick marks by clicking near them</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/rendering.html#the-input-element-as-a-range-control">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1803118">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<input type=range list=tickmarks min=-5 max=35 dir=rtl>
+<datalist id=tickmarks>
+ <option value=0></option>
+ <option value=3></option>
+</datalist>
+<script>
+ const range = document.querySelector("input[type=range]");
+ const step = 1;
+ promise_test(async function snapToRTLTickMarks() {
+ const assertions = [[-3, "-3"], [-2, "0"], [1, "0"], [2, "3"], [5, "3"], [6, "6"]];
+ const rect = range.getBoundingClientRect();
+ const padding = 10;
+ const right = rect.right - padding;
+ const width = rect.width - 2 * padding;
+ const actions = new test_driver.Actions();
+ const min = parseInt(range.min);
+ const max = parseInt(range.max);
+ for (const assertion of assertions) {
+ const moveTo = (right - width * (assertion[0] - min) / (max - min)) | 0;
+ const expected = assertion[1];
+ await actions
+ .pointerMove(moveTo, rect.top)
+ .pointerDown()
+ .pointerUp()
+ .send();
+ assert_equals(range.value, expected);
+ }
+ });
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-03.html b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-03.html
new file mode 100644
index 0000000000..9ee80199e4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-03.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name=viewport content=width=device-width>
+<title>Snap to a vertical slider's tick marks by clicking near them</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/rendering.html#the-input-element-as-a-range-control">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1803118">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<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>
+ input[type=range] {
+ writing-mode: vertical-lr;
+ }
+</style>
+<input type=range list=tickmarks min=-5 max=35>
+<datalist id=tickmarks>
+ <option value=0></option>
+ <option value=3></option>
+</datalist>
+<script>
+ const range = document.querySelector("input[type=range]");
+ promise_test(async function snapToVerticalTickMarks() {
+ const assertions = [[-3, "-3"], [-2, "0"], [1, "0"], [2, "3"], [5, "3"], [6, "6"]];
+ const rect = range.getBoundingClientRect();
+ const padding = 10;
+ const bottom = rect.bottom - padding;
+ const height = rect.height - 2 * padding;
+ const actions = new test_driver.Actions();
+ const min = parseInt(range.min);
+ const max = parseInt(range.max);
+ for (const assertion of assertions) {
+ const moveTo = (bottom - height * (assertion[0] - min) / (max - min)) | 0;
+ const expected = assertion[1];
+ await actions
+ .pointerMove(rect.left, moveTo)
+ .pointerDown()
+ .pointerUp()
+ .send();
+ assert_equals(range.value, expected);
+ }
+ });
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/form-submission-0/non-usv-filenames.window.js b/testing/web-platform/mozilla/tests/html/semantics/forms/form-submission-0/non-usv-filenames.window.js
new file mode 100644
index 0000000000..9b5aa88abb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/form-submission-0/non-usv-filenames.window.js
@@ -0,0 +1,95 @@
+// META: script=/html/semantics/forms/form-submission-0/enctypes-helper.js
+
+// This test is built on the same infrastructure as the WPT tests
+// urlencoded2.window.js, multipart-formdata.window.js and text-plain.window.js,
+// except modified because this file only tests the serialization of filenames.
+// See the enctypes-helper.js file in the regular WPT test suite for more info.
+
+// The `urlencoded`, `multipart` and `textPlain` functions take a `file`
+// property rather than `name` and `value` properties, and the value of
+// `expected` is the serialization of the filename in the given encoding.
+
+function formSubmissionTemplate2(enctype, expectedBuilder) {
+ const formTestFn = formSubmissionTemplate(enctype, expectedBuilder);
+ return ({ file, formEncoding, expected, description }) =>
+ formTestFn({ name: "a", value: file, formEncoding, expected, description });
+}
+
+const urlencoded = formSubmissionTemplate2(
+ "application/x-www-form-urlencoded",
+ filename => `a=${filename}`
+);
+const multipart = formSubmissionTemplate2(
+ "multipart/form-data",
+ (filename, serialized) => {
+ const boundary = serialized.split("\r\n")[0];
+ return [
+ boundary,
+ `Content-Disposition: form-data; name="a"; filename="${filename}"`,
+ "Content-Type: text/plain",
+ "",
+ "", // File contents
+ `${boundary}--`,
+ "",
+ ].join("\r\n");
+ }
+);
+const textPlain = formSubmissionTemplate2(
+ "text/plain",
+ filename => `a=${filename}\r\n`
+);
+
+// -----------------------------------------------------------------------------
+
+(async () => {
+ // This creates an empty filesystem file with an arbitrary name and returns it
+ // as a File object with name "a\uD800b".
+ const file = SpecialPowers.unwrap(
+ await SpecialPowers.createFiles(
+ [{ data: "", options: { name: "a\uD800b", type: "text/plain" } }],
+ files => files[0]
+ )
+ );
+
+ urlencoded({
+ file,
+ formEncoding: "UTF-8",
+ expected: "a%EF%BF%BDb",
+ description: "lone surrogate in filename, UTF-8",
+ });
+
+ urlencoded({
+ file,
+ formEncoding: "windows-1252",
+ expected: "a%26%2365533%3Bb",
+ description: "lone surrogate in filename, windows-1252",
+ });
+
+ multipart({
+ file,
+ formEncoding: "UTF-8",
+ expected: "a\xEF\xBF\xBDb",
+ description: "lone surrogate in filename, UTF-8",
+ });
+
+ multipart({
+ file,
+ formEncoding: "windows-1252",
+ expected: "a&#65533;b",
+ description: "lone surrogate in filename, windows-1252",
+ });
+
+ textPlain({
+ file,
+ formEncoding: "UTF-8",
+ expected: "a\xEF\xBF\xBDb",
+ description: "lone surrogate in filename, UTF-8",
+ });
+
+ textPlain({
+ file,
+ formEncoding: "windows-1252",
+ expected: "a&#65533;b",
+ description: "lone surrogate in filename, windows-1252",
+ });
+})();
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/input-radio-key-navigation.html b/testing/web-platform/mozilla/tests/html/semantics/forms/input-radio-key-navigation.html
new file mode 100644
index 0000000000..2eee99ffaf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/input-radio-key-navigation.html
@@ -0,0 +1,61 @@
+<!doctype html>
+<title>Keyboard navigation on input type=radio</title>
+<meta charset=utf-8>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Ãlvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+
+<form dir="ltr">
+ <input type=radio name=whatever value=1>
+ <input type=radio name=whatever value=2>
+ <input type=radio name=whatever value=3>
+</form>
+<form dir="rtl">
+ <input type=radio name=whatever value=1>
+ <input type=radio name=whatever value=2>
+ <input type=radio name=whatever value=3>
+</form>
+<script>
+const KEYS = {
+ ArrowLeft: '\uE012',
+ ArrowUp: '\uE013',
+ ArrowRight: '\uE014',
+ ArrowDown: '\uE015',
+};
+
+function nextFocusIndex(currentIndex, length, forward) {
+ if (forward) {
+ return (currentIndex + 1) % length;
+ }
+ return (currentIndex == 0 ? length : currentIndex) - 1;
+}
+
+async function testMove(form, keyName, forward) {
+ let radios = form.querySelectorAll("input[type=radio]");
+ assert_equals(radios.length, 3, "Sanity check");
+
+ let focusIndex = 1;
+ radios[focusIndex].focus();
+
+ // Enough to wrap around, and one more to test the last active element too.
+ for (let i = 0; i <= radios.length; ++i) {
+ assert_equals(document.activeElement, radios[focusIndex], `Focused expected radio input (${focusIndex})`);
+ await test_driver.send_keys(document.activeElement, KEYS[keyName]);
+ focusIndex = nextFocusIndex(focusIndex, radios.length, forward);
+ }
+}
+
+promise_test(async t => {
+ for (let form of document.querySelectorAll("form")) {
+ const rtl = form.dir == "rtl";
+ await testMove(form, "ArrowDown", /* forward = */ true);
+ await testMove(form, "ArrowUp", /* forward = */ false);
+ await testMove(form, "ArrowLeft", /* forward = */ rtl);
+ await testMove(form, "ArrowRight", /* forward = */ !rtl);
+ }
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html b/testing/web-platform/mozilla/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html
new file mode 100644
index 0000000000..26c2a439ab
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html
@@ -0,0 +1,203 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<div id=target></div>
+<script>
+ var target = document.getElementById("target");
+ var sometext = "something";
+ var shorttext = "abc";
+ var elemData = [
+ {
+ desc: "textarea not in body",
+ factory: () => document.createElement("textarea"),
+ },
+ {
+ desc: "input not in body",
+ factory: () => document.createElement("input"),
+ },
+ {
+ desc: "textarea in body",
+ factory: () => document.body.appendChild(document.createElement("textarea")),
+ },
+ {
+ desc: "input in body",
+ factory: () => document.body.appendChild(document.createElement("input")),
+ },
+ {
+ desc: "textarea in body with parsed default value",
+ factory: () => {
+ target.innerHTML = "<textarea>abcdefghij</textarea>"
+ return target.querySelector("textarea");
+ },
+ },
+ {
+ desc: "input in body with parsed default value",
+ factory: () => {
+ target.innerHTML = "<input value='abcdefghij'>"
+ return target.querySelector("input");
+ },
+ },
+ {
+ desc: "focused textarea",
+ factory: () => {
+ var t = document.body.appendChild(document.createElement("textarea"));
+ t.focus();
+ return t;
+ },
+ },
+ {
+ desc: "focused input",
+ factory: () => {
+ var i = document.body.appendChild(document.createElement("input"));
+ i.focus();
+ return i;
+ },
+ },
+ {
+ desc: "focused then blurred textarea",
+ factory: () => {
+ var t = document.body.appendChild(document.createElement("textarea"));
+ t.focus();
+ t.blur();
+ return t;
+ },
+ },
+ {
+ desc: "focused then blurred input",
+ factory: () => {
+ var i = document.body.appendChild(document.createElement("input"));
+ i.focus();
+ i.blur()
+ return i;
+ },
+ },
+ ];
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ assert_equals(el.selectionStart, 0,
+ `Cursor start should be at beginning of ${data.desc}`);
+ assert_equals(el.selectionEnd, 0,
+ `Cursor end should be at beginning of ${data.desc}`);
+ }, `cursor location for initial value of ${data.desc}`);
+}
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ el.defaultValue = sometext;
+ // The "focused or has been focused" case behaves differently.
+ if (data.desc.includes("focused")) {
+ assert_equals(el.selectionStart, el.value.length,
+ `Cursor start should be at end of ${data.desc}`);
+ assert_equals(el.selectionEnd, el.value.length,
+ `Cursor end should be at end of ${data.desc}`);
+ } else {
+ assert_equals(el.selectionStart, 0,
+ `Cursor start should be at beginning of ${data.desc}`);
+ assert_equals(el.selectionEnd, 0,
+ `Cursor end should be at beginning of ${data.desc}`);
+ }
+ }, `cursor location after defaultValue set of ${data.desc}`);
+}
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ el.selectionStart = el.selectionStart;
+ el.defaultValue = sometext;
+ // The focused case behaves differently.
+ if (data.desc.includes("focused")) {
+ assert_equals(el.selectionStart, el.value.length,
+ `Cursor start should be at end of ${data.desc}`);
+ assert_equals(el.selectionEnd, el.value.length,
+ `Cursor end should be at end of ${data.desc}`);
+ } else {
+ assert_equals(el.selectionStart, 0,
+ `Cursor start should be at beginning of ${data.desc}`);
+ assert_equals(el.selectionEnd, 0,
+ `Cursor end should be at beginning of ${data.desc}`);
+ }
+ }, `cursor location after defaultValue set after no-op selectionStart set of ${data.desc}`);
+}
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ el.value = sometext;
+ assert_equals(el.selectionStart, sometext.length,
+ `Cursor start should be at end of ${data.desc}`);
+ assert_equals(el.selectionEnd, sometext.length,
+ `Cursor end should be at end of ${data.desc}`);
+ }, `cursor location after value set of ${data.desc}`);
+}
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ assert_true(sometext.length > 8,
+ "sometext too short, test won't work right");
+ el.defaultValue = sometext;
+ el.selectionStart = 1;
+ el.selectionEnd = 8;
+ assert_equals(el.selectionStart, 1, "We just set selectionStart!");
+ assert_equals(el.selectionEnd, 8, "We just set selectionEnd!");
+ assert_true(shorttext.length > 1,
+ "shorttext too short, test won't work right");
+ assert_true(shorttext.length < 8,
+ "shorttext too long, test won't work right");
+ el.defaultValue = shorttext;
+ // The "focused or has been focused" case behaves differently.
+ if (data.desc.includes("focused")) {
+ assert_equals(el.selectionStart, el.value.length,
+ `Cursor start should be at end of ${data.desc}`);
+ assert_equals(el.selectionEnd, el.value.length,
+ `Cursor end should be at end of ${data.desc}`);
+ } else {
+ assert_equals(el.selectionStart, 1,
+ "Shouldn't have moved selection start");
+ assert_equals(el.selectionEnd, shorttext.length,
+ "Should have adjusted selection end");
+ }
+ }, `selection location after defaultValue set to shorter than selectionEnd of ${data.desc}`);
+}
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ assert_true(sometext.length > 8,
+ "sometext too short, test won't work right");
+ el.defaultValue = sometext;
+ el.selectionStart = 5;
+ el.selectionEnd = 8;
+ assert_equals(el.selectionStart, 5, "We just set selectionStart!");
+ assert_equals(el.selectionEnd, 8, "We just set selectionEnd!");
+ assert_true(shorttext.length < 5,
+ "shorttext too long, test won't work right");
+ el.defaultValue = shorttext;
+ // The "focused or has been focused" case behaves differently.
+ if (data.desc.includes("focused")) {
+ assert_equals(el.selectionStart, el.value.length,
+ `Cursor start should be at end of ${data.desc}`);
+ assert_equals(el.selectionEnd, el.value.length,
+ `Cursor end should be at end of ${data.desc}`);
+ } else {
+ assert_equals(el.selectionStart, shorttext.length,
+ "Should have adjusted selection start");
+ assert_equals(el.selectionEnd, shorttext.length,
+ "Should have adjusted selection end");
+ }
+ }, `selection location after defaultValue set to shorter than selectionStart of ${data.desc}`);
+}
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01-notref.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01-notref.html
new file mode 100644
index 0000000000..67591468cf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01-notref.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<title>vertical range input with datalist reference</title>
+<input type="range" orient="vertical" min="-100" max="100" value="0" step="10" name="power" list="powers">
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01.html
new file mode 100644
index 0000000000..f1bd96f391
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>vertical range input with datalist</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range)">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=841942">
+<link rel="author" href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel="mismatch" href="range-tick-marks-01-notref.html">
+<input type="range" orient="vertical" min="-100" max="100" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="0">
+ <option value="-30">
+ <option value="30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02-notref.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02-notref.html
new file mode 100644
index 0000000000..59acde1482
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02-notref.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<title>max and min attributes applied to vertical range input with datalist reference</title>
+<input type="range" orient="vertical" min="-100" max="100" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="0">
+ <option value="-30">
+ <option value="30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02.html
new file mode 100644
index 0000000000..bd45631d4a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>max and min attributes applied to vertical range input with datalist</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range)">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#the-min-and-max-attributes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=841942">
+<link rel="author" href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel="mismatch" href="range-tick-marks-02-notref.html">
+<input type="range" orient="vertical" min="-40" max="40" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="0">
+ <option value="-30">
+ <option value="30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03-ref.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03-ref.html
new file mode 100644
index 0000000000..df473920ec
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03-ref.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>no vertical range tick marks for disabled datalist elements reference</title>
+<input type="range" orient="vertical" min="-100" max="100" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="-30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03.html
new file mode 100644
index 0000000000..83b5c2eb66
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>no vertical range tick marks for disabled datalist elements</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range)">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#htmldatalistelement">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-disabled">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=841942">
+<link rel="author" href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel="match" href="range-tick-marks-03-ref.html">
+<input type="range" orient="vertical" min="-100" max="100" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="0" disabled>
+ <option value="-30">
+ <option value="30" disabled>
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04-ref.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04-ref.html
new file mode 100644
index 0000000000..c2bf59e52b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04-ref.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>no range tick marks for vertical range tick marks that are step mismatches reference</title>
+<input type=range step=3 value=1 min=-5 max=5 list=degrees>
+<datalist id=degrees>
+ <option value=-2>
+ <option value=4>
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04.html
new file mode 100644
index 0000000000..a47334b411
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>no range tick marks for vertical range tick marks that are step mismatches</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range)">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1803303">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel=match href=range-tick-marks-04-ref.html>
+<input type=range step=3 value=1 min=-5 max=5 list=degrees>
+<datalist id=degrees>
+ <option value=-4>
+ <option value=-2>
+ <option value=0>
+ <option value=2>
+ <option value=4>
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/time-enter-keypress.html b/testing/web-platform/mozilla/tests/html/semantics/forms/time-enter-keypress.html
new file mode 100644
index 0000000000..2ffeb22cb4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/time-enter-keypress.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<title>Enter submits on time input</title>
+<meta charset=utf-8>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Ãlvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<form>
+ <input type="time" name="time">
+ <input type=submit>
+</form>
+<form>
+ <input type="date" name="date">
+ <input type=submit>
+</form>
+<form>
+ <input type="datetime-local" name="datetime-local">
+ <input type=submit>
+</form>
+<script>
+async function testEnterOnInput(form) {
+ const submitted = new Promise(resolve => {
+ form.addEventListener("submit", function(e) {
+ e.preventDefault();
+ resolve();
+ }, { once: true });
+ });
+ const input = form.querySelector("input");
+ input.focus();
+
+ const ENTER = "\uE007";
+ await new test_driver.Actions()
+ .keyDown(ENTER)
+ .keyUp(ENTER)
+ .send()
+ await submitted;
+ assert_true(true, "Form was submitted on enter for input " + input.type);
+}
+
+promise_test(async t => {
+ for (let form of document.querySelectorAll("form")) {
+ await testEnterOnInput(form, t);
+ }
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-circular.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-circular.html
new file mode 100644
index 0000000000..c99893a786
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-circular.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load dynamically imported async modules circular</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load async dynamic module")
+ window.addEventListener("error", scriptError);
+ function scriptError(e) {
+ // An error is expected
+ test.step(() => assert_true(e.message !== "FAIL"));
+ }
+ function scriptLoaded() {
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/async_dynamic_module_circular.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-error.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-error.html
new file mode 100644
index 0000000000..ff2ac06222
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-error.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load dynamically imported async modules which errors</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load dynamically imported async modules with error")
+ window.addEventListener("error", scriptError);
+ function scriptError(e) {
+ // An error is expected
+ test.step(() => assert_true(e.message !== "FAIL"));
+ }
+ function scriptLoaded() {
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/async_dynamic_module_error.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module.html
new file mode 100644
index 0000000000..2e1b267492
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load dynamically imported async modules</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load async dynamic module")
+ window.addEventListener("error", scriptError);
+ function scriptError(e) {
+ // An error is expected
+ test.step(() => assert_true(e.message !== "FAIL"));
+ }
+ function scriptLoaded() {
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/async_dynamic_module.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-circular.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-circular.html
new file mode 100644
index 0000000000..7540ebb5ec
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-circular.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load async modules circular</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load async module")
+ window.addEventListener("error", scriptError);
+ function scriptError(e) {
+ // An error is expected
+ test.step(() => assert_true(e.message !== "FAIL"));
+ test.done();
+ }
+ function scriptLoaded() {
+ test.step(() => assert_unreached("Should not load before error"));
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/async_module_circular.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()">
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-error.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-error.html
new file mode 100644
index 0000000000..c720611080
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-error.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load an async module</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load dynamic async module with syntax error")
+ window.addEventListener("error", scriptError);
+ function scriptError(e) {
+ // An error is expected
+ test.step(() => assert_true(e.message !== "FAIL"));
+ }
+ function scriptLoaded() {
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/async_module_error.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module.html
new file mode 100644
index 0000000000..566e2a379d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load dynamically imported async modules</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load async dynamic module")
+ function scriptLoaded() {
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/async_module.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()">
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/circular-module-import-with-syntax-error.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/circular-module-import-with-syntax-error.html
new file mode 100644
index 0000000000..e472656e34
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/circular-module-import-with-syntax-error.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load a module with circular imports and syntax error</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load module with circular imports and syntax error")
+ window.addEventListener("error", scriptError);
+ function scriptError() {
+ // An error is expected
+ test.done();
+ }
+ function scriptLoaded() {
+ test.step(() => assert_unreached("Should not load"));
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/circular_error1.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/create-module-script.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/create-module-script.html
new file mode 100644
index 0000000000..44337a0217
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/create-module-script.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Insert non-async module script</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ var test = async_test("Create module script")
+ var moduleRan = false;
+ function loadModule() {
+ var script = document.createElement("script");
+ script.onerror = function() {
+ test.step(() => assert_unreached("Should not get an error"));
+ test.done();
+ };
+ script.onload = function() {
+ test.step(() => assert_equals(moduleRan, true));
+ test.done();
+ };
+ script.type = "module";
+ script.src = "support/module.js";
+ script.async = false;
+ document.documentElement.appendChild(script);
+ }
+</script>
+<body onload='loadModule()'></body>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/mixed-content-import.https.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/mixed-content-import.https.html
new file mode 100644
index 0000000000..5342bd525c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/mixed-content-import.https.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Attempt to load a mixed content module graph</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Attempt to load a mixed content module graph")
+ window.addEventListener("error", testNoError);
+ function scriptError() {
+ // An error is expected
+ test.done();
+ }
+ function scriptLoaded() {
+ test.step(() => assert_unreached("Should not load"));
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ onerror="scriptError()"
+ onload="scriptLoaded()"
+ src="./support/mixed_import.js">
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/module-error-reporting.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/module-error-reporting.html
new file mode 100644
index 0000000000..e4c43060b6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/module-error-reporting.html
@@ -0,0 +1,89 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Insert non-async module script</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ const test = async_test("Test error event properties");
+
+ let errorCount = 0;
+ let eventCount = 0;
+ window.addEventListener("error", handleError);
+ window.addEventListener("load", handleLoaded);
+
+ function handleEvent(event) {
+ eventCount++;
+ test.step(() => assert_equals(typeof event, "object"));
+ test.step(() => assert_true(event instanceof Event));
+ }
+
+ function handleError(event) {
+ errorCount++;
+ test.step(() => assert_equals(typeof event, "object"));
+ test.step(() => assert_true(event instanceof ErrorEvent));
+ switch (errorCount) {
+ case 1:
+ test.step(() => assert_true(event.error instanceof SyntaxError));
+ test.step(() => assert_true(event.filename.endsWith("/bad_local_export.js")));
+ test.step(() => assert_equals(event.lineno, 3));
+ test.step(() => assert_equals(event.colno, 7));
+ break;
+ case 2:
+ test.step(() => assert_true(event.error instanceof TypeError));
+ test.step(() => assert_true(event.filename.endsWith("/import_resolve_failure.js")));
+ test.step(() => assert_equals(event.lineno, 2));
+ test.step(() => assert_equals(event.colno, 16));
+ break;
+ case 3:
+ test.step(() => assert_true(event.error instanceof TypeError));
+ test.step(() => assert_true(event.filename.endsWith("/indirect_export_resolve_failure.js")));
+ test.step(() => assert_equals(event.lineno, 2));
+ test.step(() => assert_equals(event.colno, 19));
+ break;
+ case 4:
+ test.step(() => assert_true(event.error instanceof SyntaxError));
+ test.step(() => assert_true(event.filename.endsWith("/missing_import.js")));
+ test.step(() => assert_equals(event.lineno, 2));
+ test.step(() => assert_equals(event.colno, 8));
+ break;
+ case 5:
+ test.step(() => assert_true(event.error instanceof SyntaxError));
+ test.step(() => assert_true(event.filename.endsWith("/missing_indirect_export.js")));
+ test.step(() => assert_equals(event.lineno, 2));
+ test.step(() => assert_equals(event.colno, 11));
+ break;
+ case 6:
+ test.step(() => assert_true(event.error instanceof SyntaxError));
+ test.step(() => assert_true(event.filename.endsWith("/module_eval_error.js")));
+ test.step(() => assert_equals(event.lineno, 3));
+ test.step(() => assert_equals(event.colno, 0));
+ break;
+ }
+ }
+
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+
+ function handleLoaded() {
+ test.step(() => assert_equals(eventCount, 2));
+ test.step(() => assert_equals(errorCount, 6));
+ test.done();
+ }
+
+</script>
+
+<!-- Errors that fire an event on the script element -->
+<script type="module" src="" onerror="handleEvent(event)"></script>
+<script type="module" src="./does_not_exist" onerror="handleEvent(event)"></script>
+
+<!-- Errors that fire an error event on the global -->
+<script type="module" src="./support/bad_local_export.js" onerror="handleEvent(event)"></script>
+<script type="module" src="./support/import_resolve_failure.js" onerror="testNoError()"></script>
+<script type="module" src="./support/indirect_export_resolve_failure.js" onerror="testNoError()"></script>
+<script type="module" src="./support/missing_import.js" onerror="testNoError()"></script>
+<script type="module" src="./support/missing_indirect_export.js" --onerror="testNoError()"></script>
+<script type="module" src="./support/module_eval_error.js" onerror="testNoError()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/reload-failed-module-script.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/reload-failed-module-script.html
new file mode 100644
index 0000000000..b95d3fe330
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/reload-failed-module-script.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Insert non-async module script</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ var test = async_test("Reload failed module script");
+
+ var errorCount = 0;
+ window.addEventListener("error", handleError);
+
+ function handleError() {
+ errorCount++;
+
+ if (errorCount == 1) {
+ reloadModule();
+ return;
+ }
+
+ test.step(() => assert_equals(errorCount, 2));
+ test.done();
+ }
+
+ function reloadModule() {
+ var script = document.createElement("script");
+ script.onerror = testNoError;
+ script.type = "module";
+ script.src = "support/missing_import.js";
+ script.async = false;
+ document.documentElement.appendChild(script);
+ }
+
+ function testNoError() {
+ test.step(() => assert_unreached("No event expect here"));
+ test.done();
+ }
+
+</script>
+<script type="module" src="support/missing_import.js" onerror="testNoError()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module.js
new file mode 100644
index 0000000000..238dc11402
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module.js
@@ -0,0 +1,11 @@
+var ns = await import('./async_test_module.js');
+if (ns.default !== 42) {
+ throw new Error("FAIL");
+}
+if (ns.x !== "named") {
+ throw new Error("FAIL");
+}
+if (ns.y !== 39) {
+ throw new Error("FAIL");
+}
+
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_circular.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_circular.js
new file mode 100644
index 0000000000..93bfc3aca8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_circular.js
@@ -0,0 +1,5 @@
+try {
+ var ns = await import('./async_test_module_circular_1.js');
+} catch(ns) {
+ throw Error("Fails as expected");
+};
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_error.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_error.js
new file mode 100644
index 0000000000..3832960108
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_error.js
@@ -0,0 +1,5 @@
+try {
+ await import('./bad_local_export.js');
+} catch(ns) {
+ throw Error("Fails as expected");
+};
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module.js
new file mode 100644
index 0000000000..34a590bcfc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module.js
@@ -0,0 +1,14 @@
+import nsPromise from './async_test_module.js';
+
+console.log("hi");
+let ns = await nsPromise;
+
+if (ns.default !== 42) {
+ throw new Error("FAIL");
+}
+if (ns.x !== "named") {
+ throw new Error("FAIL");
+}
+if (ns.y !== 39) {
+ throw new Error("FAIL");
+}
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_circular.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_circular.js
new file mode 100644
index 0000000000..a9dff71b17
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_circular.js
@@ -0,0 +1,3 @@
+import module from './async_test_module_circular_1.js';
+
+throw new Error("FAIL");
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_error.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_error.js
new file mode 100644
index 0000000000..3fa6991768
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_error.js
@@ -0,0 +1,4 @@
+import ns from "./async_test_module_failure.js";
+
+throw Error("FAIL");
+
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module.js
new file mode 100644
index 0000000000..201c76eedf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module.js
@@ -0,0 +1,12 @@
+await 1;
+await 2;
+export default await Promise.resolve(42);
+
+export const y = await 39;
+export const x = await 'named';
+
+// Bonus: this rejection is not unwrapped
+if (false) {
+ await Promise.reject(42);
+}
+
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_1.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_1.js
new file mode 100644
index 0000000000..2fdf67baca
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_1.js
@@ -0,0 +1,3 @@
+import module from './async_test_module_circular_2.js';
+
+export default {};
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_2.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_2.js
new file mode 100644
index 0000000000..0a09aacc39
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_2.js
@@ -0,0 +1,5 @@
+import module from './async_test_module_circular_3.js';
+
+await module.test();
+
+export default {};
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_3.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_3.js
new file mode 100644
index 0000000000..d815bc00e4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_3.js
@@ -0,0 +1,8 @@
+import module from './async_test_module_circular_1.js';
+
+export default {
+ async test() {
+ throw new Error("error thrown");
+ return Promise.resolve()
+ }
+};
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_failure.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_failure.js
new file mode 100644
index 0000000000..6f823f3003
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_failure.js
@@ -0,0 +1,6 @@
+export default 42;
+
+export const named = 'named';
+
+var rejection = Promise.reject(TypeError('I reject this!'));
+await rejection;
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/bad_local_export.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/bad_local_export.js
new file mode 100644
index 0000000000..0b3df8de32
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/bad_local_export.js
@@ -0,0 +1,3 @@
+// Attempt to export something that doesn't exist.
+
+export missing;
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error1.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error1.js
new file mode 100644
index 0000000000..f0310fe0b1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error1.js
@@ -0,0 +1,2 @@
+import { test2 } from "./circular_error2.js";
+import "./circular_error3.js";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error2.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error2.js
new file mode 100644
index 0000000000..5b163eab93
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error2.js
@@ -0,0 +1 @@
+import "./circular_error3.js";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error3.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error3.js
new file mode 100644
index 0000000000..2589defac8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error3.js
@@ -0,0 +1 @@
+import "./circular_error1.js";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/evaluation-order-setup.mjs b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/evaluation-order-setup.mjs
new file mode 100644
index 0000000000..d3f22e9ee0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/evaluation-order-setup.mjs
@@ -0,0 +1,19 @@
+globalThis.setup({allow_uncaught_exception: true});
+
+globalThis.log = [];
+
+globalThis.addEventListener("error",
+ event => globalThis.log.push("global-error", event.error.message));
+globalThis.addEventListener("onunhandledrejection",
+ event => globalThis.log.push('unhandled-promise-rejection'));
+globalThis.addEventListener("load",
+ event => globalThis.log.push("global-load"));
+
+globalThis.unreachable = function() {
+ globalThis.log.push("unreachable");
+}
+
+globalThis.test_load = async_test("Test evaluation order of modules");
+globalThis.testDone = globalThis.test_load.step_func_done(() => {
+ assert_array_equals(globalThis.log, globalThis.expectedLog);
+});
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/import_resolve_failure.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/import_resolve_failure.js
new file mode 100644
index 0000000000..a2e2875f20
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/import_resolve_failure.js
@@ -0,0 +1,2 @@
+// Import from an unresolvable module specifier.
+import {x} from "unresolvable";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/indirect_export_resolve_failure.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/indirect_export_resolve_failure.js
new file mode 100644
index 0000000000..282fd2ed62
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/indirect_export_resolve_failure.js
@@ -0,0 +1,2 @@
+// Export from an unresolvable module specifier.
+export {x, y} from "unresolvable";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_import.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_import.js
new file mode 100644
index 0000000000..885db02dde
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_import.js
@@ -0,0 +1,2 @@
+// Import a non-existent export to trigger instantiation failure.
+import {not_found} from "./module.js";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_indirect_export.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_indirect_export.js
new file mode 100644
index 0000000000..8494031b09
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_indirect_export.js
@@ -0,0 +1,2 @@
+// Import a non-existent export to trigger instantiation failure.
+export {x, not_found} from "./module.js";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import.js
new file mode 100644
index 0000000000..371018f1f4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import.js
@@ -0,0 +1 @@
+export * from "http://web-platform.test:8000/_mozilla/html/semantics/scripting-1/the-script-element/support/mixed_import2.js"
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import2.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import2.js
new file mode 100644
index 0000000000..60c6c8d8b0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import2.js
@@ -0,0 +1 @@
+export default "foo";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module.js
new file mode 100644
index 0000000000..1269686475
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module.js
@@ -0,0 +1,2 @@
+export let x = 42;
+moduleRan = true;
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module_eval_error.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module_eval_error.js
new file mode 100644
index 0000000000..3bd872b2e6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module_eval_error.js
@@ -0,0 +1,3 @@
+// A module that throws when evaluated.
+
+this = 0;
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/README.md b/testing/web-platform/mozilla/tests/html/syntax/charset/README.md
new file mode 100644
index 0000000000..0558ae1cd5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/README.md
@@ -0,0 +1,7 @@
+The tests in this directory intentionally differ from WebKit and Blink.
+
+These are case where using the real tree builder (with `noscript`) parsing
+as in the scripting enabled mode and with CDATA sections parsing with
+awareness of foreign content differs from WebKit's and Blink's behavior
+that works as if there was a pre-foreign content, pre-template tree builder
+running in the scripting disabled mode.
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-after-template.html b/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-after-template.html
new file mode 100644
index 0000000000..71ef9144e0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-after-template.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<head>
+<link rel="mismatch" href="references/in-noscript-after-template-ref.html">
+<noscript><template></template><meta charset="windows-1251"></noscript>
+</head>
+<body>
+<p>Meta in <code>noscript</code> after <code>template</code> (which is also inside the <code>noscript</code>).</p>
+<p>Test: æ</p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-ncr.html b/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-ncr.html
new file mode 100644
index 0000000000..645f151b26
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-ncr.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<head>
+<link rel="mismatch" href="references/in-noscript-ncr-ref.html">
+<noscript><meta charset="&#119;indows-1251"></noscript>
+</head>
+<body>
+<p>Meta with NCR in the encoding label in <code>noscript</code>.</p>
+<p>Test: æ</p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript.html b/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript.html
new file mode 100644
index 0000000000..e76054d618
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<head>
+<link rel="mismatch" href="references/in-noscript-ref.html">
+<noscript><meta charset="windows-1251"></noscript>
+</head>
+<body>
+<p>Meta in <code>noscript</code>.</p>
+<p>Test: æ</p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/in-svg-in-cdata-after-gt.html b/testing/web-platform/mozilla/tests/html/syntax/charset/in-svg-in-cdata-after-gt.html
new file mode 100644
index 0000000000..56783b7afc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/in-svg-in-cdata-after-gt.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<head>
+<link rel="mismatch" href="references/in-svg-in-cdata-after-gt-ref.html">
+</head>
+<body>
+<svg><![CDATA[><meta charset="windows-1251">]]></svg>
+<p>In SVG in CDATA after greater-than sign in the CDATA (after head).</p>
+<p>Test: æ</p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-after-template-ref.html b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-after-template-ref.html
new file mode 100644
index 0000000000..27defe54c0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-after-template-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<p>Meta in <code>noscript</code> after <code>template</code> (which is also inside the <code>noscript</code>).</p>
+<p>Test: ж</p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ncr-ref.html b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ncr-ref.html
new file mode 100644
index 0000000000..3581ab68db
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ncr-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<p>Meta with NCR in the encoding label in <code>noscript</code>.</p>
+<p>Test: ж</p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ref.html b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ref.html
new file mode 100644
index 0000000000..9bb9f24b88
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<p>Meta in <code>noscript</code>.</p>
+<p>Test: ж</p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-svg-in-cdata-after-gt-ref.html b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-svg-in-cdata-after-gt-ref.html
new file mode 100644
index 0000000000..2868f47fc2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-svg-in-cdata-after-gt-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<svg></svg>
+<p>In SVG in CDATA after greater-than sign in the CDATA (after head).</p>
+<p>Test: ж</p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/parsing/math-parse01.html b/testing/web-platform/mozilla/tests/html/syntax/parsing/math-parse01.html
new file mode 100644
index 0000000000..3aff716d9f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/parsing/math-parse01.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>math in html: parsing</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>math in html: parsing</h1>
+
+<div id="log" style="display:block"></div>
+
+<div style="display:none">
+<div><math id="m1"><mtext/></math></div>
+<div id="d1"><math><mrow/><mi/></math></div>
+<div id="d2"><math><mrow><mrow><mn>1</mn></mrow><mi>a</mi></mrow></math></div>
+<div id="d3">&lang;&rang;</div>
+<div id="d4">&Kopf;</div>
+<div id="d5"><math><semantics><mi>a</mi><annotation-xml><foo/><bar/></annotation-xml></semantics></math></div>
+<div id="d6"><math><semantics><mi>a</mi><annotation-xml encoding="text/html"><div></div></annotation-xml></semantics><mn/></math>
+</div>
+
+
+<script>
+
+test(function() {
+assert_equals(document.getElementById("m1"),document.getElementsByTagName("math")[0]);
+},"The id attribute should be recognised on math elements");
+
+test(function() {
+assert_equals(document.getElementById("d1").firstChild.nodeName,"math")
+},"The node name should be math");
+
+test(function() {
+assert_equals(document.getElementById("d1").firstChild.namespaceURI ,"http://www.w3.org/1998/Math/MathML")
+},"math should be in MathML Namespace");
+
+test(function() {
+assert_equals(document.getElementById("d1").firstChild.childNodes.length ,2)
+},"Math has 2 children (empty tag syntax)");
+
+test(function() {
+assert_equals(document.getElementById("d2").firstChild.childNodes.length ,1)
+},"Nested mrow elements should be parsed correctly");
+
+test(function() {
+assert_equals(document.getElementById("d3").firstChild.nodeValue ,"\u27E8\u27E9")
+},"Testing rang and lang entity code points");
+
+test(function() {
+assert_equals(document.getElementById("d4").firstChild.nodeValue ,"\uD835\uDD42")
+},"Testing Kopf (Plane 1) entity code point");
+
+test(function() {
+assert_equals(document.getElementById("d5").firstChild.firstChild.childNodes[1].childNodes.length ,2)
+},"Empty element tags in annotation-xml parsed as per XML.");
+
+test(function() {
+assert_equals(document.getElementById("d6").firstChild.childNodes.length ,2)
+},"html tags allowed in annotation-xml/@encoding='text/html'.");
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/indic-detection/LICENSE b/testing/web-platform/mozilla/tests/indic-detection/LICENSE
new file mode 100644
index 0000000000..604209a804
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/LICENSE
@@ -0,0 +1,359 @@
+Creative Commons Legal Code
+
+Attribution-ShareAlike 3.0 Unported
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
+ DAMAGES RESULTING FROM ITS USE.
+
+License
+
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
+COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
+COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
+AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
+TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
+BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
+CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
+CONDITIONS.
+
+1. Definitions
+
+ a. "Adaptation" means a work based upon the Work, or upon the Work and
+ other pre-existing works, such as a translation, adaptation,
+ derivative work, arrangement of music or other alterations of a
+ literary or artistic work, or phonogram or performance and includes
+ cinematographic adaptations or any other form in which the Work may be
+ recast, transformed, or adapted including in any form recognizably
+ derived from the original, except that a work that constitutes a
+ Collection will not be considered an Adaptation for the purpose of
+ this License. For the avoidance of doubt, where the Work is a musical
+ work, performance or phonogram, the synchronization of the Work in
+ timed-relation with a moving image ("synching") will be considered an
+ Adaptation for the purpose of this License.
+ b. "Collection" means a collection of literary or artistic works, such as
+ encyclopedias and anthologies, or performances, phonograms or
+ broadcasts, or other works or subject matter other than works listed
+ in Section 1(f) below, which, by reason of the selection and
+ arrangement of their contents, constitute intellectual creations, in
+ which the Work is included in its entirety in unmodified form along
+ with one or more other contributions, each constituting separate and
+ independent works in themselves, which together are assembled into a
+ collective whole. A work that constitutes a Collection will not be
+ considered an Adaptation (as defined below) for the purposes of this
+ License.
+ c. "Creative Commons Compatible License" means a license that is listed
+ at https://creativecommons.org/compatiblelicenses that has been
+ approved by Creative Commons as being essentially equivalent to this
+ License, including, at a minimum, because that license: (i) contains
+ terms that have the same purpose, meaning and effect as the License
+ Elements of this License; and, (ii) explicitly permits the relicensing
+ of adaptations of works made available under that license under this
+ License or a Creative Commons jurisdiction license with the same
+ License Elements as this License.
+ d. "Distribute" means to make available to the public the original and
+ copies of the Work or Adaptation, as appropriate, through sale or
+ other transfer of ownership.
+ e. "License Elements" means the following high-level license attributes
+ as selected by Licensor and indicated in the title of this License:
+ Attribution, ShareAlike.
+ f. "Licensor" means the individual, individuals, entity or entities that
+ offer(s) the Work under the terms of this License.
+ g. "Original Author" means, in the case of a literary or artistic work,
+ the individual, individuals, entity or entities who created the Work
+ or if no individual or entity can be identified, the publisher; and in
+ addition (i) in the case of a performance the actors, singers,
+ musicians, dancers, and other persons who act, sing, deliver, declaim,
+ play in, interpret or otherwise perform literary or artistic works or
+ expressions of folklore; (ii) in the case of a phonogram the producer
+ being the person or legal entity who first fixes the sounds of a
+ performance or other sounds; and, (iii) in the case of broadcasts, the
+ organization that transmits the broadcast.
+ h. "Work" means the literary and/or artistic work offered under the terms
+ of this License including without limitation any production in the
+ literary, scientific and artistic domain, whatever may be the mode or
+ form of its expression including digital form, such as a book,
+ pamphlet and other writing; a lecture, address, sermon or other work
+ of the same nature; a dramatic or dramatico-musical work; a
+ choreographic work or entertainment in dumb show; a musical
+ composition with or without words; a cinematographic work to which are
+ assimilated works expressed by a process analogous to cinematography;
+ a work of drawing, painting, architecture, sculpture, engraving or
+ lithography; a photographic work to which are assimilated works
+ expressed by a process analogous to photography; a work of applied
+ art; an illustration, map, plan, sketch or three-dimensional work
+ relative to geography, topography, architecture or science; a
+ performance; a broadcast; a phonogram; a compilation of data to the
+ extent it is protected as a copyrightable work; or a work performed by
+ a variety or circus performer to the extent it is not otherwise
+ considered a literary or artistic work.
+ i. "You" means an individual or entity exercising rights under this
+ License who has not previously violated the terms of this License with
+ respect to the Work, or who has received express permission from the
+ Licensor to exercise rights under this License despite a previous
+ violation.
+ j. "Publicly Perform" means to perform public recitations of the Work and
+ to communicate to the public those public recitations, by any means or
+ process, including by wire or wireless means or public digital
+ performances; to make available to the public Works in such a way that
+ members of the public may access these Works from a place and at a
+ place individually chosen by them; to perform the Work to the public
+ by any means or process and the communication to the public of the
+ performances of the Work, including by public digital performance; to
+ broadcast and rebroadcast the Work by any means including signs,
+ sounds or images.
+ k. "Reproduce" means to make copies of the Work by any means including
+ without limitation by sound or visual recordings and the right of
+ fixation and reproducing fixations of the Work, including storage of a
+ protected performance or phonogram in digital form or other electronic
+ medium.
+
+2. Fair Dealing Rights. Nothing in this License is intended to reduce,
+limit, or restrict any uses free from copyright or rights arising from
+limitations or exceptions that are provided for in connection with the
+copyright protection under copyright law or other applicable laws.
+
+3. License Grant. Subject to the terms and conditions of this License,
+Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
+perpetual (for the duration of the applicable copyright) license to
+exercise the rights in the Work as stated below:
+
+ a. to Reproduce the Work, to incorporate the Work into one or more
+ Collections, and to Reproduce the Work as incorporated in the
+ Collections;
+ b. to create and Reproduce Adaptations provided that any such Adaptation,
+ including any translation in any medium, takes reasonable steps to
+ clearly label, demarcate or otherwise identify that changes were made
+ to the original Work. For example, a translation could be marked "The
+ original work was translated from English to Spanish," or a
+ modification could indicate "The original work has been modified.";
+ c. to Distribute and Publicly Perform the Work including as incorporated
+ in Collections; and,
+ d. to Distribute and Publicly Perform Adaptations.
+ e. For the avoidance of doubt:
+
+ i. Non-waivable Compulsory License Schemes. In those jurisdictions in
+ which the right to collect royalties through any statutory or
+ compulsory licensing scheme cannot be waived, the Licensor
+ reserves the exclusive right to collect such royalties for any
+ exercise by You of the rights granted under this License;
+ ii. Waivable Compulsory License Schemes. In those jurisdictions in
+ which the right to collect royalties through any statutory or
+ compulsory licensing scheme can be waived, the Licensor waives the
+ exclusive right to collect such royalties for any exercise by You
+ of the rights granted under this License; and,
+ iii. Voluntary License Schemes. The Licensor waives the right to
+ collect royalties, whether individually or, in the event that the
+ Licensor is a member of a collecting society that administers
+ voluntary licensing schemes, via that society, from any exercise
+ by You of the rights granted under this License.
+
+The above rights may be exercised in all media and formats whether now
+known or hereafter devised. The above rights include the right to make
+such modifications as are technically necessary to exercise the rights in
+other media and formats. Subject to Section 8(f), all rights not expressly
+granted by Licensor are hereby reserved.
+
+4. Restrictions. The license granted in Section 3 above is expressly made
+subject to and limited by the following restrictions:
+
+ a. You may Distribute or Publicly Perform the Work only under the terms
+ of this License. You must include a copy of, or the Uniform Resource
+ Identifier (URI) for, this License with every copy of the Work You
+ Distribute or Publicly Perform. You may not offer or impose any terms
+ on the Work that restrict the terms of this License or the ability of
+ the recipient of the Work to exercise the rights granted to that
+ recipient under the terms of the License. You may not sublicense the
+ Work. You must keep intact all notices that refer to this License and
+ to the disclaimer of warranties with every copy of the Work You
+ Distribute or Publicly Perform. When You Distribute or Publicly
+ Perform the Work, You may not impose any effective technological
+ measures on the Work that restrict the ability of a recipient of the
+ Work from You to exercise the rights granted to that recipient under
+ the terms of the License. This Section 4(a) applies to the Work as
+ incorporated in a Collection, but this does not require the Collection
+ apart from the Work itself to be made subject to the terms of this
+ License. If You create a Collection, upon notice from any Licensor You
+ must, to the extent practicable, remove from the Collection any credit
+ as required by Section 4(c), as requested. If You create an
+ Adaptation, upon notice from any Licensor You must, to the extent
+ practicable, remove from the Adaptation any credit as required by
+ Section 4(c), as requested.
+ b. You may Distribute or Publicly Perform an Adaptation only under the
+ terms of: (i) this License; (ii) a later version of this License with
+ the same License Elements as this License; (iii) a Creative Commons
+ jurisdiction license (either this or a later license version) that
+ contains the same License Elements as this License (e.g.,
+ Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible
+ License. If you license the Adaptation under one of the licenses
+ mentioned in (iv), you must comply with the terms of that license. If
+ you license the Adaptation under the terms of any of the licenses
+ mentioned in (i), (ii) or (iii) (the "Applicable License"), you must
+ comply with the terms of the Applicable License generally and the
+ following provisions: (I) You must include a copy of, or the URI for,
+ the Applicable License with every copy of each Adaptation You
+ Distribute or Publicly Perform; (II) You may not offer or impose any
+ terms on the Adaptation that restrict the terms of the Applicable
+ License or the ability of the recipient of the Adaptation to exercise
+ the rights granted to that recipient under the terms of the Applicable
+ License; (III) You must keep intact all notices that refer to the
+ Applicable License and to the disclaimer of warranties with every copy
+ of the Work as included in the Adaptation You Distribute or Publicly
+ Perform; (IV) when You Distribute or Publicly Perform the Adaptation,
+ You may not impose any effective technological measures on the
+ Adaptation that restrict the ability of a recipient of the Adaptation
+ from You to exercise the rights granted to that recipient under the
+ terms of the Applicable License. This Section 4(b) applies to the
+ Adaptation as incorporated in a Collection, but this does not require
+ the Collection apart from the Adaptation itself to be made subject to
+ the terms of the Applicable License.
+ c. If You Distribute, or Publicly Perform the Work or any Adaptations or
+ Collections, You must, unless a request has been made pursuant to
+ Section 4(a), keep intact all copyright notices for the Work and
+ provide, reasonable to the medium or means You are utilizing: (i) the
+ name of the Original Author (or pseudonym, if applicable) if supplied,
+ and/or if the Original Author and/or Licensor designate another party
+ or parties (e.g., a sponsor institute, publishing entity, journal) for
+ attribution ("Attribution Parties") in Licensor's copyright notice,
+ terms of service or by other reasonable means, the name of such party
+ or parties; (ii) the title of the Work if supplied; (iii) to the
+ extent reasonably practicable, the URI, if any, that Licensor
+ specifies to be associated with the Work, unless such URI does not
+ refer to the copyright notice or licensing information for the Work;
+ and (iv) , consistent with Ssection 3(b), in the case of an
+ Adaptation, a credit identifying the use of the Work in the Adaptation
+ (e.g., "French translation of the Work by Original Author," or
+ "Screenplay based on original Work by Original Author"). The credit
+ required by this Section 4(c) may be implemented in any reasonable
+ manner; provided, however, that in the case of a Adaptation or
+ Collection, at a minimum such credit will appear, if a credit for all
+ contributing authors of the Adaptation or Collection appears, then as
+ part of these credits and in a manner at least as prominent as the
+ credits for the other contributing authors. For the avoidance of
+ doubt, You may only use the credit required by this Section for the
+ purpose of attribution in the manner set out above and, by exercising
+ Your rights under this License, You may not implicitly or explicitly
+ assert or imply any connection with, sponsorship or endorsement by the
+ Original Author, Licensor and/or Attribution Parties, as appropriate,
+ of You or Your use of the Work, without the separate, express prior
+ written permission of the Original Author, Licensor and/or Attribution
+ Parties.
+ d. Except as otherwise agreed in writing by the Licensor or as may be
+ otherwise permitted by applicable law, if You Reproduce, Distribute or
+ Publicly Perform the Work either by itself or as part of any
+ Adaptations or Collections, You must not distort, mutilate, modify or
+ take other derogatory action in relation to the Work which would be
+ prejudicial to the Original Author's honor or reputation. Licensor
+ agrees that in those jurisdictions (e.g. Japan), in which any exercise
+ of the right granted in Section 3(b) of this License (the right to
+ make Adaptations) would be deemed to be a distortion, mutilation,
+ modification or other derogatory action prejudicial to the Original
+ Author's honor and reputation, the Licensor will waive or not assert,
+ as appropriate, this Section, to the fullest extent permitted by the
+ applicable national law, to enable You to reasonably exercise Your
+ right under Section 3(b) of this License (right to make Adaptations)
+ but not otherwise.
+
+5. Representations, Warranties and Disclaimer
+
+UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
+OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
+KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
+INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
+FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
+LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
+WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
+OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
+
+6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
+LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
+ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
+ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
+BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. Termination
+
+ a. This License and the rights granted hereunder will terminate
+ automatically upon any breach by You of the terms of this License.
+ Individuals or entities who have received Adaptations or Collections
+ from You under this License, however, will not have their licenses
+ terminated provided such individuals or entities remain in full
+ compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
+ survive any termination of this License.
+ b. Subject to the above terms and conditions, the license granted here is
+ perpetual (for the duration of the applicable copyright in the Work).
+ Notwithstanding the above, Licensor reserves the right to release the
+ Work under different license terms or to stop distributing the Work at
+ any time; provided, however that any such election will not serve to
+ withdraw this License (or any other license that has been, or is
+ required to be, granted under the terms of this License), and this
+ License will continue in full force and effect unless terminated as
+ stated above.
+
+8. Miscellaneous
+
+ a. Each time You Distribute or Publicly Perform the Work or a Collection,
+ the Licensor offers to the recipient a license to the Work on the same
+ terms and conditions as the license granted to You under this License.
+ b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
+ offers to the recipient a license to the original Work on the same
+ terms and conditions as the license granted to You under this License.
+ c. If any provision of this License is invalid or unenforceable under
+ applicable law, it shall not affect the validity or enforceability of
+ the remainder of the terms of this License, and without further action
+ by the parties to this agreement, such provision shall be reformed to
+ the minimum extent necessary to make such provision valid and
+ enforceable.
+ d. No term or provision of this License shall be deemed waived and no
+ breach consented to unless such waiver or consent shall be in writing
+ and signed by the party to be charged with such waiver or consent.
+ e. This License constitutes the entire agreement between the parties with
+ respect to the Work licensed here. There are no understandings,
+ agreements or representations with respect to the Work not specified
+ here. Licensor shall not be bound by any additional provisions that
+ may appear in any communication from You. This License may not be
+ modified without the mutual written agreement of the Licensor and You.
+ f. The rights granted under, and the subject matter referenced, in this
+ License were drafted utilizing the terminology of the Berne Convention
+ for the Protection of Literary and Artistic Works (as amended on
+ September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
+ Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
+ and the Universal Copyright Convention (as revised on July 24, 1971).
+ These rights and subject matter take effect in the relevant
+ jurisdiction in which the License terms are sought to be enforced
+ according to the corresponding provisions of the implementation of
+ those treaty provisions in the applicable national law. If the
+ standard suite of rights granted under applicable copyright law
+ includes additional rights not granted under this License, such
+ additional rights are deemed to be included in the License; this
+ License is not intended to restrict the license of any rights under
+ applicable law.
+
+
+Creative Commons Notice
+
+ Creative Commons is not a party to this License, and makes no warranty
+ whatsoever in connection with the Work. Creative Commons will not be
+ liable to You or any party on any legal theory for any damages
+ whatsoever, including without limitation any general, special,
+ incidental or consequential damages arising in connection to this
+ license. Notwithstanding the foregoing two (2) sentences, if Creative
+ Commons has expressly identified itself as the Licensor hereunder, it
+ shall have all rights and obligations of Licensor.
+
+ Except for the limited purpose of indicating to the public that the
+ Work is licensed under the CCPL, Creative Commons does not authorize
+ the use by either party of the trademark "Creative Commons" or any
+ related trademark or logo of Creative Commons without the prior
+ written consent of Creative Commons. Any permitted use will be in
+ compliance with Creative Commons' then-current trademark usage
+ guidelines, as may be published on its website or otherwise made
+ available upon request from time to time. For the avoidance of doubt,
+ this trademark restriction does not form part of the License.
+
+ Creative Commons may be contacted at https://creativecommons.org/.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/README.txt b/testing/web-platform/mozilla/tests/indic-detection/README.txt
new file mode 100644
index 0000000000..9fa13a660a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/README.txt
@@ -0,0 +1,14 @@
+The text (non-markup/JavaScript) content of the files in this directory originates from Wikipedia and
+is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported license
+<https://creativecommons.org/licenses/by-sa/3.0/legalcode>.
+
+The content comes from the following articles (and their revisions):
+https://hi.wikipedia.org/w/index.php?title=%E0%A4%AE%E0%A4%82%E0%A4%97%E0%A4%B2_%E0%A4%97%E0%A5%8D%E0%A4%B0%E0%A4%B9&oldid=5105576
+https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711
+
+This directory tests that content meant for intentionally mis-encoded legacy Devanagari and Tamil fonts that Chrome's encoding detector knows about is detected as windows-1252. These fonts assign Devanagari or Tamil glyphs to code points that are symbols or Latin characters in windows-1252. In chardetng, the detection mechanism is determining that the content isn't in any chardetng-supported encoding and, therefore, the fallback is windows-1252.
+
+Tests are missing for the following fonts that Chrome knows about:
+LT TM Barani
+TMNews
+TamilWeb
diff --git a/testing/web-platform/mozilla/tests/indic-detection/baskar-jagran.html b/testing/web-platform/mozilla/tests/indic-detection/baskar-jagran.html
new file mode 100644
index 0000000000..c8de3ec2a4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/baskar-jagran.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+×¢»Ü »ëã âõÚ×¢ÇÜ ×ð¢ âêØü âð ¿õÍæ »ýã ãñÐ §â·ðUUU ÌÜ ·UUUè ¥æÖæ ÚçQ¤× ãñ, çÁâ ßÁã âð §âð "ÜæÜ »ýã" ·ðUUU Ùæ× âð Öè ÁæÙæ ÁæÌæ ãñÐ âõÚ×¢ÇÜ ·ðUUU »ýã Îô ÌÚã ·ðUUU ãôÌð ãñ¢ - "SÍÜèØ »ýã" çÁÙ·UUUæ ÌÜ ¥æÖæâèØ ãôÌæ ãñ ¥æñÚ
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://hi.wikipedia.org/w/index.php?title=%E0%A4%AE%E0%A4%82%E0%A4%97%E0%A4%B2_%E0%A4%97%E0%A5%8D%E0%A4%B0%E0%A4%B9&oldid=5105576">&#2350;&#2306;&#2327;&#2354; &#2327;&#2381;&#2352;&#2361;</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/elango.html b/testing/web-platform/mozilla/tests/indic-detection/elango.html
new file mode 100644
index 0000000000..175917cfd8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/elango.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+ÙNYëYÖš (Mars) s¡Veh|•T†‡¥ E·[ J£ ÚLÖ· Bh•. C‰ s¡V]ìX죋‰ SÖÁLÖY‰ ÚLÖ[ÖL E·[‰.Ceh|•T†‡¥ –Lop½V ÚLÖ[Ö] “RÄeh A|†RRÖL CWQëPÖY‰ p½V ÚLÖ[ÖL ÙNYëYÖš C£ef\‰. ÚU]Öyz]Ÿ CeÚLÖºeh ÚTÖŸeLP°¸Á ÙTVÛWo syz·[]Ÿ. CRÁ ÚU¼TWTë‘¥ LÖQTëT|• C£•“ BeÛN| CeÚLÖÛ[o ÙN‹Sì\UÖLe LÖy|f\‰.[12] CR]ÖÚXÚV CR¼ho ÙNYëYÖš GÁ\ ÙTVŸ H¼TyP‰. J£ “«NÖŸ ÚLÖ[Ö] CRÁ ÚU¼TWTë“ N‹‡W]ì¥ E·[‰ÚTÖ¥ fQëQe hZìLÛ[•, “«›¥ E·[‰ ÚTÖÁ\ G¡UÛXL·, T·[†RÖehL·, TÖÛXY]jL·, T]ì™zV ‰£YTë Th‡LÛ[• ÙLÖQëP‰. ÙNYëYÖ›Á rZ¼peLÖX˜•, T£Y UÖ¼\jLº• “«eh E·[ÛRTë ÚTÖÁ\ÛYÚV. s¡V UQëPX†‰· –L EVWUÖ] JXì•Tr UÛX•, –LTëÙT¡V ÙNjh†‰Të T·[†RÖehLº· JÁ\Ö] U¡]Ÿ T·[†RÖeh• ÙNYëYÖ›ÚXÚV E·[].
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711">&#2970;&#3014;&#2997;&#3021;&#2997;&#3006;&#2991;&#3021; (&#2965;&#3019;&#2995;&#3021;)</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/htchanakya.html b/testing/web-platform/mozilla/tests/indic-detection/htchanakya.html
new file mode 100644
index 0000000000..27c1ce5510
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/htchanakya.html
@@ -0,0 +1,12 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+×´»Ü »ëã âõÚU×´ÇÜ ×ð´ âêØü âð ¿õÍæ »ýã ãñÐ §âXWð ÌÜ XWè ¥æÖæ ÚUçQ× ãñ, çÁâ ßÁã âð §âð "ÜæÜ »ýã" XWð Ùæ× âð Öè ÁæÙæ ÁæÌæ ãñÐ âõÚU×´ÇÜ XWð »ýã Îô ÌÚUã XWð ãôÌð ãñ´ - "SÍÜèØ »ýã" çÁÙXWæ ÌÜ ¥æÖæâèØ ãôÌæ ãñ ¥õÚU "»ñâèØ »ýã" Áô ¥çÏXWÌÚU »ñâ âð çÙç×üÌ ãñ´Ð Âëfßè XWè ÌÚUã, ×´»Ü Öè °XW SÍÜèØ ÏÚUæÌÜ ßæÜæ »ýã ãñÐ §âXWæ ßæÌæßÚUJæ çßÚUÜ ãñÐ §âXWè âÌã Îð¹Ùð ÂÚU ¿´Îý×æ XWð »Ìü ¥õÚU Âëfßè XWð ÑßæÜæ×éç¹Øô´, ²ææçÅØô´, ÚUðç»SÌæÙ ¥õÚU VLWßèØ ÕYWèüÜè ¿ôçÅØô´ XWè ØæÎ çÎÜæÌè ãñÐ âõÚU×´ÇÜ XWæ âÕâð ¥çÏXW ªW¡¿æ ÂßüÌ, ¥ôܳÂâ ×ôiâ ×´»Ü ÂÚU ãè çSÍÌ ãñÐ âæÍ ãè çßàææÜÌ× XWñiØÙ ßñÜðâ ×ñÚUèÙðçÚUâ Öè Øãè´ ÂÚU çSÍÌ ãñÐ ¥ÂÙè Öõ»ôçÜXW çßàæðáÌæ¥ô´ XWð ¥Üæßæ, ×´»Ü XWæ ²æêJæüÙ XWæÜ ¥õÚU ×õâ×è ¿XWý Âëfßè XWð â×æÙ ãñ´Ð §â »ýã ÂÚU ÁèßÙ ãôÙð XWè â´ÖæßÙæ XWô ã×ðàææ âð ÂçÚUXWçËÂÌ çXWØæ »Øæ ãñÐ
+
+v~{z ×ð´ ×ðçÚUÙÚU y XWð mæÚUæ XWè ÂãÜè ×´»Ü ©ÇæÙ âð ÂãÜð ÌXW Øã ×æÙæ ÁæÌæ Íæ çXW »ýã XWè âÌã ÂÚU ÌÚUÜ ¥ßSÍæ ×ð´ ÁÜ ãô âXWÌæ ãñÐ Øã ãËXWð ¥õÚU »ãÚUð ÚU´» XWð ϦÕô´ XWè ¥æßçÌüXW âê¿Ùæ¥ô´ ÂÚU ¥æÏæçÚUÌ Íæ çßàæðá ÌõÚU ÂÚU, VLWßèØ ¥ÿææ´àæô´, Áô Ü´Õð ãôÙð ÂÚU â×éÎý ¥õÚU ×ãæmèÂô´ XWè ÌÚUã çιÌð ãñ´, XWæÜð striations XWè ÃØæGØæ XWéÀU ÂýðÿæXWô´ mæÚUæ ÂæÙè XWè çâ´¿æ§ü ÙãÚUô´ XWð MW ×ð´ XWè »Øè ãñÐ §Ù÷ âèÏè ÚUð¹æ¥ô´ XWè ×õÁêλè ÕæÎ ×ð´ çâh Ùãè´ ãô ÂæØè ¥õÚU Øð ×æÙæ »Øæ çXW Øð ÚUð¹æØð´ ×æµæ ÂýXWæàæèØ Öý× XWð ¥Üæßæ XWéÀU ¥õÚU Ùãè´ ãñ´Ð çYWÚU Öè, âõÚU ×´ÇÜ XWð âÖè »ýãô´ ×ð´ Âëfßè XWð ¥Üæßæ, ×´»Ü XWô ÁèßÙ çßSÌæÚU XWæ ×ãPßÂêJæü çßXWË ×æÙæ ÁæÌæ ãñÐ
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://hi.wikipedia.org/w/index.php?title=%E0%A4%AE%E0%A4%82%E0%A4%97%E0%A4%B2_%E0%A4%97%E0%A5%8D%E0%A4%B0%E0%A4%B9&oldid=5105576">&#2350;&#2306;&#2327;&#2354; &#2327;&#2381;&#2352;&#2361;</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/shreetam.html b/testing/web-platform/mozilla/tests/indic-detection/shreetam.html
new file mode 100644
index 0000000000..1fcf4ae40f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/shreetam.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+ö\ÆÁõ´ (Mars) `›¯USk®£zvÀ EÒÍ J¸ ÷PõÒ BS®. Cx `›¯Û¼¸¢x |õßPõÁx ÷PõÍõP EÒÍx.CUSk®£zvÀ ªPa]Ô¯ ÷PõÍõÚ ¦uÝUS AkzuuõP CµshõÁx ]Ô¯ ÷PõÍõP ö\ÆÁõ´ C¸UQÓx. ÷©ÚõmiÚº CU÷PõÐUS ÷£õºUPhÄÎß ö£¯øµa `mi²ÒÍÚº. Cuß ÷©Ø£µ¨¤À Põn¨£k® C¸®¦ BUø\k CU÷PõøÍa ö\¢{Ó©õPU PõmkQÓx.[12] CuÚõ÷»÷¯ CuØSa ö\ÆÁõ´ GßÓ ö£¯º HØ£mhx. J¸ ¦Â\õº ÷PõÍõÚ Cuß ÷©Ø£µ¨¦ \¢vµÛÀ EÒÍx÷£õÀ QsnU SÈPøͲ®, ¦Â°À EÒÍx ÷£õßÓ G›©ø»PÒ, £ÒÍzuõUSPÒ, £õø»ÁÚ[PÒ, £Û‰i¯ x¸Á¨ £SvPøͲ® öPõshx. ö\ÆÁõ°ß _ÇØ]UPõ»•®, £¸Á ©õØÓ[PЮ ¦ÂUS EÒÍøu¨ ÷£õßÓøÁ÷¯. `›¯ ©sh»zxÒ ªP E¯µ©õÚ J¼®£_ ©ø»²®, ªP¨ö£›¯ ö\[Szx¨ £ÒÍzuõUSPÐÒ JßÓõÚ ©›Úº £ÒÍzuõUS® ö\ÆÁõ°÷»÷¯ EÒÍÚ.
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711">&#2970;&#3014;&#2997;&#3021;&#2997;&#3006;&#2991;&#3021; (&#2965;&#3019;&#2995;&#3021;)</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/tab.html b/testing/web-platform/mozilla/tests/indic-detection/tab.html
new file mode 100644
index 0000000000..1dc5ee85bc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/tab.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+ªêõ¢õ£ò¢ (Mars) Åó¤ò袰´ñ¢ðî¢î¤ô¢ à÷¢÷ å¼ «è£÷¢ Ý°ñ¢. Þ¶ Åó¤òù¤ô¤¼ï¢¶ ï£ù¢è£õ¶ «è£÷£è à÷¢÷¶.Þ袰´ñ¢ðî¢î¤ô¢ ñ¤èê¢ê¤ø¤ò «è£÷£ù ¹îÂ袰 Ü´î¢îî£è Þóí¢ì£õ¶ ê¤ø¤ò «è£÷£è ªêõ¢õ£ò¢ Þ¼è¢è¤ø¶. «ñù£ì¢®ùó¢ Þ被è£À袰 «ð£ó¢è¢èì¾÷¤ù¢ ªðò¬óê¢ Å좮»÷¢÷ùó¢. Þîù¢ «ñø¢ðóð¢ð¤ô¢ è£íð¢ð´ñ¢ Þ¼ñ¢¹ Ýè¢¬ê´ Þ被裬÷ê¢ ªêï¢ï¤øñ£èè¢ è£ì¢´è¤ø¶.[12] Þîù£«ô«ò Þîø¢°ê¢ ªêõ¢õ£ò¢ âù¢ø ªðòó¢ ãø¢ðì¢ì¶. å¼ ¹õ¤ê£ó¢ «è£÷£ù Þîù¢ «ñø¢ðó𢹠êï¢î¤óù¤ô¢ à÷¢÷¶«ð£ô¢ è¤í¢íè¢ °ö¤è¬÷»ñ¢, ¹õ¤ò¤ô¢ à÷¢÷¶ «ð£ù¢ø âó¤ñ¬ôè÷¢, ð÷¢÷î¢î£è¢°è÷¢, ð£¬ôõùé¢è÷¢, ðù¤Í®ò ¶¼õð¢ ð°î¤è¬÷»ñ¢ ªè£í¢ì¶. ªêõ¢õ£ò¤ù¢ ²öø¢ê¤è¢è£ôºñ¢, ð¼õ ñ£ø¢øé¢èÀñ¢ ¹õ¤è¢° à÷¢÷¬î𢠫ð£ù¢ø¬õ«ò. Åó¤ò ñí¢ìô÷¢ ñ¤è àòóñ£ù åô¤ñ¢ð² ñ¬ô»ñ¢, ñ¤èð¢ªðó¤ò ªê颰ð¢ ð÷¢÷î¢î£è¢°èÀ÷¢ åù¢ø£ù ñó¤ùó¢ ð÷¢÷î¢î£è¢°ñ¢ ªêõ¢õ£ò¤«ô«ò à÷¢÷ù.
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711">&#2970;&#3014;&#2997;&#3021;&#2997;&#3006;&#2991;&#3021; (&#2965;&#3019;&#2995;&#3021;)</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/tam.html b/testing/web-platform/mozilla/tests/indic-detection/tam.html
new file mode 100644
index 0000000000..8b4f9d0cbf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/tam.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+ªêšõ£Œ (Mars) ÅKò‚°´‹ðˆF™ àœ÷ å¼ «è£œ Ý°‹. Þ¶ ÅKòQL¼‰¶ è£õ¶ «è£÷£è àœ÷¶.Þ‚°´‹ðˆF™ Iè„CPò «è£÷£ù ¹î‚° Ü´ˆîî£è Þó‡ì£õ¶ CPò «è£÷£è ªêšõ£Œ Þ¼‚Aø¶. «ñù£†®ù˜ Þ‚«è£À‚° «ð£˜‚èì¾O¡ ªðò¬ó„ ņ®»œ÷ù˜. Þî¡ «ñŸðóŠH™ è£íŠð´‹ Þ¼‹¹ Ý‚¬ê´ Þ‚«è£¬÷„ ªê‰Gøñ£è‚ 裆´Aø¶.[12] Þîù£«ô«ò Þ„ ªêšõ£Œ â¡ø ªðò˜ ãŸð†ì¶. å¼ ¹M꣘ «è£÷£ù Þî¡ «ñŸðóŠ¹ ê‰FóQ™ àœ÷¶«ð£™ A‡í‚ °Nè¬÷»‹, ¹MJ™ àœ÷¶ «ð£¡ø âKñ¬ôèœ, ðœ÷ˆî£‚°èœ, ð£¬ôõùƒèœ, ðQÍ®ò ¶¼õŠ ð°Fè¬÷»‹ ªè£‡ì¶. ªêšõ£J¡ ²öŸC‚è£ôº‹, ð¼õ ñ£ŸøƒèÀ‹ ¹M‚° àœ÷¬îŠ «ð£¡ø¬õ«ò. ÅKò ñ‡ìôˆ¶œ Iè àòóñ£ù åL‹ð² ñ¬ô»‹, I芪ðKò ªêƒ°ˆ¶Š ðœ÷ˆî£‚°èÀœ å¡ø£ù ñKù˜ ðœ÷ˆî£‚°‹ ªêšõ£J«ô«ò àœ÷ù.
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711">&#2970;&#3014;&#2997;&#3021;&#2997;&#3006;&#2991;&#3021; (&#2965;&#3019;&#2995;&#3021;)</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/tboomi.html b/testing/web-platform/mozilla/tests/indic-detection/tboomi.html
new file mode 100644
index 0000000000..3976416449
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/tboomi.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+%NÊY”» (Mars) wÁVfji¶T¦§Å EÔ[ Jà &L”Ô Bj¶. C© wÁVàÆë© S”ßL”Y© &L”[”L EÔ[©.Cfji¶T¦§Å ·LotÛV &L”[”] ´Râfj Ai¦RR”L CW¡P”Y© tÛV &L”[”L %NÊY”» CÃfg\©. &U]”}~]À Cf&L”Øfj &T”ÀfLPÍÕß %TV+Wo w}~¾Ô[]À. CRß &UÚTW±²Å L”Q±Ti¶ Cö´ Bf+Ni Cf&L”+[o %N«¬\U”Lf L”}ig\©.[12] CR]”&X&V CRÚjo %NÊY”» Gß\ %TVÀ HÚT}P©. Jà ´ËN”À &L”[”] CRß &UÚTW±´ N«§WàÅ EÔ[©&T”Å g¡Qf jÐL+[¾¶, ´Ë¼Å EÔ[© &T”ß\ GÁU+XLÔ, TÔ[¦R”fjLÔ, T”+XY]lLÔ, Tàº~V ©ÃY± Tj§L+[¾¶ %L”¡P©. %NÊY”¼ß vZÚtfL”X¹¶, TÃY U”Ú\lLض ´Ëfj EÔ[+R± &T”ß\+Y&V. wÁV U¡PX¦©Ô ·L EVWU”] JƶTv U+X¾¶, ·L±%TÁV %Nlj¦©± TÔ[¦R”fjLØÔ Jß\”] UÁ]À TÔ[¦R”fj¶ %NÊY”¼&X&V EÔ[].
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711">&#2970;&#3014;&#2997;&#3021;&#2997;&#3006;&#2991;&#3021; (&#2965;&#3019;&#2995;&#3021;)</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/tscii.html b/testing/web-platform/mozilla/tests/indic-detection/tscii.html
new file mode 100644
index 0000000000..c9d47b4f9f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/tscii.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+¦ºùÅ¡ö (Mars) ÝâÂìÌÎõÀò¾¢ø ¯ûÇ ´Õ §¸¡û ¬Ìõ. þÐ ÝâÂɢĢÕóÐ ¿¡ý¸¡ÅÐ §¸¡Ç¡¸ ¯ûÇÐ.þìÌÎõÀò¾¢ø Á¢¸îº¢È¢Â §¸¡Ç¡É Ò¾ÛìÌ «Îò¾¾¡¸ þÃñ¼¡ÅÐ º¢È¢Â §¸¡Ç¡¸ ¦ºùÅ¡ö þÕ츢ÈÐ. §ÁÉ¡ðÊÉ÷ þ째¡ÙìÌ §À¡÷츼×Ç¢ý ¦À¨Ãî ÝðÊÔûÇÉ÷. þ¾ý §ÁüÀÃôÀ¢ø ¸¡½ôÀÎõ þÕõÒ ¬ì¨ºÎ þ째¡¨Çî ¦ºó¿¢ÈÁ¡¸ì ¸¡ðθ¢ÈÐ.[12] þ¾É¡§Ä§Â þ¾üÌî ¦ºùÅ¡ö ±ýÈ ¦ÀÂ÷ ²üÀð¼Ð. ´Õ ÒÅ¢º¡÷ §¸¡Ç¡É þ¾ý §ÁüÀÃôÒ ºó¾¢ÃÉ¢ø ¯ûÇЧÀ¡ø ¸¢ñ½ì ÌÆ¢¸¨ÇÔõ, ÒŢ¢ø ¯ûÇÐ §À¡ýÈ ±Ã¢Á¨Ä¸û, ÀûÇò¾¡ì̸û, À¡¨ÄÅÉí¸û, ÀÉ¢ãÊ ÐÕÅô À̾¢¸¨ÇÔõ ¦¸¡ñ¼Ð. ¦ºùš¢ý ÍÆüº¢ì¸¡ÄÓõ, ÀÕÅ Á¡üÈí¸Ùõ ÒÅ¢ìÌ ¯ûǨ¾ô §À¡ýȨŧÂ. Ýâ Áñ¼ÄòÐû Á¢¸ ¯ÂÃÁ¡É ´Ä¢õÀÍ Á¨ÄÔõ, Á¢¸ô¦Àâ ¦ºíÌòÐô ÀûÇò¾¡ì̸Ùû ´ýÈ¡É ÁâÉ÷ ÀûÇò¾¡ìÌõ ¦ºùš¢§Ä§Â ¯ûÇÉ.
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711">&#2970;&#3014;&#2997;&#3021;&#2997;&#3006;&#2991;&#3021; (&#2965;&#3019;&#2995;&#3021;)</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-0.html b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-0.html
new file mode 100644
index 0000000000..584620392c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-0.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<link rel="match" href="prefs-false.html"/>
+<div id="result"></div>
+<script>
+// This can be any pref, as long as it doesn't affect the test and the default value is false
+// If it's updated here prefs-1.html and prefs-2.html need the same update
+result.innerHTML = SpecialPowers.getBoolPref("apz.allow_zooming_out");
+document.documentElement.classList.remove("reftest-wait");
+</script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-1.html b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-1.html
new file mode 100644
index 0000000000..8f1029d3ff
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<link rel="match" href="prefs-true.html"/>
+<div id="result"></div>
+<script>
+// If the pref is changed here, it needs to also be changed in the corresponding ini file
+result.innerHTML = SpecialPowers.getBoolPref("apz.allow_zooming_out");
+document.documentElement.classList.remove("reftest-wait");
+</script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-2.html b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-2.html
new file mode 100644
index 0000000000..54ebde2ebe
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<link rel="match" href="prefs-false.html"/>
+<div id="result"></div>
+<script>
+result.innerHTML = SpecialPowers.getBoolPref("apz.allow_zooming_out");
+document.documentElement.classList.remove("reftest-wait");
+</script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-false.html b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-false.html
new file mode 100644
index 0000000000..667d1e1bf6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-false.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<div id="result">false</div>
+</html>
diff --git a/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-true.html b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-true.html
new file mode 100644
index 0000000000..df3fe20eff
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-true.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<div id="result">true</div>
+</html>
diff --git a/testing/web-platform/mozilla/tests/infrastructure/specialPowers/specialpowers.html b/testing/web-platform/mozilla/tests/infrastructure/specialPowers/specialpowers.html
new file mode 100644
index 0000000000..38615028d2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/infrastructure/specialPowers/specialpowers.html
@@ -0,0 +1,7 @@
+<title>Check specialPowers is available in gecko-only tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+test(() => assert_equals(SpecialPowers.sanityCheck(), "foo"))
+</script>
diff --git a/testing/web-platform/mozilla/tests/media/2x2-green.ogv b/testing/web-platform/mozilla/tests/media/2x2-green.ogv
new file mode 100644
index 0000000000..29903c0a81
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/media/2x2-green.ogv
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-in-background.https.html b/testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-in-background.https.html
new file mode 100644
index 0000000000..55d1d24dce
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-in-background.https.html
@@ -0,0 +1,67 @@
+<!doctype html>
+<title>enumerateDevices() in background tab with focus in chrome</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<body></body>
+<script>
+'use strict';
+// This test is not in cross-browser wpt because it uses Gecko-specific API
+// for focusing browser chrome and it assumes a specific tab browser design.
+// It assumes that
+// * browser chrome widget focus is associated with a single current document
+// presentation (tab),
+// https://github.com/w3c/mediacapture-main/issues/752#issuecomment-742036800
+// * window.open() focuses the new tab and makes the current tab hidden, and
+// * the opener tab becomes the current and visible tab again when the new tab
+// is closed.
+const blank_url = '/common/blank.html';
+
+function promise_event(target, name) {
+ return new Promise(resolve => target[`on${name}`] = resolve);
+}
+
+promise_test(async t => {
+ // Open a new tab, which is expected to receive focus and hide the first tab.
+ await test_driver.bless('window.open()');
+ assert_true(document.hasFocus(), 'This test needs focus on the browser.');
+ const promise_hidden = promise_event(document, 'visibilitychange');
+ const proxy = window.open(blank_url);
+ t.add_cleanup(() => proxy.close());
+ await Promise.all([
+ promise_hidden,
+ promise_event(proxy, 'focus'),
+ promise_event(proxy, 'load'),
+ ]);
+ assert_true(proxy.document.hasFocus(), 'proxy.document.hasFocus()');
+
+ await Promise.all([
+ promise_event(proxy, 'blur'),
+ SpecialPowers.spawnChrome([], function focus_url_bar() {
+ this.browsingContext.topChromeWindow.gURLBar.focus();
+ }),
+ ]);
+ assert_false(proxy.document.hasFocus(), 'proxy.document.hasFocus()');
+ assert_false(document.hasFocus(), 'document.hasFocus()');
+ assert_equals(document.visibilityState, 'hidden', 'visibilityState');
+
+ // Enumeration should remain pending while the first tab is background.
+ const promise_enumerate = navigator.mediaDevices.enumerateDevices();
+ // Enumerate in the foreground tab to confirm that URL bar focus is
+ // sufficient, and to provide enough time to check that the Promise from the
+ // background tab does not settle.
+ await proxy.navigator.mediaDevices.enumerateDevices();
+ // Race a settled Promise to check that the enumeration in the background tab
+ // has not settled.
+ const result = await Promise.race([promise_enumerate, 'pending']);
+ assert_equals(result, 'pending', 'pending Promise while background.');
+
+ // The enumeration Promise should resolve after the first tab returns to the
+ // foreground.
+ proxy.close();
+ await promise_event(document, 'visibilitychange');
+ assert_equals(document.visibilityState, 'visible', 'visibilityState');
+ await promise_enumerate;
+}, 'enumerateDevices in background');
+</script>
diff --git a/testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-without-focus.https.html b/testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-without-focus.https.html
new file mode 100644
index 0000000000..6516a514c5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-without-focus.https.html
@@ -0,0 +1,58 @@
+<!doctype html>
+<title>enumerateDevices() without focus</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<body></body>
+<script>
+'use strict';
+const blank_url = '/common/blank.html';
+
+function promise_event(target, name) {
+ return new Promise(resolve => target[`on${name}`] = resolve);
+}
+// When testdriver.js supports switch-to-window, it can replace this function
+// and this test can be upstreamed.
+// https://github.com/web-platform-tests/wpt/issues/10666
+function switch_toplevel_focus_for_window(win) {
+ return win.SpecialPowers.spawnChrome([], function activate_browser_window() {
+ this.browsingContext.topChromeWindow.focus();
+ });
+}
+
+promise_test(async t => {
+ await test_driver.bless('window.open()');
+ assert_true(document.hasFocus(), 'This test needs focus on the document.');
+ const promise_blur = promise_event(window, 'blur');
+ // 'resizable' is requested for a separate OS window on relevant platforms
+ // so that this test tests OS focus changes rather than document visibility.
+ const proxy = window.open(blank_url, '', 'resizable');
+ t.add_cleanup(() => proxy.close());
+ await Promise.all([
+ promise_blur,
+ switch_toplevel_focus_for_window(proxy),
+ promise_event(proxy, 'load'),
+ ]);
+ assert_false(document.hasFocus(), 'document.hasFocus() after blur');
+
+ // Enumeration should remain pending without focus.
+ const promise_enumerate = navigator.mediaDevices.enumerateDevices();
+ // Enumerate in the focused window to provide enough time to check that
+ // the Promise from the unfocused window does not settle.
+ await proxy.navigator.mediaDevices.enumerateDevices();
+ // Race a settled Promise to check that the enumeration in the first window
+ // has not settled.
+ const result = await Promise.race([promise_enumerate, 'pending']);
+ assert_equals(result, 'pending', 'pending Promise without focus.');
+
+ // The enumeration Promise should resolve after focus returns to the window.
+ proxy.close();
+ await Promise.all([
+ promise_event(window, 'focus'),
+ switch_toplevel_focus_for_window(window),
+ ]);
+ assert_true(document.hasFocus(), 'document.hasFocus() after focus');
+ await promise_enumerate;
+}, 'enumerateDevices without focus');
+</script>
diff --git a/testing/web-platform/mozilla/tests/placeholder b/testing/web-platform/mozilla/tests/placeholder
new file mode 100644
index 0000000000..92dd3d5151
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/placeholder
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+This is a placeholder file to ensure that this directory remains
+in source control and test packages even when it is otherwise empty. \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/screen-capture/getdisplaymedia-user-activation-consumed.https.html b/testing/web-platform/mozilla/tests/screen-capture/getdisplaymedia-user-activation-consumed.https.html
new file mode 100644
index 0000000000..d0623fbaa5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/screen-capture/getdisplaymedia-user-activation-consumed.https.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<head>
+<title>Test getDisplayMedia() after user activation is consumed</title>
+<link rel="help" href="https://w3c.github.io/mediacapture-screen-share/#dom-mediadevices-getdisplaymedia">
+</head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+'use strict';
+
+promise_test(async t => {
+ await test_driver.bless('transient activation');
+ // SpecialPowers is used to consume user activation because the only
+ // spec-compliant Gecko API that consumes user activation is
+ // navigator.share(), which is disabled on CI versions of WINNT.
+ // https://searchfox.org/mozilla-central/rev/66547980e8e8ca583473c74f207cae5bac1ed541/testing/web-platform/meta/web-share/share-consume-activation.https.html.ini#4
+ const had_transient_activation =
+ SpecialPowers.wrap(document).consumeTransientUserGestureActivation();
+ assert_true(had_transient_activation,
+ 'should have had transient activation');
+ const p = navigator.mediaDevices.getDisplayMedia();
+ // Race a settled promise to check that the returned promise is already
+ // rejected.
+ await promise_rejects_dom(
+ t, 'InvalidStateError', Promise.race([p, Promise.resolve()]),
+ 'getDisplayMedia should have returned an already-rejected promise.');
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/service-workers/bug1675097.https.html b/testing/web-platform/mozilla/tests/service-workers/bug1675097.https.html
new file mode 100644
index 0000000000..e093f616c1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/bug1675097.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Controlled iframe with initial about:blank becomes sandboxed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<body>
+<script>
+
+// This test creates an iframe controlled by a service worker, which in turn has
+// another iframe in it. The inner iframe's fetch is intercepted by the service
+// worker, which notifies the outer iframe, which then adds the sandbox
+// attribute to the inner iframe. The outer iframe then signals the service
+// worker that it should evaluate self.clients.matchAll() and finally respond to
+// the inner iframe's fetch.
+// Evaluating self.clients.matchAll() causes the creation of the initial
+// about:blank document for the inner iframe, and if the sandboxing flags used
+// for that document are not the original flags for the inner iframe (i.e. none)
+// then the document will have an opaque origin, a case that we need to handle
+// properly.
+promise_test(async t => {
+ const URL = 'resources/bug1675097-sw.js';
+ const SCOPE = 'resources/';
+
+ const registration = await service_worker_unregister_and_register(t, URL, SCOPE);
+ t.add_cleanup(() => registration.unregister());
+
+ await wait_for_state(t, registration.installing, 'activated');
+
+ const outer = await with_iframe(SCOPE + 'bug1675097-iframe.html');
+ t.add_cleanup(() => outer.remove());
+}, 'Regression test for bug 1675097');
+
+</script>
+</body>
diff --git a/testing/web-platform/mozilla/tests/service-workers/no_intercept_for_crossorigin_media.https.html b/testing/web-platform/mozilla/tests/service-workers/no_intercept_for_crossorigin_media.https.html
new file mode 100644
index 0000000000..245522f5fc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/no_intercept_for_crossorigin_media.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Don't intercept cross-origin media requests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js">
+</script>
+<body>
+<script>
+
+'use strict';
+
+const scope = './resources/';
+
+/**
+ * Ensure service workers don't intercept cross-origin media range requests.
+ */
+promise_test(async t => {
+ const registration = await service_worker_unregister_and_register(
+ t, scope + 'intercept_media_sw.js', scope);
+ t.add_cleanup(() => registration.unregister());
+ await wait_for_state(t, registration.installing, 'activated');
+
+ const frame = await with_iframe(scope + 'crossorigin_media_iframe.html');
+ return frame.contentWindow.create_media_promise()
+}, 'Service worker does not intercept a cross-origin media range request');
+
+/**
+ * Ensure service workers do intercept cross-origin media non-range requests.
+ */
+promise_test(async t => {
+ const registration = await service_worker_unregister_and_register(
+ t, scope + 'intercept_media_sw.js', scope);
+ t.add_cleanup(() => registration.unregister());
+ await wait_for_state(t, registration.installing, 'activated');
+
+ const frame = await with_iframe(scope + 'crossorigin_media_iframe_nonrange.html');
+ return frame.contentWindow.create_media_promise()
+}, 'Service worker intercepts a cross-origin non-range media request');
+
+</script>
+</body>
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/blank.html b/testing/web-platform/mozilla/tests/service-workers/resources/blank.html
new file mode 100644
index 0000000000..a3c3a4689a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/blank.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<title>Empty doc</title>
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-iframe.html b/testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-iframe.html
new file mode 100644
index 0000000000..5ad7b95594
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-iframe.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<body>
+<script>
+let channel = new MessageChannel();
+channel.port1.onmessage = event => {
+ if (event.data === 'intercepted') {
+ const iframe = document.querySelector('iframe');
+ iframe.sandbox = '';
+ navigator.serviceWorker.controller.postMessage({ type: 'ack' });
+ }
+};
+navigator.serviceWorker.controller.postMessage({ type: 'register', port: channel.port2 }, [channel.port2]);
+</script>
+<iframe src='inner'></iframe>
+</body>
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-sw.js b/testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-sw.js
new file mode 100644
index 0000000000..e2894e1032
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-sw.js
@@ -0,0 +1,26 @@
+// We use promises because the message and fetch events do not have a guaranteed
+// order, since they come from different task sources.
+var resolvePortPromise;
+var portPromise = new Promise(resolve => resolvePortPromise = resolve);
+var resolveResolveResponsePromise;
+var resolveResponsePromise = new Promise(resolve => resolveResolveResponsePromise = resolve);
+
+self.addEventListener('fetch', event => {
+ if (event.request.url.indexOf('inner') !== -1) {
+ event.respondWith(new Promise(resolve => {
+ resolveResolveResponsePromise(resolve);
+ }));
+ portPromise.then(port => port.postMessage('intercepted'));
+ }
+});
+
+self.addEventListener('message', event => {
+ if (event.data.type === 'register') {
+ resolvePortPromise(event.data.port);
+ }
+ else if (event.data.type === 'ack') {
+ self.clients.matchAll()
+ .then(() => resolveResponsePromise)
+ .then(resolveResponse => resolveResponse(new Response('inner iframe')));
+ }
+});
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe.html b/testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe.html
new file mode 100644
index 0000000000..54cdcaa8dd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+<script>
+
+'use strict';
+
+function remoteize(relpath) {
+ let curdir = location.href.replace(/\/[^\/]*$/, '/')
+ return curdir.replace('://', '://www1.') + relpath
+}
+
+function create_media_promise() {
+ return new Promise((resolve, reject) => {
+ let video = document.createElement('video')
+ video.autoplay = true
+ video.muted = true
+ video.onplay = () => resolve('ok')
+ video.onerror = () => reject('video error')
+ video.src = remoteize('fetch_video.py')
+ })
+}
+
+</script>
+</body>
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe_nonrange.html b/testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe_nonrange.html
new file mode 100644
index 0000000000..8e3c20fdeb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe_nonrange.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<body>
+<script>
+
+'use strict';
+
+function remoteize(relpath) {
+ let curdir = location.href.replace(/\/[^\/]*$/, '/')
+ return curdir.replace('://', '://www1.') + relpath
+}
+
+function create_media_promise() {
+ return new Promise((resolve, reject) => {
+ let image = document.createElement('img')
+ image.onload = () => resolve('ok')
+ image.onerror = () => reject('image error')
+ image.src = remoteize('blank.html') // sw will replace with an image
+ })
+}
+
+</script>
+</body>
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/empty.js b/testing/web-platform/mozilla/tests/service-workers/resources/empty.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/empty.js
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/fetch_video.py b/testing/web-platform/mozilla/tests/service-workers/resources/fetch_video.py
new file mode 100644
index 0000000000..541f00c019
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/fetch_video.py
@@ -0,0 +1,14 @@
+import os
+
+
+def main(request, response):
+ filename = os.path.join(request.doc_root, u"media", u"2x2-green.ogv")
+ body = open(filename, "rb").read()
+ length = len(body)
+ headers = [
+ (b"Content-Type", b"video/ogg"),
+ (b"Accept-Ranges", b"bytes"),
+ (b"Content-Length", b"%d" % length),
+ (b"Content-Range", b"bytes 0-%d/%d" % (length - 1, length)),
+ ]
+ return headers, body
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/green.png b/testing/web-platform/mozilla/tests/service-workers/resources/green.png
new file mode 100644
index 0000000000..28a1faab37
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/green.png
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/intercept_media_sw.js b/testing/web-platform/mozilla/tests/service-workers/resources/intercept_media_sw.js
new file mode 100644
index 0000000000..6c373aa0de
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/intercept_media_sw.js
@@ -0,0 +1,14 @@
+'use strict';
+
+self.addEventListener('fetch', event => {
+ if (event.request.url.indexOf('fetch_video.py') !== -1) {
+ // A no-cors media range request /should not/ be intercepted.
+ // Respond with some text to cause an error.
+ event.respondWith(new Response('intercepted'));
+ }
+ else if (event.request.url.indexOf('blank.html') !== -1) {
+ // A no-cors media non-range request /should/ be intercepted.
+ // Respond with an image to avoid an error.
+ event.respondWith(fetch('green.png'));
+ }
+});
diff --git a/testing/web-platform/mozilla/tests/service-workers/update_completes_in_disconnected_global.https.html b/testing/web-platform/mozilla/tests/service-workers/update_completes_in_disconnected_global.https.html
new file mode 100644
index 0000000000..371fa81161
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/update_completes_in_disconnected_global.https.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<title>Service Worker: Disconnected Global Update()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+'use strict';
+
+/**
+ * Gecko-only pseudo-crash-test to verify that if we call
+ * ServiceWorkerRegistration.update() from a global that will be destroyed
+ * before any IPC call could return that we don't crash.
+ *
+ * We use an iframe to create a global that we can destroy on demand.
+ */
+promise_test(async function(t) {
+ const scope = 'resources/blank.html';
+ const sw_url = 'resources/empty.js';
+
+ let reg = await service_worker_unregister_and_register(t, sw_url, scope);
+ t.add_cleanup(function() {
+ return service_worker_unregister(t, scope);
+ });
+
+ // Wait for the worker to be activated so that we are in a known state.
+ await wait_for_state(t, reg.installing, 'activated');
+
+ let f = await with_iframe(scope);
+ let f_global = f.contentWindow;
+ // The frame should be controlled, although it's not necessary for the test.
+ assert_true(!!f_global.navigator.serviceWorker.controller);
+ t.add_cleanup(function() {
+ if (f) {
+ f.remove();
+ }
+ });
+
+ // Get a registration object that lives in the iframe's global.
+ let f_reg = await f_global.navigator.serviceWorker.getRegistration(reg.scope);
+ assert_true(!!f_reg, 'got registration');
+ assert_equals(reg.scope, f_reg.scope, 'Right registration');
+
+ // Trigger the update and destroy the global.
+ let update_resolved = false;
+ let update_rejected = false;
+
+ let update_promise = f_reg.update();
+ update_promise.then(
+ () => { update_resolved = true; }, () => { update_rejected = true; });
+
+ f.remove();
+ f = null;
+ f_global = null;
+
+ // Now we want to wait on an update call that should fire strictly after the
+ // update call above.
+ await reg.update();
+
+ assert_false(update_resolved, "frame update() should not have resolved");
+ assert_true(update_rejected, "frame update() should have rejected");
+}, 'ServiceWorkerRegistration.update() concluding in a disconnected global');
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/web-animations/web-animations-print-ref.html b/testing/web-platform/mozilla/tests/web-animations/web-animations-print-ref.html
new file mode 100644
index 0000000000..539d072d16
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/web-animations/web-animations-print-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<html>
+<title>Web animation</title>
+<p style="color: blue">blue with animation support; olive without</p>
diff --git a/testing/web-platform/mozilla/tests/web-animations/web-animations-print.html b/testing/web-platform/mozilla/tests/web-animations/web-animations-print.html
new file mode 100644
index 0000000000..2b1ed19eb6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/web-animations/web-animations-print.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<link rel="help" href="https://drafts.csswg.org/web-animations/">
+<link rel="match" href="web-animations-print-ref.html">
+<title>Web animation</title>
+<p id="anim" style="color: olive">blue with animation support; olive without</p>
+<script>
+const animationData = [
+ {"color":"#0000FF"},
+ {"color":"#0000FF"}
+];
+
+const animationTiming = {
+ "duration":1,
+ "iterations":Infinity
+};
+
+let element = document.getElementById("anim");
+element.animate(animationData, animationTiming);
+</script>
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/__init__.py
new file mode 100644
index 0000000000..910b202075
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/__init__.py
@@ -0,0 +1,20 @@
+import contextlib
+
+
+def set_context(session, context):
+ session.send_session_command("POST", "moz/context", {"context": context})
+
+
+@contextlib.contextmanager
+def using_context(session, context):
+ orig_context = session.send_session_command("GET", "moz/context")
+ needs_change = context != orig_context
+
+ if needs_change:
+ set_context(session, context)
+
+ try:
+ yield
+ finally:
+ if needs_change:
+ set_context(session, orig_context)
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/reference_context.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/reference_context.py
new file mode 100644
index 0000000000..1a5906339b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/reference_context.py
@@ -0,0 +1,72 @@
+import pytest
+
+from . import using_context
+
+pytestmark = pytest.mark.asyncio
+
+
+# Helper to assert the order of top level browsing contexts.
+# The window used for the assertion is inferred from the first context id of
+# expected_context_ids.
+def assert_tab_order(session, expected_context_ids):
+ with using_context(session, "chrome"):
+ context_ids = session.execute_script(
+ """
+ const contextId = arguments[0];
+ const { TabManager } =
+ ChromeUtils.importESModule("chrome://remote/content/shared/TabManager.sys.mjs");
+ const browsingContext = TabManager.getBrowsingContextById(contextId);
+ const chromeWindow = browsingContext.embedderElement.ownerGlobal;
+ const tabBrowser = TabManager.getTabBrowser(chromeWindow);
+ return tabBrowser.browsers.map(browser => TabManager.getIdForBrowser(browser));
+ """,
+ args=(expected_context_ids[0],),
+ )
+
+ assert context_ids == expected_context_ids
+
+
+async def test_reference_context(bidi_session, current_session):
+ # Create a new window with a tab tab1
+ result = await bidi_session.browsing_context.create(type_hint="window")
+ tab1_context_id = result["context"]
+
+ # Create a second window with a tab tab2
+ result = await bidi_session.browsing_context.create(type_hint="window")
+ tab2_context_id = result["context"]
+
+ # Create a new tab tab3 next to tab1
+ result = await bidi_session.browsing_context.create(
+ type_hint="tab", reference_context=tab1_context_id
+ )
+ tab3_context_id = result["context"]
+
+ # Create a new tab tab4 next to tab2
+ result = await bidi_session.browsing_context.create(
+ type_hint="tab", reference_context=tab2_context_id
+ )
+ tab4_context_id = result["context"]
+
+ # Create a new tab tab5 also next to tab2 (should consequently be between
+ # tab2 and tab4)
+ result = await bidi_session.browsing_context.create(
+ type_hint="tab", reference_context=tab2_context_id
+ )
+ tab5_context_id = result["context"]
+
+ # Create a new window, but pass a reference_context from an existing window.
+ # The reference context is expected to be ignored here.
+ result = await bidi_session.browsing_context.create(
+ type_hint="window", reference_context=tab2_context_id
+ )
+ tab6_context_id = result["context"]
+
+ # We expect 3 windows in total, with a specific tab order:
+ # - the first window should contain tab1, tab3
+ assert_tab_order(current_session, [tab1_context_id, tab3_context_id])
+ # - the second window should contain tab2, tab5, tab4
+ assert_tab_order(
+ current_session, [tab2_context_id, tab5_context_id, tab4_context_id]
+ )
+ # - the third window should contain tab6
+ assert_tab_order(current_session, [tab6_context_id])
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/type_hint.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/type_hint.py
new file mode 100644
index 0000000000..337a03b3dd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/type_hint.py
@@ -0,0 +1,31 @@
+import pytest
+from tests.support.asserts import assert_success
+
+from . import using_context
+
+pytestmark = pytest.mark.asyncio
+
+
+def count_window_handles(session):
+ with using_context(session, "chrome"):
+ response = session.transport.send(
+ "GET", "session/{session_id}/window/handles".format(**vars(session))
+ )
+ chrome_handles = assert_success(response)
+ return len(chrome_handles)
+
+
+@pytest.mark.parametrize("type_hint", ["tab", "window"])
+async def test_type_hint(bidi_session, current_session, type_hint):
+ assert len(await bidi_session.browsing_context.get_tree()) == 1
+ assert count_window_handles(current_session) == 1
+
+ await bidi_session.browsing_context.create(type_hint=type_hint)
+
+ if type_hint == "window":
+ expected_window_count = 2
+ else:
+ expected_window_count = 1
+
+ assert len(await bidi_session.browsing_context.get_tree()) == 2
+ assert count_window_handles(current_session) == expected_window_count
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/error.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/error.py
new file mode 100644
index 0000000000..374359d1ae
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/error.py
@@ -0,0 +1,48 @@
+import os
+from copy import deepcopy
+
+import pytest
+from tests.bidi.browsing_context.navigate import navigate_and_assert
+
+pytestmark = pytest.mark.asyncio
+
+
+async def test_insecure_certificate(configuration, url, custom_profile, geckodriver):
+ try:
+ # Create a new profile and remove the certificate storage so that
+ # loading a HTTPS page will cause an insecure certificate error
+ os.remove(os.path.join(custom_profile.profile, "cert9.db"))
+ except Exception:
+ pass
+
+ config = deepcopy(configuration)
+ config["capabilities"]["moz:firefoxOptions"]["args"] = [
+ "--profile",
+ custom_profile.profile,
+ ]
+ # Capability matching not implemented yet for WebDriver BiDi (bug 1713784)
+ config["capabilities"]["acceptInsecureCerts"] = False
+ config["capabilities"]["webSocketUrl"] = True
+
+ driver = geckodriver(config=config)
+ driver.new_session()
+
+ bidi_session = driver.session.bidi_session
+ await bidi_session.start()
+
+ contexts = await bidi_session.browsing_context.get_tree(max_depth=0)
+ await navigate_and_assert(
+ bidi_session,
+ contexts[0],
+ url("/common/blank.html", protocol="https"),
+ expected_error=True,
+ )
+
+
+async def test_invalid_content_encoding(bidi_session, new_tab, inline):
+ await navigate_and_assert(
+ bidi_session,
+ new_tab,
+ f"{inline('<div>foo')}&pipe=header(Content-Encoding,gzip)",
+ expected_error=True,
+ )
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/errors/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/errors/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/errors/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/errors/errors.py b/testing/web-platform/mozilla/tests/webdriver/bidi/errors/errors.py
new file mode 100644
index 0000000000..69b1f2fb7a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/errors/errors.py
@@ -0,0 +1,8 @@
+import pytest
+from webdriver.bidi.error import UnknownCommandException
+
+
+@pytest.mark.asyncio
+async def test_internal_method(bidi_session, send_blocking_command):
+ with pytest.raises(UnknownCommandException):
+ await send_blocking_command("log._applySessionData", {})
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/interface/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/interface/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/interface/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/interface/interface.py b/testing/web-platform/mozilla/tests/webdriver/bidi/interface/interface.py
new file mode 100644
index 0000000000..561b80d120
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/interface/interface.py
@@ -0,0 +1,26 @@
+import pytest
+from webdriver.bidi.client import BidiSession
+from webdriver.bidi.modules.script import ContextTarget
+
+pytestmark = pytest.mark.asyncio
+
+
+async def test_navigator_webdriver_enabled(inline, browser):
+ # Request a new browser with only WebDriver BiDi and not Marionette/CDP enabled.
+ current_browser = browser(use_bidi=True, extra_prefs={"remote.active-protocols": 1})
+ server_host = current_browser.remote_agent_host
+ server_port = current_browser.remote_agent_port
+
+ async with BidiSession.bidi_only(
+ f"ws://{server_host}:{server_port}"
+ ) as bidi_session:
+ contexts = await bidi_session.browsing_context.get_tree(max_depth=0)
+ assert len(contexts) > 0
+
+ result = await bidi_session.script.evaluate(
+ expression="navigator.webdriver",
+ target=ContextTarget(contexts[0]["context"]),
+ await_promise=False,
+ )
+
+ assert result == {"type": "boolean", "value": True}
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/script/exception_details.py b/testing/web-platform/mozilla/tests/webdriver/bidi/script/exception_details.py
new file mode 100644
index 0000000000..43bccdb845
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/script/exception_details.py
@@ -0,0 +1,69 @@
+import pytest
+from webdriver.bidi.modules.script import ContextTarget, ScriptEvaluateResultException
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("await_promise", [True, False])
+@pytest.mark.parametrize(
+ "expression",
+ [
+ "null",
+ "{ toString: 'not a function' }",
+ "{ toString: () => {{ throw 'toString not allowed'; }} }",
+ "{ toString: () => true }",
+ ],
+)
+@pytest.mark.asyncio
+async def test_call_function_without_to_string_interface(
+ bidi_session, top_context, await_promise, expression
+):
+ function_declaration = "()=>{throw { toString: 'not a function' } }"
+ if await_promise:
+ function_declaration = "async" + function_declaration
+
+ with pytest.raises(ScriptEvaluateResultException) as exception:
+ await bidi_session.script.call_function(
+ function_declaration=function_declaration,
+ await_promise=await_promise,
+ target=ContextTarget(top_context["context"]),
+ )
+
+ assert "exceptionDetails" in exception.value.result
+ exceptionDetails = exception.value.result["exceptionDetails"]
+
+ assert "text" in exceptionDetails
+ assert isinstance(exceptionDetails["text"], str)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("await_promise", [True, False])
+@pytest.mark.parametrize(
+ "expression",
+ [
+ "null",
+ "{ toString: 'not a function' }",
+ "{ toString: () => {{ throw 'toString not allowed'; }} }",
+ "{ toString: () => true }",
+ ],
+)
+@pytest.mark.asyncio
+async def test_evaluate_without_to_string_interface(
+ bidi_session, top_context, await_promise, expression
+):
+ if await_promise:
+ expression = f"Promise.reject({expression})"
+ else:
+ expression = f"throw {expression}"
+
+ with pytest.raises(ScriptEvaluateResultException) as exception:
+ await bidi_session.script.evaluate(
+ expression=expression,
+ await_promise=await_promise,
+ target=ContextTarget(top_context["context"]),
+ )
+
+ assert "exceptionDetails" in exception.value.result
+ exceptionDetails = exception.value.result["exceptionDetails"]
+
+ assert "text" in exceptionDetails
+ assert isinstance(exceptionDetails["text"], str)
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/websocket_upgrade.py b/testing/web-platform/mozilla/tests/webdriver/bidi/websocket_upgrade.py
new file mode 100644
index 0000000000..e5ebfa1eb0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/websocket_upgrade.py
@@ -0,0 +1,156 @@
+import pytest
+from support.network import get_host, websocket_request
+
+
+@pytest.mark.parametrize(
+ "hostname, port_type, status",
+ [
+ # Valid hosts
+ ("localhost", "server_port", 101),
+ ("localhost", "default_port", 101),
+ ("127.0.0.1", "server_port", 101),
+ ("127.0.0.1", "default_port", 101),
+ ("[::1]", "server_port", 101),
+ ("[::1]", "default_port", 101),
+ ("192.168.8.1", "server_port", 101),
+ ("192.168.8.1", "default_port", 101),
+ ("[fdf8:f535:82e4::53]", "server_port", 101),
+ ("[fdf8:f535:82e4::53]", "default_port", 101),
+ # Invalid hosts
+ ("mozilla.org", "server_port", 400),
+ ("mozilla.org", "wrong_port", 400),
+ ("mozilla.org", "default_port", 400),
+ ("localhost", "wrong_port", 400),
+ ("127.0.0.1", "wrong_port", 400),
+ ("[::1]", "wrong_port", 400),
+ ("192.168.8.1", "wrong_port", 400),
+ ("[fdf8:f535:82e4::53]", "wrong_port", 400),
+ ],
+ ids=[
+ # Valid hosts
+ "localhost with same port as RemoteAgent",
+ "localhost with default port",
+ "127.0.0.1 (loopback) with same port as RemoteAgent",
+ "127.0.0.1 (loopback) with default port",
+ "[::1] (ipv6 loopback) with same port as RemoteAgent",
+ "[::1] (ipv6 loopback) with default port",
+ "ipv4 address with same port as RemoteAgent",
+ "ipv4 address with default port",
+ "ipv6 address with same port as RemoteAgent",
+ "ipv6 address with default port",
+ # Invalid hosts
+ "random hostname with the same port as RemoteAgent",
+ "random hostname with a different port than RemoteAgent",
+ "random hostname with default port",
+ "localhost with a different port than RemoteAgent",
+ "127.0.0.1 (loopback) with a different port than RemoteAgent",
+ "[::1] (ipv6 loopback) with a different port than RemoteAgent",
+ "ipv4 address with a different port than RemoteAgent",
+ "ipv6 address with a different port than RemoteAgent",
+ ],
+)
+def test_host_header(browser, hostname, port_type, status):
+ # Request a default browser
+ current_browser = browser(use_bidi=True)
+ server_host = current_browser.remote_agent_host
+ server_port = current_browser.remote_agent_port
+ test_host = get_host(port_type, hostname, server_port)
+
+ response = websocket_request(server_host, server_port, host=test_host)
+ assert response.status == status
+
+
+@pytest.mark.parametrize(
+ "hostname, port_type, status",
+ [
+ # Allowed hosts
+ ("testhost", "server_port", 101),
+ ("testhost", "default_port", 101),
+ ("testhost", "wrong_port", 400),
+ # IP addresses
+ ("192.168.8.1", "server_port", 101),
+ ("192.168.8.1", "default_port", 101),
+ ("[fdf8:f535:82e4::53]", "server_port", 101),
+ ("[fdf8:f535:82e4::53]", "default_port", 101),
+ ("127.0.0.1", "server_port", 101),
+ ("127.0.0.1", "default_port", 101),
+ ("[::1]", "server_port", 101),
+ ("[::1]", "default_port", 101),
+ # Localhost
+ ("localhost", "server_port", 400),
+ ("localhost", "default_port", 400),
+ ],
+ ids=[
+ # Allowed hosts
+ "allowed host with same port as RemoteAgent",
+ "allowed host with default port",
+ "allowed host with wrong port",
+ # IP addresses
+ "ipv4 address with same port as RemoteAgent",
+ "ipv4 address with default port",
+ "ipv6 address with same port as RemoteAgent",
+ "ipv6 address with default port",
+ "127.0.0.1 (loopback) with same port as RemoteAgent",
+ "127.0.0.1 (loopback) with default port",
+ "[::1] (ipv6 loopback) with same port as RemoteAgent",
+ "[::1] (ipv6 loopback) with default port",
+ # Localhost
+ "localhost with same port as RemoteAgent",
+ "localhost with default port",
+ ],
+)
+def test_allowed_hosts(browser, hostname, port_type, status):
+ # Request a browser with custom allowed hosts.
+ current_browser = browser(
+ use_bidi=True,
+ extra_args=["--remote-allow-hosts", "testhost"],
+ )
+ server_host = current_browser.remote_agent_host
+ server_port = current_browser.remote_agent_port
+ test_host = get_host(port_type, hostname, server_port)
+
+ response = websocket_request(server_host, server_port, host=test_host)
+ assert response.status == status
+
+
+@pytest.mark.parametrize(
+ "origin, status",
+ [
+ (None, 101),
+ ("", 400),
+ ("sometext", 400),
+ ("http://localhost:1234", 400),
+ ],
+)
+def test_origin_header(browser, origin, status):
+ # Request a default browser.
+ current_browser = browser(use_bidi=True)
+ server_host = current_browser.remote_agent_host
+ server_port = current_browser.remote_agent_port
+ response = websocket_request(server_host, server_port, origin=origin)
+ assert response.status == status
+
+
+@pytest.mark.parametrize(
+ "origin, status",
+ [
+ (None, 101),
+ ("", 400),
+ ("sometext", 400),
+ ("http://localhost:1234", 101),
+ ("https://localhost:1234", 400),
+ ],
+)
+def test_allowed_origins(browser, origin, status):
+ # Request a browser with custom allowed origins.
+ current_browser = browser(
+ use_bidi=True,
+ extra_args=["--remote-allow-origins", "http://localhost:1234"],
+ )
+ server_port = current_browser.remote_agent_port
+
+ # Both `localhost` and `127.0.0.1` have to accept connections.
+ for target_host in ["127.0.0.1", "localhost"]:
+ print(f"Connecting to the WebSocket via host {target_host}")
+ response = websocket_request(target_host, server_port, origin=origin)
+ assert response.status == status
diff --git a/testing/web-platform/mozilla/tests/webdriver/cdp/__init__.py b/testing/web-platform/mozilla/tests/webdriver/cdp/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/cdp/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/cdp/debugger_address.py b/testing/web-platform/mozilla/tests/webdriver/cdp/debugger_address.py
new file mode 100644
index 0000000000..434edae4b5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/cdp/debugger_address.py
@@ -0,0 +1,77 @@
+import json
+
+import pytest
+from support.context import using_context
+from tests.support.http_request import HTTPRequest
+
+
+def test_debugger_address_not_set(session):
+ debugger_address = session.capabilities.get("moz:debuggerAddress")
+ assert debugger_address is None
+
+
+@pytest.mark.capabilities({"moz:debuggerAddress": False})
+def test_debugger_address_false(session):
+ debugger_address = session.capabilities.get("moz:debuggerAddress")
+ assert debugger_address is None
+
+
+@pytest.mark.capabilities({"moz:debuggerAddress": True})
+def test_debugger_address_true_fission_disabled(session):
+ debugger_address = session.capabilities.get("moz:debuggerAddress")
+ assert debugger_address is not None
+
+ host, port = debugger_address.split(":")
+ assert host == "127.0.0.1"
+ assert port.isnumeric()
+
+ # Fetch the browser version via the debugger address, `localhost` has
+ # to work as well.
+ for target_host in [host, "localhost"]:
+ print(f"Connecting to WebSocket via host {target_host}")
+ http = HTTPRequest(target_host, int(port))
+ with http.get("/json/version") as response:
+ data = json.loads(response.read())
+ assert session.capabilities["browserVersion"] in data["Browser"]
+
+ # Force disabling Fission until Remote Agent is compatible
+ with using_context(session, "chrome"):
+ assert (
+ session.execute_script("""return Services.appinfo.fissionAutostart""")
+ is False
+ )
+
+
+@pytest.mark.capabilities(
+ {
+ "moz:debuggerAddress": True,
+ "moz:firefoxOptions": {
+ "prefs": {
+ "fission.autostart": True,
+ }
+ },
+ }
+)
+def test_debugger_address_true_fission_override(session):
+ debugger_address = session.capabilities.get("moz:debuggerAddress")
+ assert debugger_address is not None
+
+ host, port = debugger_address.split(":")
+ assert host == "127.0.0.1"
+ assert port.isnumeric()
+
+ # Fetch the browser version via the debugger address, `localhost` has
+ # to work as well.
+ for target_host in [host, "localhost"]:
+ print(f"Connecting to WebSocket via host {target_host}")
+ http = HTTPRequest(target_host, int(port))
+ with http.get("/json/version") as response:
+ data = json.loads(response.read())
+ assert session.capabilities["browserVersion"] in data["Browser"]
+
+ # Allow Fission to be enabled when setting the preference
+ with using_context(session, "chrome"):
+ assert (
+ session.execute_script("""return Services.appinfo.fissionAutostart""")
+ is True
+ )
diff --git a/testing/web-platform/mozilla/tests/webdriver/cdp/port_file.py b/testing/web-platform/mozilla/tests/webdriver/cdp/port_file.py
new file mode 100644
index 0000000000..aa294deb24
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/cdp/port_file.py
@@ -0,0 +1,30 @@
+import os
+
+from support.network import websocket_request
+
+
+def test_devtools_active_port_file(browser):
+ current_browser = browser(use_cdp=True)
+
+ assert current_browser.remote_agent_port != 0
+ assert current_browser.debugger_address.startswith("/devtools/browser/")
+
+ port_file = os.path.join(current_browser.profile.profile, "DevToolsActivePort")
+ assert os.path.exists(port_file)
+
+ current_browser.quit(clean_profile=False)
+ assert not os.path.exists(port_file)
+
+
+def test_connect(browser):
+ current_browser = browser(use_cdp=True)
+
+ # Both `localhost` and `127.0.0.1` have to accept connections.
+ for target_host in ["127.0.0.1", "localhost"]:
+ print(f"Connecting to the WebSocket via host {target_host}")
+ response = websocket_request(
+ target_host,
+ current_browser.remote_agent_port,
+ path=current_browser.debugger_address,
+ )
+ assert response.status == 101
diff --git a/testing/web-platform/mozilla/tests/webdriver/conftest.py b/testing/web-platform/mozilla/tests/webdriver/conftest.py
new file mode 100644
index 0000000000..d754b39e79
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/conftest.py
@@ -0,0 +1,15 @@
+import os
+import sys
+
+base = os.path.dirname(__file__)
+webdriver_path = os.path.abspath(
+ os.path.join(base, "..", "..", "..", "tests", "webdriver")
+)
+sys.path.insert(0, os.path.join(webdriver_path))
+
+pytest_plugins = [
+ "support.fixtures",
+ "tests.support.fixtures",
+ "tests.support.fixtures_bidi",
+ "tests.support.fixtures_http",
+]
diff --git a/testing/web-platform/mozilla/tests/webdriver/element_send_keys/scroll_into_view.py b/testing/web-platform/mozilla/tests/webdriver/element_send_keys/scroll_into_view.py
new file mode 100644
index 0000000000..080195d345
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/element_send_keys/scroll_into_view.py
@@ -0,0 +1,50 @@
+from tests.support.asserts import assert_success
+from tests.support.helpers import is_element_in_viewport
+
+
+def element_send_keys(session, element, text):
+ return session.transport.send(
+ "POST",
+ "/session/{session_id}/element/{element_id}/value".format(
+ session_id=session.session_id, element_id=element.id
+ ),
+ {"text": text},
+ )
+
+
+def test_option_select_container_outside_of_scrollable_viewport(session, inline):
+ session.url = inline(
+ """
+ <select style="margin-top: 102vh;">
+ <option value="foo">foo</option>
+ <option value="bar" id="bar">bar</option>
+ </select>
+ """
+ )
+ element = session.find.css("option#bar", all=False)
+ select = session.find.css("select", all=False)
+
+ response = element_send_keys(session, element, "bar")
+ assert_success(response)
+
+ assert is_element_in_viewport(session, select)
+ assert is_element_in_viewport(session, element)
+
+
+def test_option_stays_outside_of_scrollable_viewport(session, inline):
+ session.url = inline(
+ """
+ <select multiple style="height: 105vh; margin-top: 100vh;">
+ <option value="foo" id="foo" style="height: 100vh;">foo</option>
+ <option value="bar" id="bar" style="background-color: yellow;">bar</option>
+ </select>
+ """
+ )
+ select = session.find.css("select", all=False)
+ option_bar = session.find.css("option#bar", all=False)
+
+ response = element_send_keys(session, option_bar, "bar")
+ assert_success(response)
+
+ assert is_element_in_viewport(session, select)
+ assert is_element_in_viewport(session, option_bar)
diff --git a/testing/web-platform/mozilla/tests/webdriver/execute_async_script/__init__.py b/testing/web-platform/mozilla/tests/webdriver/execute_async_script/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/execute_async_script/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/execute_async_script/execute_async.py b/testing/web-platform/mozilla/tests/webdriver/execute_async_script/execute_async.py
new file mode 100644
index 0000000000..990f2c1b31
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/execute_async_script/execute_async.py
@@ -0,0 +1,59 @@
+import pytest
+from tests.support.asserts import assert_success
+from tests.support.sync import Poll
+
+
+def execute_async_script(session, script, args=None):
+ if args is None:
+ args = []
+ body = {"script": script, "args": args}
+
+ return session.transport.send(
+ "POST", "/session/{session_id}/execute/async".format(**vars(session)), body
+ )
+
+
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_no_abort_by_user_prompt_in_other_tab(session, inline, dialog_type):
+ original_handle = session.window_handle
+ original_handles = session.handles
+
+ session.url = inline(
+ """
+ <a onclick="window.open();">open window</a>
+ <script>
+ window.addEventListener("message", function (event) {{
+ {}("foo");
+ }});
+ </script>
+ """.format(
+ dialog_type
+ )
+ )
+
+ session.find.css("a", all=False).click()
+ wait = Poll(session, timeout=5, message="No new window has been opened")
+ new_handles = wait.until(lambda s: set(s.handles) - set(original_handles))
+ assert len(new_handles) == 1
+
+ session.window_handle = new_handles.pop()
+
+ response = execute_async_script(
+ session,
+ """
+ const resolve = arguments[0];
+
+ // Trigger opening a user prompt in the other window.
+ window.opener.postMessage("foo", "*");
+
+ // Delay resolving the Promise to ensure a user prompt has been opened.
+ setTimeout(() => resolve(42), 500);
+ """,
+ )
+
+ assert_success(response, 42)
+
+ session.window.close()
+
+ session.window_handle = original_handle
+ session.alert.accept()
diff --git a/testing/web-platform/mozilla/tests/webdriver/get_window_handle/__init__.py b/testing/web-platform/mozilla/tests/webdriver/get_window_handle/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/get_window_handle/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/get_window_handle/chrome.py b/testing/web-platform/mozilla/tests/webdriver/get_window_handle/chrome.py
new file mode 100644
index 0000000000..af24be4b9e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/get_window_handle/chrome.py
@@ -0,0 +1,25 @@
+from support.context import using_context
+from tests.support.asserts import assert_success
+
+
+def get_window_handle(session):
+ return session.transport.send(
+ "GET", "session/{session_id}/window".format(**vars(session))
+ )
+
+
+def test_basic(session):
+ with using_context(session, "chrome"):
+ response = get_window_handle(session)
+ assert_success(response, session.window_handle)
+
+
+def test_different_handle_than_content_scope(session):
+ response = get_window_handle(session)
+ content_handle = assert_success(response)
+
+ with using_context(session, "chrome"):
+ response = get_window_handle(session)
+ chrome_handle = assert_success(response)
+
+ assert chrome_handle != content_handle
diff --git a/testing/web-platform/mozilla/tests/webdriver/get_window_handles/__init__.py b/testing/web-platform/mozilla/tests/webdriver/get_window_handles/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/get_window_handles/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/get_window_handles/chrome.py b/testing/web-platform/mozilla/tests/webdriver/get_window_handles/chrome.py
new file mode 100644
index 0000000000..091ac01e6c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/get_window_handles/chrome.py
@@ -0,0 +1,43 @@
+from support.context import using_context
+from tests.support.asserts import assert_success
+
+
+def get_window_handles(session):
+ return session.transport.send(
+ "GET", "session/{session_id}/window/handles".format(**vars(session))
+ )
+
+
+def test_basic(session):
+ with using_context(session, "chrome"):
+ response = get_window_handles(session)
+ assert_success(response, session.handles)
+
+
+def test_different_handles_than_content_scope(session):
+ response = get_window_handles(session)
+ content_handles = assert_success(response)
+
+ with using_context(session, "chrome"):
+ response = get_window_handles(session)
+ chrome_handles = assert_success(response)
+
+ assert chrome_handles != content_handles
+ assert len(chrome_handles) == 1
+ assert len(content_handles) == 1
+
+
+def test_multiple_windows_and_tabs(session):
+ session.new_window(type_hint="window")
+ session.new_window(type_hint="tab")
+
+ response = get_window_handles(session)
+ content_handles = assert_success(response)
+
+ with using_context(session, "chrome"):
+ response = get_window_handles(session)
+ chrome_handles = assert_success(response)
+
+ assert chrome_handles != content_handles
+ assert len(chrome_handles) == 2
+ assert len(content_handles) == 3
diff --git a/testing/web-platform/mozilla/tests/webdriver/new_session/__init__.py b/testing/web-platform/mozilla/tests/webdriver/new_session/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/new_session/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/new_session/bidi_disabled.py b/testing/web-platform/mozilla/tests/webdriver/new_session/bidi_disabled.py
new file mode 100644
index 0000000000..eeb5a18740
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/new_session/bidi_disabled.py
@@ -0,0 +1,33 @@
+from copy import deepcopy
+
+
+def test_marionette_fallback_webdriver_session(configuration, geckodriver):
+ config = deepcopy(configuration)
+ config["capabilities"]["webSocketUrl"] = True
+
+ prefs = config["capabilities"]["moz:firefoxOptions"].get("prefs", {})
+ prefs.update({"remote.active-protocols": 2})
+ config["capabilities"]["moz:firefoxOptions"]["prefs"] = prefs
+
+ try:
+ driver = geckodriver(config=config)
+ driver.new_session()
+
+ assert driver.session.capabilities.get("webSocketUrl") is None
+
+ # Sanity check that Marionette works as expected and by default returns
+ # at least one window handle
+ assert len(driver.session.handles) >= 1
+
+ finally:
+ driver.stop()
+
+ # WebDriver BiDi has to be re-enabled. Because we cannot easily
+ # get rid of the value let geckodriver overwrite it with the current
+ # default.
+ prefs.update({"remote.active-protocols": 3})
+
+ driver = geckodriver(config=config)
+ driver.new_session()
+
+ assert driver.session.capabilities.get("webSocketUrl") is not None
diff --git a/testing/web-platform/mozilla/tests/webdriver/new_session/binary.py b/testing/web-platform/mozilla/tests/webdriver/new_session/binary.py
new file mode 100644
index 0000000000..2baa40572b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/new_session/binary.py
@@ -0,0 +1,40 @@
+import os
+import sys
+
+from tests.support.asserts import assert_error, assert_success
+
+
+def test_bad_binary(new_session):
+ # skipif annotations are forbidden in wpt
+ if os.path.exists("/bin/echo"):
+ response, _ = new_session(
+ {
+ "capabilities": {
+ "alwaysMatch": {"moz:firefoxOptions": {"binary": "/bin/echo"}}
+ }
+ }
+ )
+ assert_error(response, "invalid argument")
+
+
+def test_shell_script_binary(new_session, configuration):
+ # skipif annotations are forbidden in wpt
+ if sys.platform == "linux":
+ capabilities = configuration["capabilities"].copy()
+ script = (
+ """#!/bin/bash
+
+%s $@"""
+ % capabilities["moz:firefoxOptions"]["binary"]
+ )
+ path = os.path.abspath("firefox.sh")
+ assert not os.path.exists(path)
+ try:
+ with open("firefox.sh", "w") as f:
+ f.write(script)
+ os.chmod(path, 0o744)
+ capabilities["moz:firefoxOptions"]["binary"] = path
+ response, _ = new_session({"capabilities": capabilities})
+ assert_success(response)
+ finally:
+ os.unlink(path)
diff --git a/testing/web-platform/mozilla/tests/webdriver/new_session/conftest.py b/testing/web-platform/mozilla/tests/webdriver/new_session/conftest.py
new file mode 100644
index 0000000000..1cab6784c2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/new_session/conftest.py
@@ -0,0 +1,58 @@
+import pytest
+from webdriver.transport import HTTPWireProtocol
+
+
+@pytest.fixture(name="configuration")
+def fixture_configuration(configuration):
+ """Remove "acceptInsecureCerts" from capabilities if it exists.
+
+ Some browser configurations add acceptInsecureCerts capability by default.
+ Remove it during new_session tests to avoid interference.
+ """
+ if "acceptInsecureCerts" in configuration["capabilities"]:
+ configuration = dict(configuration)
+ del configuration["capabilities"]["acceptInsecureCerts"]
+ return configuration
+
+
+@pytest.fixture(name="new_session")
+def fixture_new_session(request, configuration, current_session):
+ """Start a new session for tests which themselves test creating new sessions.
+
+ :param body: The content of the body for the new session POST request.
+
+ :param delete_existing_session: Allows the fixture to delete an already
+ created custom session before the new session is getting created. This
+ is useful for tests which call this fixture multiple times within the
+ same test.
+ """
+ custom_session = {}
+
+ transport = HTTPWireProtocol(
+ configuration["host"],
+ configuration["port"],
+ url_prefix="/",
+ )
+
+ def _delete_session(session_id):
+ transport.send("DELETE", "session/{}".format(session_id))
+
+ def new_session(body, delete_existing_session=False, headers=None):
+ # If there is an active session from the global session fixture,
+ # delete that one first
+ if current_session is not None:
+ current_session.end()
+
+ if delete_existing_session:
+ _delete_session(custom_session["session"]["sessionId"])
+
+ response = transport.send("POST", "session", body, headers=headers)
+ if response.status == 200:
+ custom_session["session"] = response.body["value"]
+ return response, custom_session.get("session", None)
+
+ yield new_session
+
+ if custom_session.get("session") is not None:
+ _delete_session(custom_session["session"]["sessionId"])
+ custom_session = None
diff --git a/testing/web-platform/mozilla/tests/webdriver/new_session/create.py b/testing/web-platform/mozilla/tests/webdriver/new_session/create.py
new file mode 100644
index 0000000000..9649b938ad
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/new_session/create.py
@@ -0,0 +1,11 @@
+# META: timeout=long
+from tests.support.asserts import assert_success
+
+
+def test_valid_content_type(new_session, configuration):
+ headers = {"content-type": "application/json"}
+ response, _ = new_session(
+ {"capabilities": {"alwaysMatch": dict(configuration["capabilities"])}},
+ headers=headers,
+ )
+ assert_success(response)
diff --git a/testing/web-platform/mozilla/tests/webdriver/new_session/invalid.py b/testing/web-platform/mozilla/tests/webdriver/new_session/invalid.py
new file mode 100644
index 0000000000..dc7a0caee9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/new_session/invalid.py
@@ -0,0 +1,53 @@
+from copy import deepcopy
+
+import pytest
+from tests.support.asserts import assert_error
+
+
+@pytest.mark.parametrize(
+ "headers",
+ [
+ {"origin": "http://localhost"},
+ {"origin": "http://localhost:8000"},
+ {"origin": "http://127.0.0.1"},
+ {"origin": "http://127.0.0.1:8000"},
+ {"origin": "null"},
+ {"ORIGIN": "https://example.org"},
+ {"host": "example.org:4444"},
+ {"Host": "example.org"},
+ {"host": "localhost:80"},
+ {"host": "localhost"},
+ {"content-type": "application/x-www-form-urlencoded"},
+ {"content-type": "multipart/form-data"},
+ {"content-type": "text/plain"},
+ {"Content-TYPE": "APPLICATION/x-www-form-urlencoded"},
+ {"content-type": "MULTIPART/FORM-DATA"},
+ {"CONTENT-TYPE": "TEXT/PLAIN"},
+ {"content-type": "text/plain ; charset=utf-8"},
+ {"content-type": "text/plain;foo"},
+ {"content-type": "text/PLAIN ; foo;charset=utf8"},
+ ],
+)
+def test_invalid(new_session, configuration, headers):
+ response, _ = new_session(
+ {"capabilities": {"alwaysMatch": dict(configuration["capabilities"])}},
+ headers=headers,
+ )
+ assert_error(response, "unknown error")
+
+
+@pytest.mark.parametrize(
+ "argument",
+ [
+ "--marionette",
+ "--remote-debugging-port",
+ "--remote-allow-hosts",
+ "--remote-allow-origins",
+ ],
+)
+def test_forbidden_arguments(configuration, new_session, argument):
+ capabilities = deepcopy(configuration["capabilities"])
+ capabilities["moz:firefoxOptions"]["args"] = [argument]
+
+ response, _ = new_session({"capabilities": {"alwaysMatch": capabilities}})
+ assert_error(response, "invalid argument")
diff --git a/testing/web-platform/mozilla/tests/webdriver/new_session/profile_root.py b/testing/web-platform/mozilla/tests/webdriver/new_session/profile_root.py
new file mode 100644
index 0000000000..582e7124e4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/new_session/profile_root.py
@@ -0,0 +1,36 @@
+import copy
+import os
+
+import pytest
+
+
+def test_profile_root(tmp_path, configuration, geckodriver):
+ profile_path = os.path.join(tmp_path, "geckodriver-test")
+ os.makedirs(profile_path)
+
+ config = copy.deepcopy(configuration)
+ # Ensure we don't set a profile in command line arguments
+ del config["capabilities"]["moz:firefoxOptions"]["args"]
+
+ extra_args = ["--profile-root", profile_path]
+
+ assert os.listdir(profile_path) == []
+
+ driver = geckodriver(config=config, extra_args=extra_args)
+ driver.new_session()
+ assert len(os.listdir(profile_path)) == 1
+ driver.delete_session()
+ assert os.listdir(profile_path) == []
+
+
+def test_profile_root_missing(tmp_path, configuration, geckodriver):
+ profile_path = os.path.join(tmp_path, "missing-path")
+
+ config = copy.deepcopy(configuration)
+ # Ensure we don't set a profile in command line arguments
+ del config["capabilities"]["moz:firefoxOptions"]["args"]
+
+ extra_args = ["--profile-root", profile_path]
+
+ with pytest.raises(Exception):
+ geckodriver(config=config, extra_args=extra_args)
diff --git a/testing/web-platform/mozilla/tests/webdriver/protocol/__init__.py b/testing/web-platform/mozilla/tests/webdriver/protocol/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/protocol/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/protocol/allow_hosts.py b/testing/web-platform/mozilla/tests/webdriver/protocol/allow_hosts.py
new file mode 100644
index 0000000000..17ae2c2c68
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/protocol/allow_hosts.py
@@ -0,0 +1,53 @@
+from copy import deepcopy
+
+import pytest
+from support.network import get_host, http_request, websocket_request
+
+
+@pytest.mark.parametrize(
+ "allow_hosts, hostname, port_type, status",
+ [
+ # Valid hosts
+ (["localhost.localdomain", "localhost"], "localhost", "server_port", 200),
+ (["localhost.localdomain", "localhost"], "127.0.0.1", "server_port", 200),
+ # Invalid hosts
+ (["localhost.localdomain"], "localhost", "server_port", 500),
+ (["localhost"], "localhost", "wrong_port", 500),
+ (["www.localhost"], "localhost", "server_port", 500),
+ ],
+)
+def test_allow_hosts(geckodriver, allow_hosts, hostname, port_type, status):
+ extra_args = ["--allow-hosts"] + allow_hosts
+
+ driver = geckodriver(hostname=hostname, extra_args=extra_args)
+ host = get_host(port_type, hostname, driver.port)
+ response = http_request(driver.hostname, driver.port, host=host)
+
+ assert response.status == status
+
+
+@pytest.mark.parametrize(
+ "allow_hosts, hostname, status",
+ [
+ (["mozilla.org", "testhost"], "testhost", 101),
+ (["mozilla.org"], "testhost", 400),
+ ],
+ ids=["allowed", "not allowed"],
+)
+def test_allow_hosts_passed_to_remote_agent(
+ configuration, geckodriver, allow_hosts, hostname, status
+):
+ config = deepcopy(configuration)
+ config["capabilities"]["webSocketUrl"] = True
+
+ extra_args = ["--allow-hosts"] + allow_hosts
+
+ driver = geckodriver(config=config, extra_args=extra_args)
+
+ driver.new_session()
+
+ host = get_host("default_port", hostname, driver.remote_agent_port)
+ response = websocket_request("127.0.0.1", driver.remote_agent_port, host=host)
+ assert response.status == status
+
+ driver.delete_session()
diff --git a/testing/web-platform/mozilla/tests/webdriver/protocol/allow_origins.py b/testing/web-platform/mozilla/tests/webdriver/protocol/allow_origins.py
new file mode 100644
index 0000000000..72b6fba482
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/protocol/allow_origins.py
@@ -0,0 +1,56 @@
+from copy import deepcopy
+
+import pytest
+from support.network import http_request, websocket_request
+
+
+@pytest.mark.parametrize(
+ "allow_origins, origin, status",
+ [
+ # Valid origins
+ (["http://web-platform.test"], "http://web-platform.test", 200),
+ (["http://web-platform.test"], "http://web-platform.test:80", 200),
+ (["https://web-platform.test"], "https://web-platform.test:443", 200),
+ # Invalid origins
+ (["https://web-platform.test"], "http://web-platform.test", 500),
+ (["http://web-platform.test:8000"], "http://web-platform.test", 500),
+ (["http://web-platform.test"], "http://www.web-platform.test", 500),
+ ],
+)
+def test_allow_hosts(configuration, geckodriver, allow_origins, origin, status):
+ extra_args = ["--allow-origins"] + allow_origins
+
+ driver = geckodriver(extra_args=extra_args)
+ response = http_request(driver.hostname, driver.port, origin=origin)
+
+ assert response.status == status
+
+
+@pytest.mark.parametrize(
+ "allow_origins, origin, status",
+ [
+ (
+ ["https://web-platform.test", "http://web-platform.test"],
+ "http://web-platform.test",
+ 101,
+ ),
+ (["https://web-platform.test"], "http://web-platform.test", 400),
+ ],
+ ids=["allowed", "not allowed"],
+)
+def test_allow_origins_passed_to_remote_agent(
+ configuration, geckodriver, allow_origins, origin, status
+):
+ config = deepcopy(configuration)
+ config["capabilities"]["webSocketUrl"] = True
+
+ extra_args = ["--allow-origins"] + allow_origins
+
+ driver = geckodriver(config=config, extra_args=extra_args)
+
+ driver.new_session()
+
+ response = websocket_request("127.0.0.1", driver.remote_agent_port, origin=origin)
+ assert response.status == status
+
+ driver.delete_session()
diff --git a/testing/web-platform/mozilla/tests/webdriver/protocol/marionette_port.py b/testing/web-platform/mozilla/tests/webdriver/protocol/marionette_port.py
new file mode 100644
index 0000000000..09951abc43
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/protocol/marionette_port.py
@@ -0,0 +1,41 @@
+import os
+from copy import deepcopy
+
+import pytest
+
+
+@pytest.mark.parametrize("port", ["0", "2828"], ids=["system allocated", "fixed"])
+def test_marionette_port(geckodriver, port):
+ extra_args = ["--marionette-port", port]
+
+ driver = geckodriver(extra_args=extra_args)
+ driver.new_session()
+ driver.delete_session()
+
+
+def test_marionette_port_outdated_active_port_file(
+ configuration, geckodriver, custom_profile
+):
+ config = deepcopy(configuration)
+ extra_args = ["--marionette-port", "0"]
+
+ # Prepare a Marionette active port file that contains a port which will
+ # never be used when requesting a system allocated port.
+ active_port_file = os.path.join(custom_profile.profile, "MarionetteActivePort")
+ with open(active_port_file, "wb") as f:
+ f.write(b"53")
+
+ config["capabilities"]["moz:firefoxOptions"]["args"] = [
+ "--profile",
+ custom_profile.profile,
+ ]
+
+ driver = geckodriver(config=config, extra_args=extra_args)
+
+ driver.new_session()
+ with open(active_port_file, "rb") as f:
+ assert f.readline() != b"53"
+
+ driver.delete_session()
+ with pytest.raises(FileNotFoundError):
+ open(active_port_file, "rb")
diff --git a/testing/web-platform/mozilla/tests/webdriver/protocol/request.py b/testing/web-platform/mozilla/tests/webdriver/protocol/request.py
new file mode 100644
index 0000000000..ad99d6964d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/protocol/request.py
@@ -0,0 +1,72 @@
+import pytest
+from support.network import get_host, http_request
+
+
+@pytest.mark.parametrize(
+ "hostname, port_type, status",
+ [
+ # Valid hosts
+ ("localhost", "server_port", 200),
+ ("127.0.0.1", "server_port", 200),
+ ("[::1]", "server_port", 200),
+ ("192.168.8.1", "server_port", 200),
+ ("[fdf8:f535:82e4::53]", "server_port", 200),
+ # Invalid hosts
+ ("localhost", "default_port", 500),
+ ("127.0.0.1", "default_port", 500),
+ ("[::1]", "default_port", 500),
+ ("192.168.8.1", "default_port", 500),
+ ("[fdf8:f535:82e4::53]", "default_port", 500),
+ ("example.org", "server_port", 500),
+ ("example.org", "wrong_port", 500),
+ ("example.org", "default_port", 500),
+ ("localhost", "wrong_port", 500),
+ ("127.0.0.1", "wrong_port", 500),
+ ("[::1]", "wrong_port", 500),
+ ("192.168.8.1", "wrong_port", 500),
+ ("[fdf8:f535:82e4::53]", "wrong_port", 500),
+ ],
+ ids=[
+ # Valid hosts
+ "localhost with same port as server",
+ "127.0.0.1 (loopback) with same port as server",
+ "[::1] (ipv6 loopback) with same port as server",
+ "ipv4 address with same port as server",
+ "ipv6 address with same port as server",
+ # Invalid hosts
+ "localhost with default port",
+ "127.0.0.1 (loopback) with default port",
+ "[::1] (ipv6 loopback) with default port",
+ "ipv4 address with default port",
+ "ipv6 address with default port",
+ "random hostname with the same port as server",
+ "random hostname with a different port than server",
+ "random hostname with default port",
+ "localhost with a different port than server",
+ "127.0.0.1 (loopback) with a different port than server",
+ "[::1] (ipv6 loopback) with a different port than server",
+ "ipv4 address with a different port than server",
+ "ipv6 address with a different port than server",
+ ],
+)
+def test_host_header(configuration, hostname, port_type, status):
+ host = get_host(port_type, hostname, configuration["port"])
+ response = http_request(configuration["host"], configuration["port"], host=host)
+
+ assert response.status == status
+
+
+@pytest.mark.parametrize(
+ "origin, add_port, status",
+ [
+ (None, False, 200),
+ ("", False, 500),
+ ("sometext", False, 500),
+ ("http://localhost", True, 500),
+ ],
+)
+def test_origin_header(configuration, origin, add_port, status):
+ if add_port:
+ origin = f"{origin}:{configuration['port']}"
+ response = http_request(configuration["host"], configuration["port"], origin=origin)
+ assert response.status == status
diff --git a/testing/web-platform/mozilla/tests/webdriver/send_alert_text.py b/testing/web-platform/mozilla/tests/webdriver/send_alert_text.py
new file mode 100644
index 0000000000..60d6a02af0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/send_alert_text.py
@@ -0,0 +1,22 @@
+from tests.support.asserts import assert_error
+from tests.support.http_handlers.authentication import basic_authentication
+
+
+def send_alert_text(session, text=None):
+ return session.transport.send(
+ "POST",
+ "session/{session_id}/alert/text".format(**vars(session)),
+ {"text": text},
+ )
+
+
+def test_basic_auth_unsupported_operation(url, session):
+ """
+ Basic auth dialogues are not included in HTML's definition of
+ 'user prompts': those are limited to the 'simple dialogues'
+ such as window.alert(), window.prompt() et al. and the print
+ dialogue.
+ """
+ session.url = basic_authentication(url)
+ response = send_alert_text(session, "Federer")
+ assert_error(response, "unsupported operation")
diff --git a/testing/web-platform/mozilla/tests/webdriver/support/__init__.py b/testing/web-platform/mozilla/tests/webdriver/support/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/support/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/support/context.py b/testing/web-platform/mozilla/tests/webdriver/support/context.py
new file mode 100644
index 0000000000..910b202075
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/support/context.py
@@ -0,0 +1,20 @@
+import contextlib
+
+
+def set_context(session, context):
+ session.send_session_command("POST", "moz/context", {"context": context})
+
+
+@contextlib.contextmanager
+def using_context(session, context):
+ orig_context = session.send_session_command("GET", "moz/context")
+ needs_change = context != orig_context
+
+ if needs_change:
+ set_context(session, context)
+
+ try:
+ yield
+ finally:
+ if needs_change:
+ set_context(session, orig_context)
diff --git a/testing/web-platform/mozilla/tests/webdriver/support/fixtures.py b/testing/web-platform/mozilla/tests/webdriver/support/fixtures.py
new file mode 100644
index 0000000000..f0646da310
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/support/fixtures.py
@@ -0,0 +1,258 @@
+import json
+import os
+import socket
+import time
+from contextlib import suppress
+from urllib.parse import urlparse
+
+import pytest
+import webdriver
+from mozprocess import ProcessHandler
+from mozprofile import Profile
+from mozrunner import FirefoxRunner
+from support.network import get_free_port
+
+
+@pytest.fixture(scope="module")
+def browser(full_configuration):
+ """Start a Firefox instance without using geckodriver.
+
+ geckodriver will automatically use the --remote-allow-hosts and
+ --remote.allow.origins command line arguments.
+
+ Starting Firefox without geckodriver allows to set those command line arguments
+ as needed. The fixture method returns the browser instance that should be used
+ to connect to a RemoteAgent supported protocol (CDP, WebDriver BiDi).
+ """
+ current_browser = None
+
+ def _browser(use_bidi=False, use_cdp=False, extra_args=None, extra_prefs=None):
+ nonlocal current_browser
+
+ # If the requested preferences and arguments match the ones for the
+ # already started firefox, we can reuse the current firefox instance,
+ # return the instance immediately.
+ if current_browser:
+ if (
+ current_browser.use_bidi == use_bidi
+ and current_browser.use_cdp == use_cdp
+ and current_browser.extra_args == extra_args
+ and current_browser.extra_prefs == extra_prefs
+ and current_browser.is_running
+ ):
+ return current_browser
+
+ # Otherwise, if firefox is already started, terminate it because we need
+ # to create a new instance for the provided preferences.
+ current_browser.quit()
+
+ firefox_options = full_configuration["capabilities"]["moz:firefoxOptions"]
+ current_browser = Browser(
+ firefox_options,
+ use_bidi=use_bidi,
+ use_cdp=use_cdp,
+ extra_args=extra_args,
+ extra_prefs=extra_prefs,
+ )
+ current_browser.start()
+ return current_browser
+
+ yield _browser
+
+ # Stop firefox at the end of the test module.
+ if current_browser is not None:
+ current_browser.quit()
+ current_browser = None
+
+
+@pytest.fixture
+def custom_profile(configuration):
+ # Clone the known profile for automation preferences
+ firefox_options = configuration["capabilities"]["moz:firefoxOptions"]
+ _, profile_folder = firefox_options["args"]
+ profile = Profile.clone(profile_folder)
+
+ yield profile
+
+ profile.cleanup()
+
+
+@pytest.fixture
+def geckodriver(configuration):
+ """Start a geckodriver instance directly."""
+ driver = None
+
+ def _geckodriver(config=None, hostname=None, extra_args=None):
+ nonlocal driver
+
+ if config is None:
+ config = configuration
+
+ driver = Geckodriver(config, hostname, extra_args)
+ driver.start()
+
+ return driver
+
+ yield _geckodriver
+
+ if driver is not None:
+ driver.stop()
+
+
+class Browser:
+ def __init__(
+ self,
+ firefox_options,
+ use_bidi=False,
+ use_cdp=False,
+ extra_args=None,
+ extra_prefs=None,
+ ):
+ self.use_bidi = use_bidi
+ self.bidi_port_file = None
+ self.use_cdp = use_cdp
+ self.cdp_port_file = None
+ self.extra_args = extra_args
+ self.extra_prefs = extra_prefs
+
+ self.debugger_address = None
+ self.remote_agent_host = None
+ self.remote_agent_port = None
+
+ # Prepare temporary profile
+ _profile_arg, profile_folder = firefox_options["args"]
+ self.profile = Profile.clone(profile_folder)
+ if self.extra_prefs is not None:
+ self.profile.set_preferences(self.extra_prefs)
+
+ if use_cdp:
+ self.cdp_port_file = os.path.join(
+ self.profile.profile, "DevToolsActivePort"
+ )
+ with suppress(FileNotFoundError):
+ os.remove(self.cdp_port_file)
+ if use_bidi:
+ self.webdriver_bidi_file = os.path.join(
+ self.profile.profile, "WebDriverBiDiServer.json"
+ )
+ with suppress(FileNotFoundError):
+ os.remove(self.webdriver_bidi_file)
+
+ # Prepare Firefox runner
+ binary = firefox_options["binary"]
+
+ cmdargs = ["-no-remote"]
+ if self.use_bidi or self.use_cdp:
+ cmdargs.extend(["--remote-debugging-port", "0"])
+ if self.extra_args is not None:
+ cmdargs.extend(self.extra_args)
+ self.runner = FirefoxRunner(
+ binary=binary, profile=self.profile, cmdargs=cmdargs
+ )
+
+ @property
+ def is_running(self):
+ return self.runner.is_running()
+
+ def start(self):
+ # Start Firefox.
+ self.runner.start()
+
+ if self.use_bidi:
+ # Wait until the WebDriverBiDiServer.json file is ready
+ while not os.path.exists(self.webdriver_bidi_file):
+ time.sleep(0.1)
+
+ # Read the connection details from file
+ data = json.loads(open(self.webdriver_bidi_file).read())
+ self.remote_agent_host = data["ws_host"]
+ self.remote_agent_port = int(data["ws_port"])
+
+ if self.use_cdp:
+ # Wait until the DevToolsActivePort file is ready
+ while not os.path.exists(self.cdp_port_file):
+ time.sleep(0.1)
+
+ # Read the port if needed and the debugger address from the
+ # DevToolsActivePort file
+ lines = open(self.cdp_port_file).readlines()
+ assert len(lines) == 2
+
+ if self.remote_agent_port is None:
+ self.remote_agent_port = int(lines[0].strip())
+ self.debugger_address = lines[1].strip()
+
+ def quit(self, clean_profile=True):
+ if self.is_running:
+ self.runner.stop()
+ self.runner.cleanup()
+
+ if clean_profile:
+ self.profile.cleanup()
+
+
+class Geckodriver:
+ def __init__(self, configuration, hostname=None, extra_args=None):
+ self.config = configuration["webdriver"]
+ self.requested_capabilities = configuration["capabilities"]
+ self.hostname = hostname or configuration["host"]
+ self.extra_args = extra_args or []
+
+ self.command = None
+ self.proc = None
+ self.port = get_free_port()
+
+ capabilities = {"alwaysMatch": self.requested_capabilities}
+ self.session = webdriver.Session(
+ self.hostname, self.port, capabilities=capabilities
+ )
+
+ @property
+ def remote_agent_port(self):
+ webSocketUrl = self.session.capabilities.get("webSocketUrl")
+ assert webSocketUrl is not None
+
+ return urlparse(webSocketUrl).port
+
+ def start(self):
+ self.command = (
+ [self.config["binary"], "--port", str(self.port)]
+ + self.config["args"]
+ + self.extra_args
+ )
+
+ def processOutputLine(line):
+ print(line)
+
+ print(f"Running command: {self.command}")
+ self.proc = ProcessHandler(
+ self.command, processOutputLine=processOutputLine, universal_newlines=True
+ )
+ self.proc.run()
+
+ # Wait for the port to become ready
+ end_time = time.time() + 10
+ while time.time() < end_time:
+ if self.proc.poll() is not None:
+ raise Exception(f"geckodriver terminated with code {self.proc.poll()}")
+ with socket.socket() as sock:
+ if sock.connect_ex((self.hostname, self.port)) == 0:
+ break
+ else:
+ raise Exception(
+ f"Failed to connect to geckodriver on {self.hostname}:{self.port}"
+ )
+
+ return self
+
+ def stop(self):
+ self.delete_session()
+
+ if self.proc:
+ self.proc.kill()
+
+ def new_session(self):
+ self.session.start()
+
+ def delete_session(self):
+ self.session.end()
diff --git a/testing/web-platform/mozilla/tests/webdriver/support/network.py b/testing/web-platform/mozilla/tests/webdriver/support/network.py
new file mode 100644
index 0000000000..25492ca5e5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/support/network.py
@@ -0,0 +1,78 @@
+import socket
+from http.client import HTTPConnection
+
+
+def websocket_request(
+ remote_agent_host, remote_agent_port, host=None, origin=None, path="/session"
+):
+ real_host = f"{remote_agent_host}:{remote_agent_port}"
+ url = f"http://{real_host}{path}"
+
+ conn = HTTPConnection(real_host)
+
+ skip_host = host is not None
+ conn.putrequest("GET", url, skip_host)
+
+ if host is not None:
+ conn.putheader("Host", host)
+
+ if origin is not None:
+ conn.putheader("Origin", origin)
+
+ conn.putheader("Connection", "upgrade")
+ conn.putheader("Upgrade", "websocket")
+ conn.putheader("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
+ conn.putheader("Sec-WebSocket-Version", "13")
+
+ conn.endheaders()
+
+ return conn.getresponse()
+
+
+def http_request(server_host, server_port, path="/status", host=None, origin=None):
+ url = f"http://{server_host}:{server_port}{path}"
+
+ conn = HTTPConnection(server_host, server_port)
+
+ custom_host = host is not None
+ conn.putrequest("GET", url, skip_host=custom_host)
+ if custom_host:
+ conn.putheader("Host", host)
+
+ if origin is not None:
+ conn.putheader("Origin", origin)
+
+ conn.endheaders()
+
+ return conn.getresponse()
+
+
+def get_free_port():
+ """Get a random unbound port"""
+ max_attempts = 10
+ err = None
+ for _ in range(max_attempts):
+ s = socket.socket()
+ try:
+ s.bind(("127.0.0.1", 0))
+ except OSError as e:
+ err = e
+ continue
+ else:
+ return s.getsockname()[1]
+ finally:
+ s.close()
+ if err is None:
+ err = Exception("Failed to get a free port")
+ raise err
+
+
+def get_host(port_type, hostname, server_port):
+ if port_type == "default_port":
+ return hostname
+ if port_type == "server_port":
+ return f"{hostname}:{server_port}"
+ if port_type == "wrong_port":
+ wrong_port = int(server_port) + 1
+ return f"{hostname}:{wrong_port}"
+ raise Exception(f"Unrecognised port_type {port_type}")
diff --git a/testing/web-platform/mozilla/tests/webdriver/take_full_screenshot/__init__.py b/testing/web-platform/mozilla/tests/webdriver/take_full_screenshot/__init__.py
new file mode 100644
index 0000000000..11a8a58a0f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/take_full_screenshot/__init__.py
@@ -0,0 +1,12 @@
+def document_dimensions(session):
+ return tuple(
+ session.execute_script(
+ """
+ const {devicePixelRatio} = window;
+ const width = document.documentElement.scrollWidth;
+ const height = document.documentElement.scrollHeight;
+
+ return [Math.floor(width * devicePixelRatio), Math.floor(height * devicePixelRatio)];
+ """
+ )
+ )
diff --git a/testing/web-platform/mozilla/tests/webdriver/take_full_screenshot/iframe.py b/testing/web-platform/mozilla/tests/webdriver/take_full_screenshot/iframe.py
new file mode 100644
index 0000000000..fc231f2e11
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/take_full_screenshot/iframe.py
@@ -0,0 +1,47 @@
+import pytest
+from tests.support.asserts import assert_success
+from tests.support.image import png_dimensions
+
+from . import document_dimensions
+
+DEFAULT_CSS_STYLE = """
+ <style>
+ div, iframe {
+ display: block;
+ border: 1px solid blue;
+ width: 10em;
+ height: 10em;
+ }
+ </style>
+"""
+
+DEFAULT_CONTENT = "<div>Lorem ipsum dolor sit amet.</div>"
+
+
+def take_full_screenshot(session):
+ return session.transport.send(
+ "GET",
+ "/session/{session_id}/moz/screenshot/full".format(
+ session_id=session.session_id
+ ),
+ )
+
+
+@pytest.mark.parametrize("domain", ["", "alt"], ids=["same_origin", "cross_origin"])
+def test_source_origin(session, url, domain, inline, iframe):
+ session.url = inline("""{0}{1}""".format(DEFAULT_CSS_STYLE, DEFAULT_CONTENT))
+
+ response = take_full_screenshot(session)
+ reference_screenshot = assert_success(response)
+ assert png_dimensions(reference_screenshot) == document_dimensions(session)
+
+ iframe_content = "<style>body {{ margin: 0; }}</style>{}".format(DEFAULT_CONTENT)
+ session.url = inline(
+ """{0}{1}""".format(DEFAULT_CSS_STYLE, iframe(iframe_content, domain=domain))
+ )
+
+ response = take_full_screenshot(session)
+ screenshot = assert_success(response)
+ assert png_dimensions(screenshot) == document_dimensions(session)
+
+ assert screenshot == reference_screenshot
diff --git a/testing/web-platform/mozilla/tests/webdriver/take_full_screenshot/screenshot.py b/testing/web-platform/mozilla/tests/webdriver/take_full_screenshot/screenshot.py
new file mode 100644
index 0000000000..02373afd57
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/take_full_screenshot/screenshot.py
@@ -0,0 +1,51 @@
+from tests.support.asserts import assert_error, assert_png, assert_success
+from tests.support.image import png_dimensions
+
+from . import document_dimensions
+
+
+def take_full_screenshot(session):
+ return session.transport.send(
+ "GET",
+ "/session/{session_id}/moz/screenshot/full".format(
+ session_id=session.session_id
+ ),
+ )
+
+
+def test_no_browsing_context(session, closed_window):
+ response = take_full_screenshot(session)
+ assert_error(response, "no such window")
+
+
+def test_html_document(session, inline):
+ session.url = inline("<input>")
+
+ response = take_full_screenshot(session)
+ value = assert_success(response)
+ assert_png(value)
+ assert png_dimensions(value) == document_dimensions(session)
+
+
+def test_xhtml_document(session, inline):
+ session.url = inline('<input type="text" />', doctype="xhtml")
+
+ response = take_full_screenshot(session)
+ value = assert_success(response)
+ assert_png(value)
+ assert png_dimensions(value) == document_dimensions(session)
+
+
+def test_document_extends_beyond_viewport(session, inline):
+ session.url = inline(
+ """
+ <style>
+ body { min-height: 200vh }
+ </style>
+ """
+ )
+
+ response = take_full_screenshot(session)
+ value = assert_success(response)
+ assert_png(value)
+ assert png_dimensions(value) == document_dimensions(session)
diff --git a/testing/web-platform/mozilla/tests/websockets/bug1793935.any.js b/testing/web-platform/mozilla/tests/websockets/bug1793935.any.js
new file mode 100644
index 0000000000..4ef84b3da4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/websockets/bug1793935.any.js
@@ -0,0 +1,23 @@
+// The module has an import that's immediately called by the start function.
+const module = new Uint8Array([
+ 0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96,
+ 0, 0, 2, 7, 1, 1, 97, 1, 98, 0, 0, 3,
+ 2, 1, 0, 8, 1, 1, 10, 6, 1, 4, 0, 16,
+ 0, 11, 0, 36, 16, 115, 111, 117, 114, 99, 101, 77,
+ 97, 112, 112, 105, 110, 103, 85, 82, 76, 18, 46, 47,
+ 114, 101, 108, 101, 97, 115, 101, 46, 119, 97, 115, 109,
+ 46, 109, 97, 112
+]);
+
+// The WebSocket server does not need to exist, because the bug occurs before
+// any connection is attempted.
+const imports = {
+ a: {
+ b: Reflect.construct.bind(null, WebSocket, ["ws://localhost:1234"])
+ }
+};
+
+promise_test(
+ () => WebAssembly.instantiate(module, imports),
+ "Ensure WebSockets can be constructed from WebAssembly"
+); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/workers/2-mib-file.py b/testing/web-platform/mozilla/tests/workers/2-mib-file.py
new file mode 100644
index 0000000000..cfb563ff21
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/2-mib-file.py
@@ -0,0 +1,7 @@
+import random
+import string
+
+
+def main(request, response):
+ r = "".join(random.choice(string.ascii_letters) for _ in range(2 * 1024 * 1024))
+ return r
diff --git a/testing/web-platform/mozilla/tests/workers/bug1674278-crash.html b/testing/web-platform/mozilla/tests/workers/bug1674278-crash.html
new file mode 100644
index 0000000000..2b037e5b37
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/bug1674278-crash.html
@@ -0,0 +1,6 @@
+<html class='test-wait'>
+<script>
+var worker = new Worker('bug1674278.js');
+worker.postMessage('', []);
+</script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/workers/bug1674278.js b/testing/web-platform/mozilla/tests/workers/bug1674278.js
new file mode 100644
index 0000000000..56105cb76e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/bug1674278.js
@@ -0,0 +1,6 @@
+self.onmessage = async function(e) {
+ var a = await self.fetch('2-mib-file.py');
+ var b = await a.blob();
+ self.close()
+ await b.arrayBuffer();
+}
diff --git a/testing/web-platform/mozilla/tests/workers/resources/worker.js b/testing/web-platform/mozilla/tests/workers/resources/worker.js
new file mode 100644
index 0000000000..cc1692eb9c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/resources/worker.js
@@ -0,0 +1,129 @@
+const maxNestingLevel = 5;
+let expectedNestingLevel = 1;
+let timer;
+let isInterval = false;
+let testStage = "ScriptLoaded";
+let stopIncreaseExpectedLevel = false;
+let startClampedTimeStamp = 0;
+let startRepeatingClamped = false;
+let repeatCount = 0;
+let maxRepeatTimes = 10;
+
+let timerCallback = async () => {
+ let now = Date.now();
+ if (WorkerTestUtils.currentTimerNestingLevel() !== expectedNestingLevel) {
+ postMessage({
+ stage: testStage,
+ status: "FAIL",
+ msg: `current timer nesting level is ${WorkerTestUtils.currentTimerNestingLevel()}, expected ${expectedNestingLevel}`,
+ });
+ if (isInterval) {
+ clearInterval(timer);
+ }
+ return;
+ }
+
+ if (!stopIncreaseExpectedLevel) {
+ if (expectedNestingLevel === maxNestingLevel) {
+ stopIncreaseExpectedLevel = true;
+ startClampedTimeStamp = now;
+ } else {
+ expectedNestingLevel = expectedNestingLevel + 1;
+ }
+ if (!isInterval) {
+ setTimeout(timerCallback, 0);
+ }
+ return;
+ }
+
+ // This is the first time the timeout is clamped, checking if it is clamped
+ // to at least 2ms.
+ if (repeatCount === 0) {
+ await Promise.resolve(true).then(() => {
+ if (WorkerTestUtils.currentTimerNestingLevel() !== expectedNestingLevel) {
+ postMessage({
+ stage: testStage,
+ status: "FAIL",
+ msg: `Timer nesting level should be in effect for immediately resolved micro-tasks`,
+ });
+ }
+ });
+ if (now - startClampedTimeStamp < 2 ) {
+ startRepeatingClamped = true;
+ } else {
+ postMessage({ stage: testStage, status: "PASS", msg: "" });
+ }
+ }
+
+ // If the first clamped timeout is less than 2ms, start to repeat the clamped
+ // timeout for 10 times. Then checking if total clamped time should be at least
+ // 25ms.
+ if (startRepeatingClamped) {
+ if (repeatCount === 10) {
+ if (now - startClampedTimeStamp < 25) {
+ postMessage({
+ stage: testStage,
+ status: "FAIL",
+ msg: `total clamped time of repeating ten times should be at least 25ms(${now - startClampedTimeStamp})`,
+ });
+ } else {
+ postMessage({ stage: testStage, status: "PASS", msg: "" });
+ }
+ } else {
+ repeatCount = repeatCount + 1;
+ if (!isInterval) {
+ setTimeout(timerCallback, 0);
+ }
+ return;
+ }
+ }
+
+ // reset testing variables
+ repeatCount = 0;
+ startRepeatingClamped = false;
+ stopIncreaseExpectedLevel = false;
+ if (isInterval) {
+ clearInterval(timer);
+ }
+};
+
+onmessage = async e => {
+ testStage = e.data;
+ switch (e.data) {
+ case "CheckInitialValue":
+ if (WorkerTestUtils.currentTimerNestingLevel() === 0) {
+ postMessage({ stage: testStage, status: "PASS", msg: "" });
+ } else {
+ postMessage({
+ stage: testStage,
+ status: "FAIL",
+ msg: `current timer nesting level should be 0(${WorkerTestUtils.currentTimerNestingLevel()}) after top level script loaded.`,
+ });
+ }
+ break;
+ case "TestSetInterval":
+ expectedNestingLevel = 1;
+ isInterval = true;
+ timer = setInterval(timerCallback, 0);
+ break;
+ case "TestSetTimeout":
+ expectedNestingLevel = 1;
+ isInterval = false;
+ setTimeout(timerCallback, 0);
+ break;
+ case "CheckNoTimer":
+ if (WorkerTestUtils.currentTimerNestingLevel() === 0) {
+ postMessage({ stage: testStage, status: "PASS", msg: "" });
+ } else {
+ postMessage({
+ stage: testStage,
+ status: "FAIL",
+ msg: `current timer nesting level should be 0(${WorkerTestUtils.currentTimerNestingLevel()}) when there is no timer in queue.`,
+ });
+ }
+
+ break;
+ }
+};
+
+postMessage({ stage: testStage, status: "PASS" });
diff --git a/testing/web-platform/mozilla/tests/workers/worker_timer_nesting_level.html b/testing/web-platform/mozilla/tests/workers/worker_timer_nesting_level.html
new file mode 100644
index 0000000000..e39f9e1b0e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/worker_timer_nesting_level.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Worker: Timer Nesting Level</title>
+<Script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict'
+
+/**
+ * This test includes following four test stages.
+ * 1. CheckInitialValue: Checking the initial value of worker's current timer
+ * nesting level after the worker's top level script is loaded. The result
+ * is expected as 0.
+ * 2. TestSetInterval: Checking the worker's current timer nesting level with
+ * setInterval with following steps
+ * 1. call setInterval(callback, 0) to create a repeating timer.
+ * 2. checking the current timer nesting level in the callback. The value
+ * should increase every time executing the callback until it reaches the
+ * maximun nesting level(5).
+ * 3. Checking the worker's current timer nesting level with immediately
+ * resolved promise.
+ * 4. Checking the the time duration between two callback launching.
+ * 3. TestSetTimeout: Checking the worker's current timer nesting level with
+ * setTimeout. This stage has similar test steps with TestSetInterval.
+ * The difference is this stage using the recursive setTimeout to accumulate
+ * the timer nesting level.
+ * 4. CheckNoTimer: Checking the situation which the worker has no pending
+ * timer. The result is expected as 0.
+ */
+
+let testStages = ["CheckInitialValue",
+ "TestSetInterval",
+ "TestSetTimeout",
+ "CheckNoTimer"];
+
+promise_test(async function(t) {
+ let result = await new Promise( (resolve, reject) => {
+ let worker = new Worker("resources/worker.js");
+ worker.onmessage = (e) => {
+ if (e.data.status === "FAIL") {
+ resolve(e.data);
+ return;
+ }
+ if (testStages.length !== 0) {
+ worker.postMessage(testStages.shift());
+ } else {
+ resolve({status: "PASS", msg: "Timer nesting level for workers"});
+ }
+ };
+ });
+ assert_true(result.status === "PASS", result.msg);
+}, 'Worker timer nesting level');
+</script>
diff --git a/testing/web-platform/mozilla/tests/xml/parsedepth.html b/testing/web-platform/mozilla/tests/xml/parsedepth.html
new file mode 100644
index 0000000000..b0b17fe98e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/xml/parsedepth.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+
+function parseBlob(blob) {
+ return new Promise(resolve => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", URL.createObjectURL(blob));
+ xhr.onload = () => {
+ resolve(xhr.responseXML);
+ }
+ xhr.send();
+ });
+}
+
+promise_test(async (t) => {
+ // Most browser engines, including Gecko, use 5000 as the limit, so test a
+ // range around that.
+ const cutoff = 5000;
+
+ let minDepth = cutoff - 100;
+ let maxDepth = cutoff + 100;
+
+ // Generate a string with elements nested maxDepth deep.
+ const openTag = "<x>";
+ const closeTag = "</x>";
+ let xml = openTag.repeat(maxDepth) + closeTag.repeat(maxDepth);
+
+ // Compute where we change from opening to closing tags.
+ const middle = maxDepth * openTag.length;
+
+ // Create a blob around the string.
+ let blob = new Blob([xml], { type: "application/xml" });
+
+ while (minDepth < maxDepth) {
+ // Try to parse a number of nested tags between minDepth and maxDepth.
+ let test = Math.ceil((minDepth + maxDepth) / 2);
+
+ // We need the number of opening and closing tags to be equal to the number
+ // that we calculated above.
+ let slice = blob.slice(middle - (test * openTag.length),
+ middle + (test * closeTag.length));
+
+ let responseXML = await parseBlob(slice);
+
+ // Move either minDepth or maxDepth so that the actual limit is still in the
+ // range of [minDepth-maxDepth].
+ if (responseXML) {
+ // Depth is ok.
+ minDepth = test;
+ } else {
+ maxDepth = test - 1;
+ }
+ }
+ assert_equals(minDepth, maxDepth);
+ assert_equals(minDepth, cutoff);
+},"Parsing XML fails when the nesting depth is 5000");
+
+</script>